Skip to content

Add Google OAuth2 Client Secret detector#5030

Open
asivaprasad09 wants to merge 4 commits into
trufflesecurity:mainfrom
asivaprasad09:add-google-client-secret-detector
Open

Add Google OAuth2 Client Secret detector#5030
asivaprasad09 wants to merge 4 commits into
trufflesecurity:mainfrom
asivaprasad09:add-google-client-secret-detector

Conversation

@asivaprasad09

@asivaprasad09 asivaprasad09 commented Jun 11, 2026

Copy link
Copy Markdown

Summary

This PR adds a comprehensive detector for Google OAuth2 Client Secrets (GOCSPX- prefix) used to authenticate server-side OAuth2 flows.

Key features:

  • Detects GOCSPX- prefix followed by exactly 28 base64url-safe characters
  • Verifies secrets against Google's OAuth2 token endpoint
  • Pairs secrets with client IDs when both appear in the same data chunk
  • Interprets Google error responses to distinguish valid vs invalid credentials
  • Includes comprehensive test coverage with mock server verification

Changes

  • Added GoogleClientSecret (ID 1053) to proto/detector_type.proto
  • Created detector implementation at pkg/detectors/googleclientsecret/
  • Integrated detector into default detector list in pkg/engine/defaults/defaults.go
  • Updated proto-generated files with new detector type
  • Added comprehensive unit tests with 100% pass rate and mock HTTP server

Pattern Details

The detector uses two regex patterns:

Client Secret Pattern:

\bGOCSPX-([0-9A-Za-z_\-]{28})\b
  • Must start with GOCSPX- prefix
  • Followed by exactly 28 characters from base64url alphabet: [0-9A-Za-z_-]
  • Word boundary ensures clean extraction

Client ID Pattern (optional pairing):

\b([0-9]+-[0-9A-Za-z_-]+\.apps\.googleusercontent\.com)\b
  • Numeric project ID + token segment + .apps.googleusercontent.com
  • When found in the same data chunk, paired with the secret in RawV2

Testing

All tests pass successfully:

go test ./pkg/detectors/googleclientsecret -tags=detectors -v

Test coverage includes:

  • Pattern matching tests:

    • Bare GOCSPX tokens
    • Tokens in JSON client_secret fields
    • Environment variable assignments
    • YAML configuration files
    • Multiple tokens in same chunk
    • Invalid patterns (too short, too long, invalid characters)
    • Deduplication
  • Verification tests with mock server:

    • Verified secret with paired client_id
    • Verified secret without client_id (uses placeholder)
    • Unverified (invalid) secret
    • No match scenarios

Verification Strategy

The detector verifies secrets by sending a token exchange request to Google's OAuth2 endpoint:

POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded

client_id={id}&client_secret={secret}&grant_type=authorization_code&code=placeholder_code&redirect_uri=http://localhost

Why this works: Google validates the client_id + client_secret pair before checking the authorization code. This allows us to verify credentials without needing a real OAuth flow.

Error response classification:

  • invalid_grant → Credentials are valid; the grant itself is invalid ✅ Verified
  • invalid_request → Credentials are valid; request format issue ✅ Verified
  • unauthorized_client → Credentials are valid; client type restriction ✅ Verified
  • invalid_client → Credentials not recognized ❌ Unverified
  • Unexpected response → Returned as VerificationError for investigation

Client ID handling:

  • If client ID found in same chunk → paired verification
  • If no client ID found → uses placeholder 000000000000-placeholder.apps.googleusercontent.com
  • Both approaches allow Google to validate the secret format and existence

SecretParts Population

The detector properly populates SecretParts:

SecretParts: map[string]string{"key": rawSecret}

When both secret and client ID are present:

RawV2: []byte(clientID + ":" + rawSecret)

ExtraData includes:

ExtraData: map[string]string{
    "rotation_guide": "https://howtorotate.com/docs/tutorials/google/",
    "oauth2_error": errResp.Error,
    "oauth2_error_description": errResp.ErrorDescription,
}

Example Matches

Will detect:

{
  "client_id": "123456789012-abc123.apps.googleusercontent.com",
  "client_secret": "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"
}
GOOGLE_CLIENT_SECRET=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
google:
  client_secret: "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"

Will NOT detect (invalid format):

GOCSPX-tooshort              # Only 8 chars after prefix (need 28)
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Z  # 29 chars (too long)
GOCSPX-Xk9mT2nQ4vL7wP1sT3uY6hJ8b!  # Invalid char '!'

Security Impact

What this secret enables:

  • Impersonation of the registered Google OAuth2 application
  • Access to user data within the scopes granted to the application
  • Ability to complete OAuth2 flows on behalf of the application

