validators

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

This file implements the contextual detectors: credentials that are identified by WHERE they appear (an HTTP Authorization header, a Cookie/Set-Cookie header, the userinfo component of a URL, a CLI password/token flag) rather than by a provider-specific token shape (validators.go et al.) or a bare key=value assignment (generic.go); assignments are deliberately not duplicated here.

All four validators redact ONLY the credential value: header names, URL scheme/host/path structure, cookie names, and flag names are always preserved byte for byte.

Escaped/embedded header lines

Log pipelines frequently embed raw header lines inside JSON strings (`{"line":"\"authorization: bearer abc...\""}`). The case-folded triggers still hit inside such strings, so every value alphabet here deliberately excludes '"' and '\\': a value terminated by an escaped closing quote (`...abc\"`) simply ends at the backslash instead of swallowing the JSON framing.

A ValidateFunc returns exactly one span, but one Cookie header can carry several credential-bearing pairs. Returning one wide span from the first to the last qualifying value would over-redact the benign pairs between them, and the `cookie:` trigger fires only once per header no matter how many pairs follow. The resolution used by cookie-session-token: the header trigger (`cookie:` / `set-cookie:`) confirms the FIRST qualifying pair's value, and the rule additionally registers the qualifying pair names themselves (`session=`, `sid=`, `token=`, `jwt=`, `auth=`) as triggers, so each qualifying pair produces its own trigger occurrence and therefore its own span. The Aho-Corasick matcher reports every occurrence of every literal, so a header with three token-bearing pairs gets three pair-trigger hits even though `cookie:` hits once. To keep those weak pair literals from firing on arbitrary key=value text (which the generic assignment rules already cover), the pair-trigger path demands cookie context: the literal "ookie" (Cookie:/Set-Cookie:, however cased) somewhere in the lookbehind, or an immediately preceding `;`-separated pair boundary. When that context is absent the pair path reports no match.

This file implements CI/devops provider credential validators: CircleCI personal/project API tokens, Buildkite API access tokens, Datadog API and Application keys, Grafana service-account and cloud-access-policy tokens, Doppler tokens, and Vercel access tokens. Each function follows the same contract as the seed validators in validators.go: pure, panic-free functions of window that report a window-relative half-open span to redact.

Several of these providers (Buildkite, Datadog, and legacy Vercel tokens) issue bare fixed-length alphanumeric/hex values with no in-token prefix at all — the same shape as a git commit SHA or an MD5 hash. Per docs/RULE_AUTHORING.md, a generic value shape like that needs additional context to be a safe deterministic rule, so these are implemented as contextual assignments keyed on the provider's own documented environment-variable name (the same design AWSSecretAccessKey, cloud.go, already uses for AWS's equally prefix-less secret access key).

CircleCI and Vercel each still have an older, bare, prefix-less token format in circulation (a 40-character hex string for CircleCI, a 24-character alphanumeric string for legacy Vercel). CircleCI's legacy format has no distinguishing context at all — the exact same shape as a git SHA-1 and, separately, a Buildkite token — so it is deliberately not matched here rather than risk a high false-positive rule; Vercel's legacy format is covered by VercelLegacyToken below since it does have a well-documented env var to key off.

This file implements the generic contextual credential rules: heuristic ValidateFuncs that fire on assignment-style context around keyword triggers like "api_key", "password", or "token" (as opposed to the deterministic provider-specific validators in validators.go, which confirm exact known token shapes). Header/URL/cookie contexts are intentionally out of scope here and handled by contextual validators.

Parsing model

