Add Anthropic API key validity check

What does this MR do and why?

This adds a validity check for Anthropic API keys (starting with prefix sk-ant-).

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. AnthropicClient.

The verifier calls GET https://api.anthropic.com/v1/models (reference), sending the token in the x-api-key header alongside anthropic-version. The endpoint returns read-only model metadata: it spends no tokens and changes nothing, and a 200 is positive proof that the key authenticated.

We map the responses to the following statuses/outcomes:

Response Outcome
200 active
401 inactive
429 RateLimitError
500, 502, 503, 504, 529 NetworkError
anything else 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

Per Anthropic's documented error taxonomy, a 403 is permission_error: "Your API key does not have permission to use the specified resource." An unrecognised key returns 401 instead, so a 403 means the key authenticated and simply lacks access. Mapping 403 to inactive would tell a user a working credential is dead and does not need rotating, so it falls through to unknown. The same reasoning covers 402 billing_error – a valid key on an account with a payment problem. It is unmapped, so it also resolves to unknown.

Why not use the messages endpoint

An earlier version of this check sent an empty body to POST /v1/messages and read the resulting 400 as proof of life: the request shape was rejected, so authentication must have passed. That worked out a live key from a rejection, and it relied on Anthropic checking credentials before the request body, which the API does not document. If that order ever changed, a revoked key would answer 400 too and we would report it as active. GET /v1/models removes the guesswork, and 400 now falls through to unknown like any other unmapped status.

Checked against the live API with a real key: 200 while active, then after revoking it, 401 with {"type": "authentication_error", "message": "API key is invalid."}. Anthropic puts the signal in type, so no body parsing is needed here, unlike OpenAI's code: invalid_api_key.

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

How to set up and validate locally

  1. Run the specs:

    bundle exec rspec ee/spec/lib/security/secret_detection/partner_tokens/anthropic_client_spec.rb \
      ee/spec/services/security/secret_detection/partner_tokens/registry_spec.rb
  2. Confirm the registry resolves the token type and that the rate limit rule exists:

    Security::SecretDetection::PartnerTokens::Registry.client_for('anthropic_key')
    # => #<Security::SecretDetection::PartnerTokens::AnthropicClient>
    Gitlab::ApplicationRateLimiter.period_for(:partner_anthropic_api)
    # => 60 seconds
  3. Optional, with a real credential – a revoked token should come back inactive and a live one active:

    Security::SecretDetection::PartnerTokens::Registry
      .client_for('anthropic_key').verify_token(ENV['TOKEN']).status

MR acceptance checklist

I have evaluated this MR against the MR acceptance checklist.

Edited by Ahmed Hemdan

Merge request reports

Loading
Loading