Add SendGrid API token validity check
What does this MR do and why?
This adds a validity check for SendGrid API tokens (starting with prefix SG.).
A user can then tell whether the exposed credential is still live rather than only that it was leaked.
Implementation
This follows ADR-006 and uses the registry directly to map the token straight to the verifier class/client, i.e. SendgridClient.
The verifier calls GET https://api.sendgrid.com/v3/scopes (reference) with the token as a Bearer credential.
That endpoint lists the scopes the key has, so it answers without sending mail or changing anything.
We map the responses to the following statuses/outcomes:
| Response | Outcome |
|---|---|
200 |
active |
401 |
inactive |
429 |
RateLimitError |
500, 502, 503 |
NetworkError |
anything else, including 403 |
unknown |
Please note that aside from 401, no other status falls to inactive.
Reporting a live token as dead is worse than reporting nothing, so unmapped statuses are always unknown.
Why 403 is not inactive
SendGrid's response-code reference documents 401 as "Requires authentication" – that is the code meaning the credential is no good, and the one we map to inactive.
The two documented causes of 403 both describe an account that is authenticated and working: "From address doesn't match Verified Sender Identity" and "You are temporarily blocked from sending emails due to repeated bad requests." A misconfigured sender or a temporary sending block is not a dead key, so 403 falls through to unknown.
Generated, not hand-written
The client and its spec are the output of the Challenge #6 (closed) AI-assisted workflow rather than hand-written code. An LLM reads the vendor's API documentation and emits a vendor spec – plain data: endpoint, auth style, token pattern, status map – a human reviews that data, and a deterministic generator turns it into the verifier and its spec. Keeping the model's output as data instead of code is what makes the review tractable.
The generator itself is not on master yet (it is part of the Challenge #6 (closed) PoC), so only its
output lands here.
Two corrections were made to the reviewed vendor spec before generating:
403was mapped toinactive. Checking that against SendGrid's documented response codes showed it was wrong for the reason above, so403is now unmapped and resolves tounknown.- The token pattern used to spell out the
22.43segment shape. It now mirrors the detection rule exactly, dot-anywhere included. The narrower version rejected any detected string that was not in that shape, and a format rejection returnsunknownwithout recording a metric or a log line – so those findings would never have been attempted and nothing would have shown it. Mirroring the rule means everything the scanner detects is actually checked.
Stack Position
We have 8 MRs for all new validity checks generated during Sec Challenge #6, with the base targeting master.
Each MR in the stack adds exactly one validity check: the client class, its spec, the registry entry, and the rate limit rule.
Stack (in review order)
| # | Check | Token type |
|---|---|---|
| 1 | GitHub PAT | Github Personal Access Token |
| 2 | OpenAI project key | OpenAiProjectKey |
| 3 | Anthropic API key | anthropic_key |
| 4 | Slack access tokens | Slack token |
| 5 | Stripe live secret key | StripeLiveSecretKey |
| 6 | Datadog API key | DataDogAPIKey |
| 7 | SendGrid API token | Sendgrid API token |
| 8 | Heroku API key | Heroku API Key |
Notes
- Token type and pattern were verified against the authoritative rule in
secret-detection-rules, including its five example keys. - Rate limit: 60 checks per minute per project. SendGrid publishes no figures for its endpoints, so this is a cautious cap of our own.
- Unmapped statuses are covered by the shared example
a partner token client, which this stack adds once and every verifier reuses. - Verifier code originates from the Sec Challenge #6 PoC branch
ghavenga-summit-ch6-validity-checks.
How to set up and validate locally
-
Run the specs:
bundle exec rspec ee/spec/lib/security/secret_detection/partner_tokens/sendgrid_client_spec.rb \ ee/spec/services/security/secret_detection/partner_tokens/registry_spec.rb -
Confirm the registry resolves the token type and that the rate limit rule exists:
Security::SecretDetection::PartnerTokens::Registry.client_for('Sendgrid API token') # => #<Security::SecretDetection::PartnerTokens::SendgridClient> Gitlab::ApplicationRateLimiter.period_for(:partner_sendgrid_api) # => 60 seconds -
Optional, with a real credential – a revoked token should come back
inactiveand a live oneactive:Security::SecretDetection::PartnerTokens::Registry .client_for('Sendgrid API token').verify_token(ENV['TOKEN']).status
MR acceptance checklist
I have evaluated this MR against the MR acceptance checklist.