#2257·gitleaks

airtable-personnal-access-token never matches a real token unless the word "airtable" is also nearby (keyword prefilter blind spot)

Author: dylanpulverCreated Sep 1, 2026Updated Sep 3, 2026

Describe the bug

The airtable-personnal-access-token rule can only fire on a real Airtable PAT when the literal string airtable happens to appear elsewhere in the same fragment. A structurally-valid PAT on its own is silently skipped.

Mechanism: the scan is gated by a keyword prefilter (detect/detect.go:319-345) — a rule is only applied to a fragment if the (lower-cased) fragment contains one of the rule's keywords. This rule's regex is a self-signaling unique-token pattern:

regex    = '''\b(pat[[:alnum:]]{14}\.[a-f0-9]{64})\b'''
keywords = ["airtable"]

The regex requires the literal prefix pat…; it does not contain airtable. So a token that matches the regex, but has no airtable word beside it, is dropped before the regex ever runs. The keyword should be a substring the regex guarantees (as with glcbt-, gldt-, xoxb-, xoxp-, whose keyword is the required prefix). airtable-api-key is correct because its regex is built with GenerateSemiGenericRegex(["airtable"], …), which embeds airtable into the pattern; the PAT rule uses a raw regex that does not.

To Reproduce (synthetic token; matches the regex, invalid):

TOK='patZx9Kq2mNvB7LcD.0f1e2d3c4b5a69788796a5b4c3d2e1f00112233445566778899aabbccddeeff0'
printf '%s\n' "$TOK"                    > bare.txt   # gitleaks: "no leaks found", exit 0
printf 'airtable_pat = "%s"\n' "$TOK"  > ctx.txt    # gitleaks: airtable-personnal-access-token, exit 1
gitleaks detect --no-git --source bare.txt
gitleaks detect --no-git --source ctx.txt

The rule's own regex matches $TOK, so the only thing gating detection is the keyword prefilter. For contrast, a bare xoxb-… Slack token with no slack word present is detected, because slack-bot-token's keyword is its required prefix.

Why it wasn't caught by validation: utils.Validate runs true positives through the full prefilter, but the true positives come from GenerateSampleSecrets("airtable", …), which wraps every sample as airtableToken="…" — so the sample always contains the keyword, and the check can't observe the bare-token miss. There is no generation-time assertion that a rule's keywords are implied by its regex.

Expected behavior: a PAT that matches the pattern is detected regardless of whether the word airtable is nearby (that self-signaling structure is the whole reason for a dedicated rule over generic-api-key).

Suggested fix (happy to PR): set Keywords: []string{"pat"} in cmd/generate/config/rules/airtable.go and regenerate the config, matching the required-prefix convention. Honest tradeoff: pat is a common substring (path, patch, update), so the regex would be evaluated on more fragments — but it is anchored (\bpat + 14 alnum + literal .) and fails fast. If you'd rather keep it context-gated, the rule arguably adds nothing over generic-api-key and could be dropped instead — your call. I can also add a generation-time lint for the whole class if useful.

I swept all 222 shipped rules for this shape; airtable-personnal-access-token is the one clean instance. (Separately, sidekiq-sensitive-url, slack-webhook-url, and slack-config-access-token have unescaped . in their domain/prefix literals — a minor over-match, not this bug.)

Basic Info: OS macOS; Gitleaks built from source at current master (commit b58d3f1).

Disclosure: this report was prepared with AI assistance; the reproduction and analysis were verified against the built binary.