156 lines
8.3 KiB
JSON
156 lines
8.3 KiB
JSON
{
|
|
"version": "0.3.0",
|
|
"id": "secret-egress",
|
|
"description": "Credential and token shapes that must never leave a machine: the fixed pattern table a pre-write guard matches against content before it is persisted. Detection data only - what to DO when one matches (block, warn, redact) is the consumer's policy and is not described here.",
|
|
"owasp": "LLM02",
|
|
"match_semantics": "first match wins; patterns are evaluated in ascending `order`",
|
|
"$comment": "Extracted without behaviour change from llm-security/hooks/scripts/pre-edit-secrets.mjs (`SECRET_PATTERNS`). NOTE THE SOURCE FILE: this is the engine-consumed hook table and NOT knowledge/secrets-patterns.md, which is a separate PCRE-flavoured agent-consumed variant that stays where it is. This repository's own extraction plan originally named the wrong one of the two; the file recorded here is the one that was actually delivered. Only the 19 fixed entries are data - entries 20 and beyond are policy-injected custom patterns at runtime and are not part of the base table. Version 0.3.0 re-extracted the table from a pinned public commit rather than from a transcription: positions 0-16 were confirmed byte-identical to what the 0.1.0 dump had already produced, and order 17 (`OpenAI Legacy API Key`) was read out of the module at that commit. The one-entry staleness disclosed in 0.2.0 is therefore closed by measurement, not by transcribing the regex out of the coord message that reported it.",
|
|
"provenance": {
|
|
"source_repo": "llm-security",
|
|
"source_files": [
|
|
"hooks/scripts/pre-edit-secrets.mjs"
|
|
],
|
|
"source_exports": [
|
|
"SECRET_PATTERNS"
|
|
],
|
|
"source_delivery": "0.1.0: operator dump 2/2, coord message from llm-security, 2026-08-09. 0.3.0: re-extracted from the module text at a pinned public commit, obtained with `git show <commit>:hooks/scripts/pre-edit-secrets.mjs` against the public remote.",
|
|
"source_commit": "47905dacae8bd5613c8ed76c088cb3de93f40091",
|
|
"source_commit_note": "`refs/heads/main` on the public remote when 0.3.0 was extracted. The commit that introduced order 17 is `088e458`, confirmed here to be an ancestor of the pinned head with `git merge-base --is-ancestor`, rather than accepted from the reporting message.",
|
|
"verified": "positionally against the module at the pinned commit: name, regex source, flags and order compared for all 19 entries, 0 divergences",
|
|
"evidence_limits": [
|
|
"No severity, and no per-entry disposition, was supplied. The source table carries a name and a pattern and nothing else, so neither is invented here.",
|
|
"The runtime-injected custom patterns (entries 20+) are policy, not data, and are out of scope. A consumer that matches only this table matches LESS than the seed hook does when a policy is loaded.",
|
|
"Fidelity is asserted against ONE commit. The pinned commit above is where this table was read; it says nothing about any later state of the source module. A consumer that needs to know whether it has drifted must re-measure against the remote, not re-read this field.",
|
|
"The comparison covers what the table declares - name, pattern source, flags, order. It does not cover the surrounding module: how the guard reads content, what it does on a match, or which paths it exempts are engine behaviour and are deliberately absent from this file."
|
|
]
|
|
},
|
|
"ordering": {
|
|
"normative": true,
|
|
"$comment": "Array order is part of the contract, not an artefact of serialisation. The source places 'JWT (three-part token)' last deliberately, so that a token inside an Authorization header is reported as 'Authorization header with token' rather than as a bare JWT. A consumer that reorders this table, or that reports all matches instead of the first, will label the same input differently from the seed runtime even though both detected it. The explicit `order` field on every entry exists so that reordering cannot happen silently through a JSON round-trip.",
|
|
"last_entry_is_load_bearing": "JWT (three-part token)"
|
|
},
|
|
"dialect": {
|
|
"name": "ecmascript",
|
|
"$comment": "Patterns are ECMAScript regular-expression source text exactly as the source literals spell it. Flags are declared per pattern; an entry with no `flags` key carries no flags. All 19 compile in Node with their declared flags, in Node with `u` added, and in Python `re` with the equivalent re.I.",
|
|
"flags": {
|
|
"i": "case-insensitive"
|
|
},
|
|
"features_used": [
|
|
"non-capturing groups: (?:...)",
|
|
"bounded quantifiers: {n,m}",
|
|
"word boundaries: \\b",
|
|
"character classes"
|
|
],
|
|
"translation_notes": [
|
|
"Python (`re`): compile with re.I where flags contain `i`. No rewriting needed; verified by compiling all 19.",
|
|
"Two patterns contain `\\/` - the redundant escape a JavaScript regex LITERAL requires and that `RegExp.prototype.source` preserves ('Slack/Discord Webhook URL' and 'Database connection string'). Kept byte-identical because Node bare, Node under `u` and Python `re` all accept it. Engines that reject unknown escapes (Go `regexp`, RE2) MUST report these two as unsupported rather than skip them silently.",
|
|
"The 'Generic credential assignment' and 'Authorization header with token' entries are shape matches, not proofs of a live credential. A consumer treating every match as a confirmed leak will produce false positives; that trade-off belongs to the consumer's policy, not to this table."
|
|
]
|
|
},
|
|
"normalisations": [],
|
|
"normalisations_note": "Empty by result, not by omission: all 19 patterns are byte-identical to the source module at the pinned commit, compared positionally. No escaping change was needed.",
|
|
"patterns": [
|
|
{
|
|
"order": 0,
|
|
"name": "AWS Access Key ID",
|
|
"pattern": "AKIA[0-9A-Z]{16}"
|
|
},
|
|
{
|
|
"order": 1,
|
|
"name": "AWS Secret Access Key",
|
|
"pattern": "(?:aws_secret(?:_access)?_key|AWS_SECRET(?:_ACCESS)?_KEY)\\s*[=:]\\s*['\"]?[0-9a-zA-Z/+=]{40}['\"]?",
|
|
"flags": "i"
|
|
},
|
|
{
|
|
"order": 2,
|
|
"name": "Azure Connection String (AccountKey/SharedAccessKey/sig)",
|
|
"pattern": "(?:AccountKey|SharedAccessKey|sig)=[A-Za-z0-9+/=]{20,}"
|
|
},
|
|
{
|
|
"order": 3,
|
|
"name": "Azure AD ClientSecret",
|
|
"pattern": "(?:client[_-]?secret|ClientSecret)\\s*[=:]\\s*['\"][^'\"]{8,}['\"]",
|
|
"flags": "i"
|
|
},
|
|
{
|
|
"order": 4,
|
|
"name": "Azure AI Services Key",
|
|
"pattern": "Ocp-Apim-Subscription-Key\\s*[=:]\\s*['\"]?[0-9a-f]{32}['\"]?",
|
|
"flags": "i"
|
|
},
|
|
{
|
|
"order": 5,
|
|
"name": "GitHub Token",
|
|
"pattern": "(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}"
|
|
},
|
|
{
|
|
"order": 6,
|
|
"name": "npm Token",
|
|
"pattern": "npm_[A-Za-z0-9]{36}"
|
|
},
|
|
{
|
|
"order": 7,
|
|
"name": "Anthropic API Key",
|
|
"pattern": "\\bsk-ant-api03-[A-Za-z0-9_-]{93}\\b"
|
|
},
|
|
{
|
|
"order": 8,
|
|
"name": "OpenAI Project Key",
|
|
"pattern": "\\bsk-proj-[A-Za-z0-9_-]{40,}\\b"
|
|
},
|
|
{
|
|
"order": 9,
|
|
"name": "GitHub Fine-Grained PAT",
|
|
"pattern": "\\bgithub_pat_[A-Za-z0-9_]{82}\\b"
|
|
},
|
|
{
|
|
"order": 10,
|
|
"name": "Google API Key",
|
|
"pattern": "\\bAIza[0-9A-Za-z_-]{35}\\b"
|
|
},
|
|
{
|
|
"order": 11,
|
|
"name": "Private Key PEM Block",
|
|
"pattern": "-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----"
|
|
},
|
|
{
|
|
"order": 12,
|
|
"name": "JWT Secret",
|
|
"pattern": "JWT[_-]?SECRET\\s*[=:]\\s*['\"][^'\"]{8,}['\"]",
|
|
"flags": "i"
|
|
},
|
|
{
|
|
"order": 13,
|
|
"name": "Slack/Discord Webhook URL",
|
|
"pattern": "https:\\/\\/(?:hooks\\.slack\\.com\\/services|discord(?:app)?\\.com\\/api\\/webhooks)\\/"
|
|
},
|
|
{
|
|
"order": 14,
|
|
"name": "Generic credential assignment",
|
|
"pattern": "(?:password|passwd|secret|token|api[_-]?key)\\s*[=:]\\s*['\"][^'\"]{8,}['\"]",
|
|
"flags": "i"
|
|
},
|
|
{
|
|
"order": 15,
|
|
"name": "Authorization header with token",
|
|
"pattern": "[Bb]earer [A-Za-z0-9\\-._~+/]{20,}"
|
|
},
|
|
{
|
|
"order": 16,
|
|
"name": "Database connection string",
|
|
"pattern": "(?:postgres|mysql|mongodb|redis):\\/\\/[^\\s]+@[^\\s]+",
|
|
"flags": "i"
|
|
},
|
|
{
|
|
"order": 17,
|
|
"name": "OpenAI Legacy API Key",
|
|
"pattern": "\\bsk-[A-Za-z0-9]{20}T3BlbkFJ[A-Za-z0-9]{20}\\b"
|
|
},
|
|
{
|
|
"order": 18,
|
|
"name": "JWT (three-part token)",
|
|
"pattern": "\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\b"
|
|
}
|
|
],
|
|
"count": 19
|
|
}
|