Draft: Add Slack token validity check

What does this MR do and why?

This adds a validity check for Slack access tokens: bot (xoxb-), user (xoxp-), workspace access (xoxa-) and workspace (xoxs-).

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

The verifier calls POST https://slack.com/api/auth.test with the token as a Bearer credential.

We map the responses to the following statuses/outcomes:

Response Outcome
200 classified from the response body, see below
429 RateLimitError
500, 502, 503, 504 NetworkError
anything else unknown

Please note that no status code maps to inactive for this vendor – inactive comes only from the body errors listed below.

Reporting a live token as dead is worse than reporting nothing, so unmapped statuses are always unknown.

Why the status code is not enough

Slack signals the outcome in the response body rather than the status code – its auth.test reference documents an invalid token as {"ok": false, "error": "invalid_auth"} and instructs that "callers should always check the value of the ok parameter in the response". A generic "200 means active" check would therefore report every dead Slack token as active.

The verifier reads body["ok"], and classifies inactive only on the errors Slack documents as meaning the token is dead: invalid_auth, account_inactive, token_revoked, token_expired. ratelimited raises RateLimitError; any other error – a scope problem, for instance – is unknown.

not_authed is deliberately not treated as dead. It means no token was sent, so it points at a bug on our side rather than a dead credential – and reporting a token dead when we never actually sent it is the worst outcome available.

A known coverage gap

The Slack token rule detects five kinds of Slack token: xoxb-, xoxp-, xoxa-, xoxs- and xoxr-. This check covers the first four, which are all access tokens.

xoxr- is left out on purpose. It is a refresh token – it exists to be exchanged for an access token and cannot authenticate an API call at all. Sending one to auth.test gets it rejected, and that rejection is indistinguishable from a genuinely revoked token, so we would report a live credential as dead.

When a finding is a xoxr- token, the verifier returns unknown and makes no API call. Nothing is reported incorrectly; refresh tokens simply do not get a validity result. Covering them needs a different check than auth.test, which is follow-up work rather than part of this MR.

Why the format check is not a copy of the rule

Everywhere else in this stack the verifier's pattern mirrors its detection rule exactly. Slack is the exception, because the rule's regex is written to spot a token in a file rather than to describe a whole one:

regex = "xox[baprs]-([0-9a-zA-Z]{10,48})"

No hyphens are allowed after the prefix, so the match stops at the first one – enough for detection, but it covers only the leading section of a real token. A format check has to answer a different question ("is this entire value a token?"), which means anchoring, and anchored that regex matches none of the rule's own five examples. Mirroring it would silently disable the check.

So the pattern here encodes the real three-section shape instead, verified against the rule's fixtures: it accepts all five examples and rejects the negativeExample with an empty secret. The other negativeExample is placeholder filler (x repeated), which no shape-based check can exclude. The rule's regex is worth fixing upstream so it matches its own examples; this MR does not wait on that.

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/slack_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('Slack token')
    # => #<Security::SecretDetection::PartnerTokens::SlackClient>
    Gitlab::ApplicationRateLimiter.period_for(:partner_slack_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('Slack token').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