Skip to content

feat(detectors): add Stream.io detector with JWT verification#5062

Open
deerajcm wants to merge 3 commits into
trufflesecurity:mainfrom
deerajcm:add-streamio-detector
Open

feat(detectors): add Stream.io detector with JWT verification#5062
deerajcm wants to merge 3 commits into
trufflesecurity:mainfrom
deerajcm:add-streamio-detector

Conversation

@deerajcm

@deerajcm deerajcm commented Jun 22, 2026

Copy link
Copy Markdown

Summary

Add detector for Stream.io (GetStream.io) API credentials with full verification support using JWT token authentication.

Features

  • ✅ Detects Stream.io API keys and secrets in various formats
    • stream_api_key, getstream_key
    • Environment variables (STREAM_API_KEY, GETSTREAM_SECRET)
    • JSON configurations
    • Code constants
  • ✅ Optional app_id detection (not required for verification)
  • Full credential verification using JWT tokens
  • ✅ Multi-region support (us-east, eu-west, singapore, sydney, tokyo)

Technical Implementation

Authentication Challenge

Stream.io requires JWT tokens signed with HMAC-SHA256 for API authentication, not raw credentials. The detector:

  1. Generates JWT tokens with resource-specific claims:
    claims := jwt.MapClaims{
        "resource": "feed",
        "action": "*",
        "feed_id": "*",
    }
  2. Signs the token using the API secret with HS256
  3. Authenticates against Stream.io's feed API endpoints

Verification Logic

  • Endpoint: /api/v1.0/feed/flat/verify/
  • 200 status = Valid credentials (auth passed, feed exists)
  • 400 status = Valid credentials (auth passed, feed config doesn't exist)
  • 401/403 status = Invalid credentials

Why 400 = Valid?

When the API returns 400 "feed group does not exist":

  • ✅ The JWT token was validated successfully
  • ✅ The API secret is correct
  • ❌ The feed configuration doesn't exist (expected for verification)

This confirms credentials are valid without needing actual feed configurations.


Note

Medium Risk
New credential detectors increase scan surface and false positives; Stream verification performs outbound API calls with leaked secrets, and committed real-looking Stream credentials in tests are a serious leak risk if those keys are valid.

Overview
This PR introduces two new secret detectors and registers new DetectorType values (BasicAuth = 1057, StreamIO = 1058) in proto/detector_type.proto and the generated Go enums.

HTTP Basic Auth matches Authorization / auth headers with a Basic base64 payload, decodes and validates username:password, and emits SecretParts (username, password, encoded). Findings are always unverified (no target URL to probe). The scanner is wired into defaults via &basicauth.Scanner{}.

Stream.io (GetStream) finds optional app_id plus api_key / api_secret using prefixed regexes, pairs keys with secrets (and app IDs when present), and when verification is enabled signs an HS256 JWT from the secret and probes regional feed API endpoints—treating 200 and 400 as successful auth. Unit and integration tests cover detection patterns and optional live verification.

Note for reviewers: StreamIO is defined in proto but streamio.Scanner is not added to pkg/engine/defaults/defaults.go, so it will not run in the default engine until registered. The integration test file also embeds what look like real API credentials, which is a security concern if merged as-is.

Reviewed by Cursor Bugbot for commit 3966ab6. Bugbot is set up for automated code reviews on this repo. Configure here.

Deeraj CM and others added 3 commits June 22, 2026 10:26
Add detector for HTTP Basic Authentication tokens (BSCAU002).
Detects Authorization: Basic <base64> patterns and decodes them
to extract username:password credentials.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
feat(detectors): add BasicAuth detector
Add detector for Stream.io (GetStream.io) API credentials with full
verification support using JWT token authentication.

Features:
- Detects Stream.io API keys and secrets in various formats
  (stream_api_key, getstream_key, environment variables, JSON configs)
- Optional app_id detection (not required for verification)
- Full credential verification using JWT tokens signed with HMAC-SHA256
- Multi-region support (us-east, eu-west, singapore, sydney, tokyo)
- Comprehensive test coverage including real credential verification

Technical implementation:
- Generates JWT tokens with resource-specific claims for feed access
- Uses Stream.io feed API endpoint for verification
- Handles 400 status as successful auth (feed doesn't exist but auth passed)
- Returns 401/403 for invalid credentials

Test results:
- ✅ Detection tests pass
- ✅ Pattern matching tests pass
- ✅ Real credential verification: Verified=true
- ✅ All integration tests pass

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@deerajcm deerajcm requested a review from a team June 22, 2026 11:34
@deerajcm deerajcm requested review from a team as code owners June 22, 2026 11:34
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ deerajcm
❌ Deeraj CM


Deeraj CM seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 3966ab6. Configure here.

// REPLACE THESE WITH YOUR REAL STREAM.IO CREDENTIALS FROM DASHBOARD
realAppId := "1644228" // Numeric ID from dashboard
realApiKey := "4u3ncebvw27r"
realApiSecret := "as57ayreare6wqz2vj2uvsgcmpgbjejcchkdd9723gku26dqvwezgpbwnbpwmsn7"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real API credentials committed in integration test

High Severity

Real Stream.io credentials (realAppId, realApiKey, realApiSecret) are hardcoded in the test file instead of placeholder values. The skip guard on line 24 checks realAppId == "your_app_id_here", but the actual value is "1644228", so the guard never triggers and these credentials are permanently exposed in the repository. Ironic for a secret-detection tool to ship with leaked secrets.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3966ab6. Configure here.

&azuresearchquerykey.Scanner{},
&azure_storage.Scanner{},
&azurerepositorykey.Scanner{},
&basicauth.Scanner{},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StreamIO detector not registered in engine defaults

High Severity

The streamio package is implemented but never imported or registered in defaults.go. Only the basicauth detector was added. Without registration, the StreamIO detector will never run during scans, making the entire feature dead code.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3966ab6. Configure here.

// Check if all regions give same error - if yes, credentials are definitely invalid
bodyStr := string(bodyBytes)
lastErr = fmt.Errorf("auth failed (status %d): %s", res.StatusCode, bodyStr)
continue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verification returns error on definitive auth failure

Medium Severity

When verifyStreamIO receives a 401/403 response, it returns (false, lastErr) with a non-nil error. The established convention (e.g., in abstract.go) is to return (false, nil) for definitively invalid credentials. Returning an error causes SetVerificationError to treat the result as "verification inconclusive" rather than "credentials invalid," misrepresenting the outcome. Additionally, the loop continues trying other regions after a 401, making unnecessary API calls since authentication is not region-dependent.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3966ab6. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants