Add Datadog API key validity check
What does this MR do and why?
This adds a validity check for Datadog API keys.
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. DatadogClient.
The verifier calls GET https://api.datadoghq.com/api/v1/validate, sending the key in the DD-API-KEY header. Unlike the other vendors in this stack, Datadog provides an endpoint whose only job is to answer whether a key is valid.
We map the responses to the following statuses/outcomes:
| Response | Outcome |
|---|---|
200 |
active |
403 |
inactive |
429 |
RateLimitError |
500, 502, 503, 504 |
NetworkError |
| anything else | unknown |
Reporting a live token as dead is worse than reporting nothing, so unmapped statuses are always unknown.
Why 403 means inactive here
403 maps to inactive for this vendor and no other, because Datadog documents it as the invalid-key answer for this endpoint rather than as a permission problem. From the Validate API key reference:
Check if the API key (not the APP key) is valid. If invalid, a
403is returned.
That reference documents exactly three codes — 200 valid, 403 authentication error, 429 too many requests. The 5xx mappings above are therefore defensive rather than documented, and anything else 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. The reviewed vendor spec for Datadog needed no corrections.
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. - Rate limit: 50 checks per minute per project. Datadog publishes no figure for the endpoint, 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/datadog_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('DataDogAPIKey') # => #<Security::SecretDetection::PartnerTokens::DatadogClient> Gitlab::ApplicationRateLimiter.period_for(:partner_datadog_api) # => 60 seconds -
Optional, with a real credential — a revoked token should come back
inactiveand a live oneactive:Security::SecretDetection::PartnerTokens::Registry .client_for('DataDogAPIKey').verify_token(ENV['TOKEN']).status
MR acceptance checklist
I have evaluated this MR against the MR acceptance checklist.