Why detection matters:
Google Client Secrets are long-lived credentials often stored in configuration files, environment variables, and version control. Exposure allows attackers to bypass application-level access controls and obtain user authorization tokens.

Design Rationale

Why GOCSPX- prefix?
Google introduced this prefix in 2021 to make client secrets more easily identifiable and scannable. The prefix is unique to Google OAuth2 client secrets.

Why 28 characters exactly?
This is Google's fixed format for GOCSPX- secrets. The strict length requirement reduces false positives significantly.

Why use placeholder authorization code?
The OAuth2 token endpoint validates credentials before validating the authorization code, allowing credential verification without a complete OAuth flow. This is a common technique for verifying OAuth credentials.

Why pair with client ID?
Client secrets are always used with their corresponding client ID. When both are present in the same chunk, pairing them:

  1. Provides complete credential context in RawV2
  2. Enables more accurate verification
  3. Helps downstream consumers understand the full credential

🤖 Generated with Claude Code


Note

Medium Risk
Verification sends discovered client secrets to Google's live OAuth token endpoint, which is sensitive credential handling; otherwise the change is additive detector plumbing with tests.

Overview
Adds a Google OAuth2 client secret (GOCSPX-) detector and wires it into the default engine as GoogleClientSecret (proto ID 1053).

The scanner matches the GOCSPX- + 28-character token pattern (keyword GOCSPX-), optionally pairs a .apps.googleusercontent.com client ID in the same chunk into RawV2, and when verification is on posts a dummy authorization-code exchange to oauth2.googleapis.com/token, treating invalid_grant / invalid_request / unauthorized_client as verified and invalid_client as not. Results include rotation metadata and OAuth error fields in ExtraData. Unit tests cover pattern cases and mock HTTP verification.

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

This commit adds a comprehensive detector for Google OAuth2 Client Secrets:
- Detects GOCSPX- prefix followed by exactly 28 base64url-safe characters
- Verifies secrets against Google's OAuth2 token endpoint
- Pairs secrets with client IDs when both are present in the same data
- Interprets Google's error responses to distinguish valid vs invalid credentials
- Includes comprehensive test coverage with mock server verification

The detector adds GoogleClientSecret (ID 1053) to the detector type enum and
follows TruffleHog's best practices for secret detection and verification.

Key features:
- Pattern matching: GOCSPX-[0-9A-Za-z_-]{28}
- Optional client ID pairing: [digits]-[token].apps.googleusercontent.com
- Verification via OAuth2 token endpoint with placeholder grant
- Error response classification:
  * invalid_grant/invalid_request → credentials recognized (verified)
  * unauthorized_client → credentials recognized (verified)
  * invalid_client → credentials not recognized (unverified)
- Proper SecretParts population with RawV2 for paired credentials
- Rotation guide in ExtraData

Verification Strategy:
The detector sends a token exchange request with a placeholder authorization
code to Google's OAuth2 endpoint. Google validates the client_id + client_secret
pair before checking the authorization code, allowing us to verify credentials
without needing a real OAuth flow. The error response type indicates whether
the credentials are genuine.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@asivaprasad09 asivaprasad09 requested a review from a team June 11, 2026 05:48
@asivaprasad09 asivaprasad09 requested review from a team as code owners June 11, 2026 05:48
Comment thread pkg/detectors/googleclientsecret/googleClientSecret.go Outdated
switch errResp.Error {
case "invalid_grant", "invalid_request":
// Credentials are known to Google; the grant/request itself is invalid.
return true, extraData, nil

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.

invalid_request marked verified despite unchecked credentials

Medium Severity

The function's own doc comment on line 125 states that invalid_request is triggered "before credential checks," meaning Google has not validated the secret. Yet the code on line 174 treats this response as verified = true. If the comment is accurate, this causes false positives — any secret (valid or not) that triggers invalid_request would be incorrectly reported as a verified credential. The inline switch comment ("Credentials are known to Google") contradicts the doc comment.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 13047a9. Configure here.

asivaprasad09 and others added 2 commits June 12, 2026 12:06

@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 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 4186815. Configure here.

case http.StatusUnauthorized:
if errResp.Error == "unauthorized_client" {
return true, extraData, nil
}

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.

unauthorized_client checked under wrong HTTP status code

Medium Severity

The unauthorized_client error is only handled under http.StatusUnauthorized (401), but per RFC 6749 §5.2 the OAuth2 token endpoint returns this error with HTTP 400. Google can also return it with 400. The existing boxoauth detector in this codebase correctly handles unauthorized_client under StatusBadRequest. When Google returns unauthorized_client with a 400 status, the inner switch falls through (it doesn't match "invalid_grant", "invalid_request", or "invalid_client"), then the outer switch also falls through, producing a spurious verification error instead of marking valid credentials as verified.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4186815. 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.

1 participant