feat(redact): six new credential patterns — GitLab, HuggingFace, npm, DigitalOcean, Bearer, GCP SA (#1946)

Coverage gaps from the #1946 security review, including token types for
tooling gstack itself drives (glab):

HIGH (block): gitlab.token (glpat-/glptt-/gldt-), huggingface.token (hf_),
npm.token (npm_), digitalocean.token (dop_v1_), gcp.service_account (the
JSON-escaped "private_key" form that dodges pem.private_key's literal-block
match when minified, confirmed by "private_key_id" proximity).

MEDIUM (warn): auth.bearer — the most FP-prone shape in the set (docs are
full of "Authorization: Bearer <token>"), so it requires header-context
proximity and the same entropy>=3.0 + placeholder validator recipe as
env.kv. "Bearer YOUR_TOKEN_HERE" never fires; calibration over coverage,
per the cries-wolf principle.

All shapes are linear-time; test/redact-pattern-lint.test.ts covers them
automatically. Engine tests add positive + placeholder-negative cases per
pattern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-06-11 20:58:58 -07:00
co-authored by Claude Fable 5
parent 6bc00abb0f
commit 028cb52ca8
2 changed files with 101 additions and 0 deletions
+58
View File
@@ -222,6 +222,48 @@ export const PATTERNS: RedactPattern[] = [
description: "GitHub fine-grained PAT",
regex: /\b(github_pat_[A-Za-z0-9_]{82})\b/,
},
{
id: "gitlab.token",
tier: "HIGH",
category: "secret",
description: "GitLab token (personal/pipeline-trigger/deploy)",
// glpat- personal access, glptt- pipeline trigger, gldt- deploy token.
// gstack drives glab first-class — these were a coverage gap (#1946).
regex: /\b(gl(?:pat|ptt|dt)-[A-Za-z0-9_-]{20,})\b/,
},
{
id: "huggingface.token",
tier: "HIGH",
category: "secret",
description: "HuggingFace access token",
regex: /\b(hf_[A-Za-z0-9]{30,})\b/,
},
{
id: "npm.token",
tier: "HIGH",
category: "secret",
description: "npm granular access token",
regex: /\b(npm_[A-Za-z0-9]{36})\b/,
},
{
id: "digitalocean.token",
tier: "HIGH",
category: "secret",
description: "DigitalOcean personal access token",
regex: /\b(dop_v1_[a-f0-9]{64})\b/,
},
{
id: "gcp.service_account",
tier: "HIGH",
category: "secret",
description: "GCP service-account JSON private key",
// The JSON-escaped form ("private_key": "-----BEGIN PRIVATE KEY-----\n...)
// dodges pem.private_key's literal-block match when minified to one line.
// Proximity to "private_key_id" confirms the GCP service-account shape.
regex: /("private_key"\s*:\s*"-----BEGIN (?:RSA |EC )?PRIVATE KEY-----)/,
nearRegex: /"private_key_id"/,
nearWindow: 300,
},
{
id: "anthropic.key",
tier: "HIGH",
@@ -352,6 +394,22 @@ export const PATTERNS: RedactPattern[] = [
!/^\$\{?[A-Za-z_]/.test(span) &&
shannonEntropy(span) >= 3.0,
},
{
id: "auth.bearer",
tier: "MEDIUM",
category: "secret",
description: "Authorization Bearer token (high-entropy, header context)",
// FP-prone shape (docs and examples are full of "Bearer <token>"), so:
// MEDIUM tier, requires "authorization" nearby, and the same entropy
// recipe as env.kv to kill Bearer YOUR_TOKEN_HERE placeholders.
regex: /\bBearer[ \t]+([A-Za-z0-9._~+/=-]{20,})\b/,
nearRegex: /authorization/i,
nearWindow: 80,
validate: (span) =>
!isPlaceholderSpan(span) &&
!/^\$\{?[A-Za-z_]/.test(span) &&
shannonEntropy(span) >= 3.0,
},
// ===== MEDIUM — PII (auto-redactable subset) =====
{