All three validators share one parse: after the trigger, skip optional horizontal whitespace, skip one optional quote character (the trigger's own closing quote, for JSON-style quoted keys like "api_key": ...), skip optional whitespace again, require an assignment separator ("=", ":", or "=>"), skip optional whitespace again, then capture a value:

  • If the next byte is a quote character (double quote, single quote, or backtick) — optionally itself escaped with a leading backslash (the case where an already-quoted string is embedded in a further layer of escaping, e.g. JSON embedded in a log line) — the value runs until the matching quote (or matching escaped-quote sequence, if the opening quote was itself escaped) and is captured without the quotes.
  • Otherwise the value is the maximal run of printable, non-space bytes, stopping at whitespace or one of the printable separators comma, semicolon, closing brace, or a stray quote character.

This is deliberately not a full unescaping parser: a value containing an actual backslash-escaped instance of its own delimiting quote (e.g. a JSON string value that itself contains \") will be truncated at that inner escape rather than continuing past it. That is an accepted simplification — the truncated prefix is still fed through entropy.Secretlike and, being shorter, is if anything less likely to pass, which is the conservative failure direction for a redaction tool.

The trigger's preceding byte is also checked (isTriggerBoundaryOK): triggers are rejected when immediately preceded by an alphanumeric byte, so "x_api_key=" still fires (preceded by '_', non-alnum) but "capi_key=" does not (preceded by 'c', alnum) — the trigger must not be a suffix of a longer identifier.

False-positive budget

FalsePositiveBudgetPerTenMiB documents the accuracy target these balanced-profile rules are tuned against. It is enforced empirically by the accuracy corpus harness, not by any check in this package; the constant exists so that harness (and this file's authors) have one canonical number to reference and keep in sync.

This file implements generic-secret-assignment, the one deep-profile- only heuristic rule this codebase ships as of this writing. It exists to give the deep profile a real meaning distinct from balanced (see docs/PROFILES.md): balanced's three generic contextual rules in generic.go are deliberately tuned to a tight keyword list (api_key, secret_key, password, token, ...) to keep their false-positive rate low enough for a default profile. This rule widens that keyword surface to bare, generic words — "key", "secret", "auth", "access", "credential", "creds" — the same broader anchor set gitleaks' and betterleaks' own "generic-api-key" rule uses (confirmed directly against both projects' source: neither is actually keyword-free, despite sometimes being described that way — both require one of that same broad keyword set followed eventually by an assignment operator).

That broader surface catches real assignments balanced's specific keywords structurally cannot, e.g. "AWS Secret Key = ..." or "Client Auth Token: ..." contain no literal "secret_key" or "auth_token" substring. It also fires on far more ordinary, non-secret text (English prose containing "key"/"auth"/"access", source identifiers like "keyboard_layout" or "author"), so it is deliberately confined to the deep profile and paired with a stricter entropy bar (entropy.PresetDeepGenericValue) than any balanced-profile generic rule uses.

This file implements two JWT-based detectors sharing one parser: the standalone JWT detector (a bare JWS compact serialization, header.payload.signature, anywhere in the input with no surrounding context required) and the Supabase service-role key detector (the same shape, additionally requiring a `"role":"service_role"` claim in the decoded payload — the claim Supabase bakes into its highest-privilege API key, the one Postgres treats as bypassing row-level security). Contextual occurrences (Authorization headers, cookies) are also caught by the contextual rules; these rules exist for the bare tokens those rules cannot see — JWTs pasted into agent transcripts, log lines, and tool output.

The shape enforced here follows the betterleaks standalone-JWT rule (both the header and the payload segment must begin with "ey" — the base64 encoding of "{" — which is what keeps the false-positive rate low on eyJ-heavy auth-debugging transcripts), tightened to the base64url alphabet RFC 7515 actually specifies for the two JSON segments.

Validators for payments, messaging, and project-management provider credentials: Stripe, Slack (user/app tokens — bot tokens are the pre-existing SlackBotToken in validators.go), SendGrid, Twilio, Linear, and Notion.

All functions here follow the same contract as validators.go: pure, window-relative, byte-only, never panic regardless of how the window is truncated relative to the trigger or the rule's configured lookaround.

Source-control and package-registry credential validators: GitHub (OAuth, App, refresh, and fine-grained PAT), GitLab (PAT, runner, and deploy tokens), npm, PyPI, and Docker Hub. The classic GitHub PAT validator (GitHubPAT, trigger "ghp_") already lives in validators.go and is not duplicated here.

These share two shapes:

  • prefix + a fixed-length run of ASCII alphanumeric characters (GitHub OAuth/App/refresh tokens, npm access tokens): see fixedAlnumToken.
  • prefix + a variable-length run of a wider token alphabet ([A-Za-z0-9_-]) bounded by a min/max length (GitLab PAT/runner/ deploy tokens, PyPI API tokens, Docker Hub PATs): see runToken.

GitHubFineGrainedPAT is the one irregular shape (two fixed-length alnum runs separated by a literal underscore) and is implemented directly.

Package validators holds the ValidateFunc implementations referenced by the generated built-in rule tables (internal/rules/zz_generated_rules.go).

Every function here has the signature

func(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

as required by rules.ValidateFunc: window is the bounded slice of bytes around a trigger occurrence (trigger occupies window[trigStart:trigEnd]), and the function reports the half-open span within window to redact. Implementations here are pure functions of window: no I/O, no logging, and no retention of window past the call. They must never panic or index out of range, regardless of how short window is relative to the trigger or the rule's configured lookahead — callers may hand them a window truncated at a chunk or input boundary.

This package currently holds two seed validators (GitHubPAT, SlackBotToken) that prove the spec-to-generated-code pipeline end to end. Additional files in this package provide the full validator catalog.

Index

Constants

View Source
const FalsePositiveBudgetPerTenMiB = 1

FalsePositiveBudgetPerTenMiB is the accuracy target for the balanced- profile generic contextual rules in this file: fewer than this many false-positive redactions per 10 MiB of ordinary keyword-dense log and config data (the kind of text where "password", "token", "api_key" etc. appear constantly without carrying an actual secret value). The accuracy corpus harness measures against this number; it is not self-enforcing at build or test time.

Variables

This section is empty.

Functions

func AWSAccessKeyID

func AWSAccessKeyID(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

AWSAccessKeyID confirms the AWS access key ID shape: one of the AKIA/ASIA/ABIA/ACCA triggers followed by exactly 16 RFC-4648 upper-base32 characters, bounded on both sides by a byte that is not an uppercase letter or digit (or the edge of window). It rejects AWS's own documentation placeholder ("AKIAIOSFODNN7EXAMPLE") and, more generally, any key whose 16-character body ends with "EXAMPLE", which real access key IDs never do.

func AWSBedrockLongLivedAPIKey

func AWSBedrockLongLivedAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

AWSBedrockLongLivedAPIKey confirms the Amazon Bedrock long-lived API key shape: the fixed trigger "ABSKQmVkcm9ja0FQSUtleS", followed by 91-251 further characters of standard base64 and up to two '=' padding characters — the base64 encoding of a structured "BedrockAPIKey-<id>-at-<account-id>:<secret>" payload that AWS accepts in place of SigV4 credentials for calling Bedrock's model-invocation APIs.

https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html https://github.com/awslabs/git-secrets (canonical AWS-maintained detection pattern)

func AWSBedrockShortLivedAPIKey

func AWSBedrockShortLivedAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

AWSBedrockShortLivedAPIKey confirms the Amazon Bedrock short-lived API key shape: the fixed trigger "bedrock-api-key-YmVkcm9jay5hbWF6b25hd3MuY29t", followed by 20-4096 further characters of standard base64 and up to two '=' padding characters.

https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html https://github.com/aws/aws-bedrock-token-generator-js (canonical AWS-maintained token construction) https://github.com/awslabs/git-secrets (canonical AWS-maintained detection pattern)

func AWSSecretAccessKey

func AWSSecretAccessKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

AWSSecretAccessKey confirms the shape of a value assigned to an aws_secret_access_key key (the trigger is matched case-insensitively, so this also fires on AWS_SECRET_ACCESS_KEY): optional spaces/tabs, a '=' or ':' separator (optionally preceded by a JSON-style closing quote), optional spaces/tabs, an optional opening quote, exactly 40 characters of [A-Za-z0-9/+=], a matching closing quote if one was opened, and a boundary. Only the 40-character value is reported as the redaction span — the key name itself is never touched. It rejects an all-identical value and the well-known AWS documentation placeholder secret.

func AnthropicAPIKey

func AnthropicAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

AnthropicAPIKey confirms the Anthropic API key shape: trigger "sk-ant-" followed by 80-130 bytes of [A-Za-z0-9_-] (this covers both the common "api03-"/"admin01-"-style sub-segment plus the ~80-120 character body, since that sub-segment's own alphabet is a subset of the body's, and the literal "sk-ant-" prefix's total post-prefix length is what's actually bounded). Real Anthropic keys are never shorter than ~100 characters after the prefix; the 80 floor rejects the short fabricated "sk-ant-api03-<32-40 chars>" keys that dominate documentation and test fixtures. It also rejects placeholder bodies (all one character, or any long repeated run) and requires a non-alphanumeric byte (or end of window) on both sides.

func AuthorizationHeader

func AuthorizationHeader(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

AuthorizationHeader confirms a credential in an Authorization: or Proxy-Authorization: header (the triggers include the colon and are case-folded). Per-scheme behavior, kept deliberately small:

  • Bearer / Token (case-insensitive): the value is the following run of token68 bytes (see isAuthTokenChar). It must be at least authBearerMinLen bytes and not an isPlaceholder value; pure template refs ("${...}") and angle-bracket placeholders ("<token>") never even parse, since '$', '{', '<' are outside the value alphabet, leaving an empty (rejected) value.
  • Basic: the value is the following run of base64 bytes, at least authBasicMinLen long. No placeholder/entropy check: a well-formed base64 credential pair in this position is always sensitive.
  • Any other scheme (Digest, AWS4-HMAC-SHA256, ...): conservative. If the rest of the line contains a case-sensitive "Signature=" (AWS SigV4), only that parameter's value token is redacted. Otherwise, if the rest of the line mentions "password" (case-insensitive), the whole parameter blob up to end of line is redacted (over-redaction of an explicitly password-bearing header is the safe direction). Otherwise there is no match — a plain Digest challenge/response never fires.

Only the credential value (or parameter blob) is redacted, never the header name or scheme word.

func AzureAppConfigurationSecret

func AzureAppConfigurationSecret(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

AzureAppConfigurationSecret confirms the shape of the value following a "Secret=" trigger (matched case-insensitively) in an App Configuration connection string ("Endpoint=...;Id=...;Secret=..."). This is the classic 32-byte base64 key shape; a longer, unpadded variant has been observed for some App Configuration secrets but is not yet documented by Microsoft, so it is deliberately not matched here rather than guessed at.

https://learn.microsoft.com/en-us/azure/azure-app-configuration/enable-dynamic-configuration-dotnet-core-push-refresh

func AzureServiceBusSASKey

func AzureServiceBusSASKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

AzureServiceBusSASKey confirms the shape of the value following a "SharedAccessKey=" trigger (matched case-insensitively). Service Bus, Event Hubs, and Relay namespaces all share this one SAS mechanism under an identical "sb://...servicebus.windows.net" connection-string grammar — the connection string alone cannot distinguish which of the three issued a given key, so this rule covers all of them.

https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-sas https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-get-connection-string

func AzureStorageAccountKey

func AzureStorageAccountKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

AzureStorageAccountKey confirms the shape of the value following an "AccountKey=" trigger (matched case-insensitively): 86-88 characters of A-Za-z0-9/+ followed by the literal "==" padding of a standard 64-byte base64-encoded blob, bounded by a byte that cannot extend the base64 run or its padding (a ';' commonly follows in an Azure connection string, which already satisfies this boundary). If the base64 run reaches the end of window before the "==" padding can be confirmed, the match is rejected outright rather than guessing at what a truncated window might have contained. It rejects a placeholder body, mirroring azureShortBase64Secret below.

func BuildkiteAPIToken

func BuildkiteAPIToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

BuildkiteAPIToken confirms the shape of a value assigned to BUILDKITE_API_TOKEN or BUILDKITE_AGENT_TOKEN (matched case-insensitively, the officially documented environment variable names for Buildkite's REST API token and agent registration token respectively): 40 characters of lowercase hex. Buildkite tokens have no fixed prefix of their own — identical in shape to a git SHA-1 hash — so this rule exists specifically for the documented variable-name context, per the file-level note above.

https://buildkite.com/docs/apis/managing-api-tokens https://buildkite.com/docs/agent/v3/tokens

func CerebrasAPIKey

func CerebrasAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

CerebrasAPIKey confirms the Cerebras API key shape: trigger "csk-" followed by 32-64 alphanumeric characters.

Cerebras does not publish a format spec on any source reachable during development: no gitleaks or TruffleHog detector exists for it, and inference-docs.cerebras.ai itself was unreachable. The "csk-" prefix and a 32-character example body come from several independent third-party integration guides that converge on the same placeholder shape ("csk-1234567890abcdef1234567890abcdef"), not from Cerebras's own docs, which show only a generic "your-api-key-here" placeholder. This should be re-verified against inference-docs.cerebras.ai/api-reference/authentication when reachable.

func CircleCIToken

func CircleCIToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

CircleCIToken confirms the current CircleCI personal/project API token shape: trigger "CCIPAT_" (personal) or "CCIPRJ_" (project), followed by a 20-24 character base58 UUID segment, a literal '_', and a fixed 40-character lowercase hex segment. CircleCI's older bare 40-character hex tokens (personal and project tokens were the same shape) are not matched by any rule here: that shape has no distinguishing context at all, identical to a git SHA-1 hash or a Buildkite token, so a deterministic rule for it would carry an unacceptable false-positive rate.

https://circleci.com/changelog/new-format-for-api-access-tokens https://circleci.com/docs/managing-api-tokens/

func CohereAPIKey

func CohereAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

CohereAPIKey confirms the shape of a value assigned to CO_API_KEY or COHERE_API_KEY (matched case-insensitively; CO_API_KEY is the keyword gitleaks' own "cohere-api-token" rule keys on): 40 characters of mixed-case alphanumeric. Cohere's key has no prefix of its own — a bare 40-character token, the same shape as many unrelated opaque or hex identifiers — so this rule is keyed on the documented variable name instead, the same design AWSSecretAccessKey (cloud.go) already uses for AWS's equally prefix-less secret access key.

https://docs.cohere.com

func CommandLinePasswordFlag

func CommandLinePasswordFlag(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

CommandLinePasswordFlag confirms a secret passed to a CLI flag (--password, --passwd, --token, --api-key, --secret; case-folded). The short "-p " form is deliberately not a trigger — it is hopelessly false-positive-prone (-p is "port" as often as "password").

The byte immediately after the trigger must be '=' or horizontal whitespace: anything else means the flag name continues ("--password-file", "--token-url") and there is no match. The byte before the trigger must not be '-' or alphanumeric ("---password", "x--token" are not flags), except for a '-' that opens the line: that is the removal marker of a unified diff line ("-" + "--password=..."). A bare (unquoted) value must not start with '-': that is the next flag, not a value ("--password --prompt").

The value is either quoted (single/double/backtick, tolerating a backslash-escaped opening quote for JSON-embedded command lines, via the shared detectQuote/findQuotedEnd helpers) or a bare argument run. Placeholder and template-ref values are rejected via isPlaceholder. Password flags require only length >= cliPasswordMinLen; token/key/secret flags require length >= cliTokenMinLen AND entropy.PresetLooseToken, since their values are machine-generated and should look random. Only the value is redacted.

func CookieSessionToken

func CookieSessionToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

CookieSessionToken confirms a credential-bearing cookie pair. The rule registers two kinds of triggers and this validator dispatches on which one fired (by inspecting window[trigStart:trigEnd]):

  • Header triggers ("cookie:", "set-cookie:", trailing ':'): the pairs after the colon are scanned in order and the FIRST pair whose name qualifies (cookieNameQualifies) and whose value qualifies (cookieValueQualifies) has its value confirmed. Later qualifying pairs in the same header are caught by the pair triggers below — see the file comment for why one validator call cannot return them all. Note the pair triggers fire only when a keyword directly abuts the '=' ("...token=", "...sid=", "sessionid="), so a non-first pair whose name buries its keyword elsewhere ("session_2=") is a known miss; the trigger list carries the common real-world names rather than every conceivable shape.
  • Pair triggers ("session=", "sessionid=", "sid=", "token=", "jwt=", "auth=", trailing '='): the full cookie name is extended backwards over name bytes (so "auth_token=" and "connect.sid=" are judged by their whole name, and "csrftoken=" is vetoed by its "csrf" prefix), cookie context is required (see cookiePairContextOK), and the following value must qualify. Without cookie context the pair path never matches: bare key=value assignments belong to the generic rules, not this one.

Only the cookie VALUE is redacted, never the name or header structure.

func CursorAPIKey

func CursorAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

CursorAPIKey confirms the Cursor API key shape: trigger "crsr_" followed by 20 or more characters of [A-Za-z0-9_-]. Cursor's own sensitive-prompt-guard hook script (github.com/cursor/cookbook) uses the regex crsr_[A-Za-z0-9_-]{20,} — an open floor with no upper bound at all, unlike every other rule in this file — so this validator imposes no separate maximum of its own: it consumes the maximal [A-Za-z0-9_-] run and only requires the 20-byte floor, relying on the rule's own maxLookahead (deliberately generous, in ai.json) as the practical ceiling rather than an arbitrary internal cap that a longer real key could silently fall outside of.

func DatadogAPIKey

func DatadogAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

DatadogAPIKey confirms the shape of a value assigned to DD_API_KEY (matched case-insensitively, Datadog's own documented Agent configuration variable): 32 characters of lowercase hex. Like Buildkite's token, a Datadog API key has no prefix of its own, so this rule is keyed on the documented variable name.

https://docs.datadoghq.com/agent/guide/environment-variables/

func DatadogApplicationKey

func DatadogApplicationKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

DatadogApplicationKey confirms the shape of a value assigned to DD_APP_KEY (matched case-insensitively, the variable name Datadog's own Terraform provider and CLI tooling document): 40 characters of lowercase hex.

https://registry.terraform.io/providers/DataDog/datadog/latest/docs

func DeepSeekAPIKey

func DeepSeekAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

DeepSeekAPIKey confirms the DeepSeek API key shape: trigger "sk-" followed by exactly 32 lowercase alphanumeric characters (35 bytes total including the prefix). This shares its trigger literal with openai-legacy-key but the two never both confirm the same occurrence: OpenAI's legacy key is 51 bytes total with a mixed-case body containing the mandatory uppercase "T3BlbkFJ" infix, which a lowercase-only, exactly-32-byte DeepSeek body can never satisfy, and vice versa.

https://api-docs.deepseek.com

func DeepgramAPIKey

func DeepgramAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

DeepgramAPIKey confirms the shape of a value assigned to DEEPGRAM_API_KEY (matched case-insensitively): 40 characters of lowercase alphanumeric. Like Cohere's, Deepgram's key has no prefix of its own — TruffleHog's own detector keys on the literal word "deepgram" appearing nearby rather than on any byte prefix — so this rule is keyed on the documented environment-variable name instead.

https://developers.deepgram.com

func DockerHubOAT

func DockerHubOAT(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

DockerHubOAT confirms the Docker Hub organization access token shape: trigger "dckr_oat_" followed by 32-100 characters from [A-Za-z0-9_-]. Docker's own docs describe organization access tokens (OATs) as a distinct token type from personal access tokens (not tied to an individual user, managed by organization owners) but don't publish a byte-level format; the "dckr_oat_" prefix and 32-character floor come from two independently-maintained scanners (TruffleHog and the betterleaks gitleaks fork) that converge on the same shape. 100 is the same generous practical ceiling DockerHubPAT uses, not a documented maximum.

https://docs.docker.com/security/access-tokens/ (token type); prefix/length: github.com/trufflesecurity/trufflehog pkg/detectors/dockerhub/v2/dockerhub.go

func DockerHubPAT

func DockerHubPAT(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

DockerHubPAT confirms the Docker Hub personal access token shape: trigger "dckr_pat_" followed by 27-100 characters from [A-Za-z0-9_-]. 100 is a generous practical ceiling chosen for this validator, not a documented maximum.

https://docs.docker.com/security/for-developers/access-tokens/

func DopplerToken

func DopplerToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

DopplerToken confirms the Doppler token shape: one of seven scoped triggers ("dp.st.", "dp.pt.", "dp.ct.", "dp.sa.", "dp.said.", "dp.scim.", "dp.audit.") followed by a 40-44 character alphanumeric body. Only a service token (dp.st.) may additionally carry a single config label between the prefix and the body (e.g. "dp.st.dev.<body>"), per Doppler's own documented format regex; the other six prefixes' regexes have no label segment, so this is tried only when the matched trigger is "dp.st.", falling back to a bodyless-label match if the label candidate doesn't resolve to a valid body.

https://docs.doppler.com/reference/auth-token-formats

func GCPAPIKey

func GCPAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GCPAPIKey confirms the GCP API key shape: trigger "AIza" followed by exactly 35 characters of [0-9A-Za-z_-], bounded on both sides by a byte outside that alphabet (or the edge of window). It rejects an all-identical body.

func GenericAPIKeyAssignment

func GenericAPIKeyAssignment(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GenericAPIKeyAssignment confirms an api-key/access-token-shaped assignment: one of this rule's triggers (api_key, apikey, api-key, access_token, auth_token, client_secret, secret_key, secret_key_base, session_secret, private_token — see internal/rules/specs/generic-credentials.json), not itself a suffix of a longer identifier, followed by an assignment separator and a value that entropy.Secretlike confirms as plausibly random under entropy.PresetAssignmentValue. Only the value is reported for redaction.

func GenericBearerLikeTokenAssignment

func GenericBearerLikeTokenAssignment(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GenericBearerLikeTokenAssignment confirms a bearer-token-shaped assignment behind the weak, single "token" trigger. Because "token" alone is much weaker evidence of a secret position than api_key or password (it also matches ordinary code like "token = csv.next()" or a documentation placeholder like "token: <YOUR_TOKEN>"), this validator demands more of the value than the other two: it must be at least bearerLikeTokenMinLen (20) bytes long, and must pass entropy.Secretlike under entropy.PresetLooseToken with that same stricter MinLen. Only the value is reported for redaction.

func GenericPasswordAssignment

func GenericPasswordAssignment(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GenericPasswordAssignment confirms a password-shaped assignment: one of this rule's triggers (password, passwd, pwd), not itself a suffix of a longer identifier, followed by an assignment separator and a value. It uses entropy.PresetAssignmentValue with MinLen lowered to 8 (password values run shorter than typical API keys) and additionally rejects any value entropy.Classify buckets as ClassWordlike — a plain dictionary word is never an acceptable password value, and this is checked explicitly here (on top of entropy.Secretlike's own unconditional word-like rejection) because password fields are exactly the position where a bare dictionary word is most likely to show up in real fixtures and logs. This validator's separator requirement also guards against the `pwd`/`passwd` shell commands and utilities: bare "pwd" or "passwd: files systemd" (an /etc/nsswitch.conf-style line) never parse past the separator/value step and so never match. Only the value is reported for redaction.

func GenericSecretAssignment

func GenericSecretAssignment(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GenericSecretAssignment confirms an assignment behind one of this rule's broad, generic triggers (key, secret, auth, access, credential, creds — see internal/rules/specs/generic-deep.json), not itself a suffix of a longer identifier. Like the balanced generic rules, it first rejects an empty inline assignment (isEmptyInlineAssignment) — documentation fragments such as `key=` — since without that guard the closing quote is mistaken for an opening value quote and unrelated following text is captured as the "secret". It then tries the same tight, zero-gap parse the balanced generic rules use (parseAssignmentValue, which already understands the "key": ... JSON-quoted-key shape); if that fails to find an immediate separator, it falls back to a gap-tolerant parse (skipWeakGap) that allows a short intervening phrase or hyphenated/underscored identifier before the separator. The captured value must pass the same shape checks the balanced rules use (isIndirectAssignmentValue, isMachineTokenValue, isAlphaSpaceOnly) plus entropy.Secretlike under the stricter entropy.PresetDeepGenericValue. Only the value is reported for redaction.

func GitHubAppToken

func GitHubAppToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GitHubAppToken confirms the GitHub App installation/user-to-server token shape: trigger "ghu_" or "ghs_" followed by exactly 36 ASCII alphanumeric characters.

https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#githubs-token-formats

func GitHubFineGrainedPAT

func GitHubFineGrainedPAT(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GitHubFineGrainedPAT confirms the GitHub fine-grained personal access token shape: trigger "github_pat_" followed by 22 ASCII alphanumeric characters, a literal underscore, and 59 more ASCII alphanumeric characters.

https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#githubs-token-formats

func GitHubOAuthToken

func GitHubOAuthToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GitHubOAuthToken confirms the GitHub OAuth access token shape: trigger "gho_" followed by exactly 36 ASCII alphanumeric characters.

https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#githubs-token-formats

func GitHubPAT

func GitHubPAT(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GitHubPAT confirms the classic GitHub personal access token shape: trigger "ghp_" followed by exactly 36 ASCII alphanumeric characters and a non-alphanumeric byte (or end of window). It rejects placeholder bodies (isPlaceholder): repeated characters ("XXXX...X"), ascending alphabet runs ("ABCDEFGH..."), and keyword stand-ins, which real tokens never are but test fixtures and documentation examples routinely use.

func GitHubRefreshToken

func GitHubRefreshToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GitHubRefreshToken confirms the GitHub OAuth refresh token shape: trigger "ghr_" followed by exactly 36 ASCII alphanumeric characters.

https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#githubs-token-formats

func GitLabDeployToken

func GitLabDeployToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GitLabDeployToken confirms the GitLab deploy token shape: trigger "gldt-" followed by 20-50 characters from [A-Za-z0-9_-].

https://docs.gitlab.com/user/project/deploy_tokens/

func GitLabFeedToken

func GitLabFeedToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GitLabFeedToken confirms the classic (v1) GitLab feed token shape: trigger "glft-" followed by exactly 20 characters from [A-Za-z0-9_-]. This is GitLab's own default Devise.friendly_token length for the user model's FEED_TOKEN_PREFIX field, corroborated by gitleaks' "gitlab-feed-token" rule and by GitLab's own client-side secret detection pattern.

GitLab also issues a newer, structurally different "v2" feed token (see GitLabFeedTokenV2) under the same "glft-" prefix; the two validators safely coexist on the shared trigger because a v2 token's body is always far longer than 20 bytes of [A-Za-z0-9_-] (it embeds a 64-hex-character HMAC plus a dash-separated numeric ID), so this validator's exact-20 length check never confirms one.

https://docs.gitlab.com/security/tokens/#token-prefixes

func GitLabFeedTokenV2

func GitLabFeedTokenV2(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GitLabFeedTokenV2 confirms the newer, path-dependent GitLab feed token shape: trigger "glft-" followed by exactly 64 hex characters, a literal '-', and one or more ASCII digits (the numeric user ID this token is scoped to). Unlike v1, this body is an HMAC-SHA256 digest of the specific feed path keyed by the user's real feed token, not a standalone random secret — but it's still sensitive: GitLab's own backend accepts it as a bearer credential for that user's feed.

https://docs.gitlab.com/security/tokens/#token-prefixes

func GitLabPAT

func GitLabPAT(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GitLabPAT confirms the GitLab personal access token shape: trigger "glpat-" followed by 20-50 characters from [A-Za-z0-9_-].

https://docs.gitlab.com/user/profile/personal_access_tokens/#create-a-personal-access-token

func GitLabPipelineTriggerToken

func GitLabPipelineTriggerToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GitLabPipelineTriggerToken confirms the GitLab pipeline trigger token shape: trigger "glptt-" followed by exactly 40 characters from [A-Za-z0-9_-]. GitLab's own client-side secret detection pattern uses this full alphabet at this length; gitleaks independently corroborates the 40-character length with a narrower hex-only alphabet, so the broader alphabet here is a safe superset chosen to avoid false negatives on a real token gitleaks' stricter pattern would miss.

https://docs.gitlab.com/security/tokens/#token-prefixes

func GitLabRunnerToken

func GitLabRunnerToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GitLabRunnerToken confirms the GitLab runner authentication token shape: trigger "glrt-" followed by 20-50 characters from [A-Za-z0-9_-].

https://docs.gitlab.com/ci/runners/new_creation_workflow/#glrt-authentication-tokens

func GrafanaCloudAccessPolicyToken

func GrafanaCloudAccessPolicyToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GrafanaCloudAccessPolicyToken confirms the Grafana Cloud access policy token shape: trigger "glc_" followed by 32-400 characters of standard base64 and up to two '=' padding characters.

https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/

func GrafanaServiceAccountToken

func GrafanaServiceAccountToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GrafanaServiceAccountToken confirms the Grafana service-account token shape: trigger "glsa_" followed by a fixed 32-character alphanumeric body, a literal '_', and a fixed 8-character hex checksum.

https://grafana.com/docs/grafana/latest/administration/service-accounts/

func GroqAPIKey

func GroqAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

GroqAPIKey confirms the Groq API key shape: trigger "gsk_" followed by exactly 52 [A-Za-z0-9] and a non-alphanumeric byte (or end of window).

func HuggingFaceToken

func HuggingFaceToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

HuggingFaceToken confirms the Hugging Face token shape: trigger "hf_" followed by exactly 34 [A-Za-z0-9] and a non-alphanumeric byte (or end of window). Identifiers like "hf_hub_download" are naturally excluded: the underscore after "hub" ends the alphanumeric run well short of 34.

func JWT

func JWT(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

JWT confirms a standalone JWS compact serialization. See parseJWT for the exact shape enforced.

func LinearAPIKey

func LinearAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

LinearAPIKey confirms a Linear personal API key: trigger "lin_api_" followed by 40-60 ASCII alphanumeric characters and a non-alphanumeric byte (or end of window).

func NotionInternalToken

func NotionInternalToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

NotionInternalToken confirms a Notion internal integration token, in either of its two documented forms:

  • trigger "secret_" preceded by a non-identifier byte (or start of window) — this trigger is a substring of the common identifier "client_secret_" (an OAuth client secret field name, not a Notion token), so a preceding letter/digit/underscore means this occurrence isn't a standalone token — followed by exactly 43 ASCII alphanumeric characters.
  • trigger "ntn_" followed by 46-60 ASCII alphanumeric characters.

Both forms require a non-identifier byte (or start of window) before the trigger, per the fixed-literal-prefix convention in docs/RULE_AUTHORING.md, and a non-alphanumeric byte (or end of window) after the body. Which shape is being validated is determined by trigEnd-trigStart (7 for "secret_", 4 for "ntn_"), since that's all that distinguishes the two triggers from inside a validator that only sees window-relative offsets.

func NpmAccessToken

func NpmAccessToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

NpmAccessToken confirms the npm access token shape: trigger "npm_" followed by exactly 36 ASCII alphanumeric characters.

The fixed length plus trailing-boundary check are what let this trigger safely coexist with npm's own identifier prefixes, e.g. "npm_config_registry" or "npm_config_yes": their bodies are not 36 bytes of pure alphanumerics (an underscore breaks the run well short of 36), so fixedAlnumToken rejects them.

https://docs.npmjs.com/about-access-tokens

func OpenAILegacyKey

func OpenAILegacyKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

OpenAILegacyKey confirms the classic OpenAI secret key shape: trigger "sk-" followed by exactly 20 [A-Za-z0-9], the literal infix "T3BlbkFJ", then exactly 20 more [A-Za-z0-9] (48 bytes total), with a non-alphanumeric byte (or end of window) on the right and a byte that is not [A-Za-z0-9_-] (or start of window) on the left.

Because "sk-" is a substring of "sk-ant-", "sk-proj-", "sk-svcacct-", and "sk-admin-", this validator explicitly refuses to confirm when the bytes immediately after the trigger begin with any of those other rules' distinguishing suffixes — those occurrences belong to anthropic-api-key or openai-project-key instead.

func OpenAIProjectKey

func OpenAIProjectKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

OpenAIProjectKey confirms the OpenAI project-scoped key shapes: trigger "sk-proj-", "sk-svcacct-", or "sk-admin-" followed by [A-Za-z0-9_-]. The body must be at least 40 bytes and at most 200. If it contains the "T3BlbkFJ" infix anywhere, it is confirmed regardless of length beyond that floor/ceiling; without the infix, it must additionally be at least 48 bytes and not look like a placeholder (all one character, or a long repeated run).

func PEMPrivateKey

func PEMPrivateKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

PEMPrivateKey confirms a PEM-encapsulated private-key block within window, treating the end of window as the end of input. It is PEMPrivateKeyBegin with final set; see that function and the file comment for the grammar and span semantics.

func PEMPrivateKeyBegin added in v0.3.0

func PEMPrivateKeyBegin(window []byte, trigStart, trigEnd int, final bool) (start, end int, state uint64, open, ok bool)

PEMPrivateKeyBegin confirms a PEM private-key block (rules.BlockBeginFunc). The trigger is "-----BEGIN " (trailing space included). The header label must contain "PRIVATE KEY" (so CERTIFICATE and PUBLIC KEY blocks are rejected), then the body is scanned until the matching "-----END ...PRIVATE KEY...-----" marker. When final is false and the body runs on past the window after a plausible run, the block is open and continues with PEMPrivateKeyExtend. A trigger opening a unified diff line (the rule's two-byte lookbehind shows the marker) makes the block a diff block, closed by the END lines of its own file versions.

func PEMPrivateKeyExtend added in v0.3.0

func PEMPrivateKeyExtend(window []byte, state uint64, final bool) (end int, next uint64, open bool)

PEMPrivateKeyExtend continues an open PEM block (rules.BlockExtendFunc) from the previous window's resume point, to the END marker, to the fail-safe end, or — when the body runs past this window too — to the next resume point.

func PuTTYPrivateKey

func PuTTYPrivateKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

PuTTYPrivateKey confirms the private section of a PuTTY PPK file within window, treating the end of window as the end of input. It is PuTTYPrivateKeyBegin with final set.

func PuTTYPrivateKeyBegin added in v0.3.0

func PuTTYPrivateKeyBegin(window []byte, trigStart, trigEnd int, final bool) (start, end int, state uint64, open, ok bool)

PuTTYPrivateKeyBegin confirms the private section of a PuTTY PPK file (rules.BlockBeginFunc). The trigger is "PuTTY-User-Key-File-"; the version digit must be 2 or 3 (followed by ':'). The validator then locates the "Private-Lines: N" header at a line start and consumes N base64 body lines. Span: first byte of "Private-Lines:" through the last base64 byte of the last body line (the public portion stays). If "Private-Lines:" never appears in the window the validator reports no match; once it has appeared, the fail-safe confirms a partial body with at least one plausible (>= 16-byte base64) line.

In a unified diff patch every line opens with '+', '-' or ' ': a header behind one such marker selects diff mode, in which every body line must open with a marker too and each file version is counted separately (see ppkScanner). The rule's two-byte lookbehind shows the trigger line's own marker, which selects the versions the key follows. A header indented by one space reads as a context line, whose body lines then need the same one-byte indent.

func PuTTYPrivateKeyExtend added in v0.3.0

func PuTTYPrivateKeyExtend(window []byte, state uint64, final bool) (end int, next uint64, open bool)

PuTTYPrivateKeyExtend continues an open PPK section (rules.BlockExtendFunc) from the previous window's resume point: a line start, the end of a counted line's base64, or inside a counted or stepped-over line.

func PyPIAPIToken

func PyPIAPIToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

PyPIAPIToken confirms the PyPI API token shape: trigger "pypi-" followed by 85-300 characters from [A-Za-z0-9_-] (a base64url-encoded macaroon). 300 is a generous practical ceiling chosen for this validator, not a documented maximum — PyPI does not publish an upper bound on token length.

https://docs.pypi.org/api/token/

func SendGridAPIKey

func SendGridAPIKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

SendGridAPIKey confirms a SendGrid API key: trigger "SG." preceded by a non-identifier byte (or start of window) — this trigger is a substring of ordinary words like "MSG." (a message-related identifier), so a preceding letter/digit/underscore means this "SG." occurrence is part of a longer identifier, not a key — followed by exactly 22 [A-Za-z0-9_-] characters, a literal '.', exactly 43 more [A-Za-z0-9_-] characters, and a byte outside that alphabet (or end of window).

func SlackAppToken

func SlackAppToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

SlackAppToken confirms a Slack app-level token: trigger "xapp-" followed by a run of 40-100 bytes drawn from [A-Za-z0-9-] containing at least 3 hyphens, and a non-alphanumeric byte (or end of window). Slack's documented shape is <digit>-<10-12 char alnum segment>-<10-13 digits>-<64 hex chars>, but this validator deliberately accepts the looser digit/dash/token-char envelope rather than pinning every segment length, so it isn't invalidated by minor token-format revisions.

func SlackBotToken

func SlackBotToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

SlackBotToken confirms the Slack bot token shape: trigger "xoxb-" preceded by a non-identifier byte (or start of window), followed by <10-13 digits>-<10-13 digits>-<24-34 alphanumeric characters> and a non-alphanumeric byte (or end of window). Every segment is checked against isPlaceholder, so fixture shapes like "xoxb-1234567890-..." are rejected along with all-identical segments.

func SlackUserToken

func SlackUserToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

SlackUserToken confirms a Slack user-scoped token: one of the triggers "xoxp-", "xoxa-", "xoxr-", "xoxs-" followed by <10-13 digits>-<10-13 digits>-<24-34 alphanumeric characters>. xoxp tokens historically carry an extra digit group before that final segment (<digits>-<digits>-<digits>-<alnum>); both the two-group and three-group shapes are accepted for all four prefixes, since the distinguishing factor is workspace/token vintage, not which of these four prefixes was used. The three-group shape is tried first (it is the more specific match); when it doesn't validate, the parser falls back to treating the segment right after the second dash as the final alphanumeric run directly.

func StripeEphemeralKey

func StripeEphemeralKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

StripeEphemeralKey confirms a Stripe ephemeral key: trigger "ek_live_" or "ek_test_" followed by 60-150 characters from the URL-safe-base64 alphabet ([A-Za-z0-9_-]) and a boundary. Unlike sk_/rk_ secret keys, an ephemeral key's body is not a flat random token: it's a base64url encoding of the account ID it's scoped to plus a random component, so it legitimately contains '-' and '_' and its length scales with the account ID rather than being fixed. Neither gitleaks nor TruffleHog has a rule for this key type; the shape here comes from real (but synthetic-safe, since only the encoding structure was inspected, never a real key's decoded contents) fixtures in Stripe's own stripe-ios and stripe-android SDK repositories.

func StripeSecretKey

func StripeSecretKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

StripeSecretKey confirms a Stripe secret/restricted API key: one of the triggers "sk_live_", "sk_test_", "rk_live_", "rk_test_" followed by 24-99 ASCII alphanumeric characters and a non-alphanumeric byte (or end of window). Test-mode keys (sk_test_/rk_test_) are still credentials — they authenticate against Stripe's test-mode API — so they are redacted identically to live-mode keys.

func StripeWebhookSecret

func StripeWebhookSecret(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

StripeWebhookSecret confirms a Stripe webhook signing secret: trigger "whsec_" followed by 32-64 ASCII alphanumeric characters and a non-alphanumeric byte (or end of window).

func SupabaseServiceRoleKey

func SupabaseServiceRoleKey(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

SupabaseServiceRoleKey confirms the same JWS compact-serialization shape as JWT (see parseJWT), additionally base64url-decoding the payload segment and requiring a `"role":"service_role"` claim within it. A payload that fails to decode as base64url — truncated by the window, or simply not base64 — is rejected rather than guessed at.

func TerraformCloudAPIToken

func TerraformCloudAPIToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

TerraformCloudAPIToken confirms the HCP Terraform/Terraform Enterprise API token shape: a 14-character alphanumeric prefix, the literal trigger ".atlasv1.", and a 60-70 character secret from [A-Za-z0-9_=-], bounded on the left by a byte that cannot extend the prefix backward (or the edge of window). A token with this shape grants read/modify access to Terraform Cloud-managed infrastructure state, which routinely contains further embedded secrets (provider credentials, database passwords) in its resource attributes and outputs.

https://developer.hashicorp.com/terraform/cloud-docs/api-docs/user-tokens https://developer.hashicorp.com/terraform/cloud-docs/api-docs/team-tokens https://developer.hashicorp.com/terraform/cloud-docs/api-docs/organization-tokens

func TwilioAPIKeySID

func TwilioAPIKeySID(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

TwilioAPIKeySID confirms a Twilio API key SID: trigger "SK" (matched case-sensitively — see the rule spec) preceded by a non-identifier byte (or start of window) — "SK" is a substring of ordinary words like "TASK" and "DESK", so a preceding letter/digit/underscore means this isn't a standalone SID — followed by exactly 32 ASCII lowercase hex characters and a non-alphanumeric byte (or end of window).

func URLCredentials

func URLCredentials(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

URLCredentials confirms a password inside a URL's userinfo component ("scheme://user:password@host/..."). The trigger is the literal "://".

The scheme before "://" must look like a scheme: a run of scheme bytes at least 2 long whose first byte is a letter — this rejects prose like `the "://" separator`. After "://", the userinfo is scanned for '@'; if a terminator byte appears first there is no userinfo and no match.

Only the PASSWORD is redacted: the bytes after the first ':' of the userinfo up to (excluding) the '@'. A username-only URL ("https://user@host") is deliberately not a match: a bare username is an identifier, not a secret, and redacting it would destroy far more benign URLs than it would protect (gitleaks flags whole user:pass URLs; this rule intentionally preserves everything but the password so the URL stays byte-for-byte usable around the marker). An empty password ("redis://:@host") is likewise no match. Percent-encoded bytes pass through unexamined — they are just bytes to this scan.

Placeholders and plain word-like values are rejected via entropy helpers plus the tiny urlPasswordPlaceholders whole-value list, so documentation and development-default shapes like "user:pass", "user:content", and "foo:xxx" never fire. Random-looking passwords remain detectable.

func VaultBatchToken

func VaultBatchToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

VaultBatchToken confirms the HashiCorp Vault batch token shape: trigger "hvb." followed by 100-300 characters from [A-Za-z0-9_-]. Batch tokens are the same blast-radius class as service tokens (whatever their policies allow) but encode more state directly into the token, which is why their body runs longer.

https://github.com/hashicorp/vault/blob/main/vault/token_store.go (TokenPrefixLength, TokenLength) https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml (production length range)

func VaultServiceToken

func VaultServiceToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

VaultServiceToken confirms the HashiCorp Vault service token shape: trigger "hvs." followed by 20-150 characters from [A-Za-z0-9_-]. A Vault service token grants whatever access its policies allow, up to full administrative control of everything Vault manages.

https://github.com/hashicorp/vault/blob/main/vault/token_store.go (TokenPrefixLength, TokenLength) https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml (production length range)

func VercelLegacyToken

func VercelLegacyToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

VercelLegacyToken confirms the shape of a value assigned to VERCEL_TOKEN, VERCEL_API_TOKEN, or VERCEL_ACCESS_TOKEN (matched case-insensitively — VERCEL_TOKEN is Vercel's own documented CLI/REST API auth variable, the other two are common conventions seen alongside it): 24 alphanumeric characters. Tokens minted before Vercel introduced its current prefixed format remain valid and have no distinguishing shape of their own, so this rule is keyed on the documented variable names rather than the token body alone.

https://vercel.com/docs/cli https://vercel.com/docs/rest-api

func VercelToken

func VercelToken(window []byte, trigStart, trigEnd int) (start, end int, ok bool)

VercelToken confirms the current (2026+) Vercel access token shape: one of five scoped triggers ("vcp_" personal, "vci_" integration, "vca_" app access, "vcr_" app refresh, "vck_" API key) followed by 40-80 alphanumeric characters.

https://vercel.com/changelog/new-token-formats-and-secret-scanning

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL