Skip to content

Fix GrafanaServiceAccount detector: emit unverified results, fix status codes, deduplicate#4944

Open
asivaprasad09 wants to merge 4 commits into
trufflesecurity:mainfrom
asivaprasad09:fix-grafana-serviceaccount-detector
Open

Fix GrafanaServiceAccount detector: emit unverified results, fix status codes, deduplicate#4944
asivaprasad09 wants to merge 4 commits into
trufflesecurity:mainfrom
asivaprasad09:fix-grafana-serviceaccount-detector

Conversation

@asivaprasad09
Copy link
Copy Markdown

@asivaprasad09 asivaprasad09 commented May 4, 2026

Summary

Problem

The existing GrafanaServiceAccount detector had several issues:

  1. Silent drop — if no *.grafana.net domain was found in the same chunk as the glsa_ token, the detector emitted no result at all. Valid tokens stored separately from their domain (e.g. token in .env, domain in config.yaml) were completely invisible.
  2. 403 treated as error — a 403 Forbidden response was falling into the indeterminate error bucket instead of being treated as a determinately invalid token.
  3. Connection leaksdefer res.Body.Close() without draining the body prevents HTTP connection reuse.
  4. Wrong HTTP client — used DetectorHttpClientWithNoLocalAddresses instead of common.SaneHttpClient() which is the standard across all other detectors.
  5. No deduplication — the same token could produce duplicate results if the regex matched multiple times in a chunk.

Changes

  • Always emit the token — if no domain is found in the chunk, an unverified result is emitted so the token is never silently lost. If a domain is found, verification is attempted as before.
  • Fix 403 handling401 and 403 both correctly return false, nil (determinately invalid).
  • Proper body drainio.Copy(io.Discard) before Close() to avoid connection leaks.
  • Switch to common.SaneHttpClient() — consistent with the rest of the codebase.
  • Deduplicate — use uniqueKeys and uniqueDomains maps before processing.
  • Extract verifyGrafanaServiceAccount() — verification logic moved to a named helper, consistent with detector conventions.

Before vs After

Scenario Before After
Token + domain in same chunk ✅ Verified ✅ Verified
Token alone, no domain ❌ Silently dropped ⚠️ Flagged unverified
Invalid token + domain ✅ Unverified ✅ Unverified
403 response ❌ Indeterminate error ✅ Determinately invalid
Duplicate matches ❌ Duplicate results ✅ Deduplicated

Test plan

  • Token + domain in same file → ✅ Found verified result with --only-verified
  • Token alone, no domain → unverified result emitted (not silently dropped)
  • Verified against real glsa_ tokens: 200 = valid, 401 = invalid

Note

Medium Risk
Medium risk because it changes secret detection/verification behavior (including emitting additional unverified findings) and adds a new detector plus a new DetectorType enum value, which can affect downstream integrations and result volume.

Overview
GrafanaServiceAccount detector fixes: tokens are now deduplicated and always emitted even when no *.grafana.net domain is present in the same chunk (unverified instead of being dropped). Verification logic is extracted to verifyGrafanaServiceAccount, switches to common.SaneHttpClient(), correctly treats 401/403 as determinately invalid, and drains response bodies to improve connection reuse.

New Together AI detector: adds a togetherai detector for tgp_v1_... API keys with optional online verification against GET /v1/models, registers it in defaults, and introduces DetectorType_TogetherAI in proto/detector_type.proto and generated enum code.

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

Akshara Sivaprasad added 2 commits May 4, 2026 12:12
Adds a detector for Together AI API keys (tgp_v1_ format).
Verifies keys via GET /v1/models endpoint.
…us codes, deduplicate

- Previously tokens with no co-located domain were silently dropped.
  Now an unverified result is always emitted so the token is never lost.
- Switch to common.SaneHttpClient() consistent with all other detectors.
- Properly drain response body with io.Copy(io.Discard) to avoid
  connection leaks.
- Treat 403 as determinately invalid (was falling into error bucket).
- Deduplicate keys and domains before processing to avoid duplicate results.
- Extract verification into verifyGrafanaServiceAccount() helper.
@asivaprasad09 asivaprasad09 requested a review from a team May 4, 2026 08:04
@asivaprasad09 asivaprasad09 requested review from a team as code owners May 4, 2026 08:04
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 4, 2026

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 3 committers have signed the CLA.

✅ asivaprasad09
❌ Akshara Sivaprasad
❌ cursoragent


Akshara Sivaprasad 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.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

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 8616d5c. Configure here.

Comment thread pkg/detectors/togetherai/togetherai.go
Comment thread pkg/detectors/grafanaserviceaccount/grafanaserviceaccount.go Outdated
Comment thread pkg/detectors/togetherai/togetherai_test.go
defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(`\b(glsa_[0-9a-zA-Z_]{41})\b`)
defaultClient = common.SaneHttpClient()
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.

Any particular reason why you've made this switch? We prefer to use detectors.DetectorHttpClientWithNoLocalAddresses with detectors that verify the credential against a custom domain.

Comment on lines -30 to -31
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
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.

nit: can we get this comment back please?

Comment on lines +63 to +72
if len(uniqueDomains) == 0 {
// No domain found in this chunk — emit unverified so the token is not silently dropped.
s1 := detectors.Result{
DetectorType: detector_typepb.DetectorType_GrafanaServiceAccount,
Raw: []byte(key),
SecretParts: map[string]string{"key": key},
}
results = append(results, s1)
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.

While I agree with the concept of emitting credentials that couldn't be verified because of absence of a domain, I believe we need to make the distinction clear between this and a token that was marked determinately unverified using a domain.

Result.ExtraData is a good place to do this. You can add a message field inside that indicates this. The ConfluenceDataCenter detector also does this.

switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized, http.StatusForbidden:
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.

Can you provide references to official documentation or examples where it says that a 403 indicates that the key is determinately invalid?

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.

Please add this new detector in a separate PR

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.

I just saw #4943. It looks like you branched off from this feature branch instead of main for this PR. Can you please update the base branch?

Copy link
Copy Markdown
Contributor

@mustansir14 mustansir14 left a comment

Choose a reason for hiding this comment

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

Hey, thanks for this!

I've requested changes mainly for the new detector you added. We would like you to do that in a separate PR, to keep the scope of this PR intact.

I have some questions/suggestions for the grafanaserviceaccount detector changes. Please check them out.

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.

4 participants