From b77c1923c81ec7dd6d3b0dc66f5b47b3f47c6744 Mon Sep 17 00:00:00 2001 From: schienbiz <274676847+schienbiz@users.noreply.github.com> Date: Sat, 29 Aug 2026 23:37:24 +0800 Subject: [PATCH] feat(redact): add Groq, Tavily and Notion API key patterns --- lib/redact-patterns.ts | 27 +++++++++++++++++++++++++++ test/redact-engine.test.ts | 10 ++++++++++ 2 files changed, 37 insertions(+) diff --git a/lib/redact-patterns.ts b/lib/redact-patterns.ts index 29bd14921..322fc696b 100644 --- a/lib/redact-patterns.ts +++ b/lib/redact-patterns.ts @@ -438,6 +438,33 @@ export const PATTERNS: RedactPattern[] = [ // gstack drives glab first-class — these were a coverage gap (#1946). regex: /\b(gl(?:pat|ptt|dt)-[A-Za-z0-9_-]{20,})\b/, }, + { + id: "groq.key", + tier: "HIGH", + category: "secret", + description: "Groq API key", + regex: /\b(gsk_[A-Za-z0-9]{20,})\b/, + }, + { + id: "tavily.key", + tier: "HIGH", + category: "secret", + description: "Tavily API key (incl. tvly-dev-/tvly-prod-)", + // Explicit environment infixes rather than a globally-optional segment, + // which would also match separator-less tvly-devabc… (same reasoning as + // openai.key above). + regex: /\b(tvly-(?:dev-|prod-)?[A-Za-z0-9]{16,})\b/, + }, + { + id: "notion.token", + tier: "HIGH", + category: "secret", + description: "Notion integration token (ntn_ current, secret_ legacy)", + // Two explicit shapes. The legacy `secret_` form keeps a high {40,} floor + // because the prefix is an ordinary English word — the length is what makes + // it a credential rather than prose. + regex: /\b(ntn_[A-Za-z0-9]{40,}|secret_[A-Za-z0-9]{40,})\b/, + }, { id: "huggingface.token", tier: "HIGH", diff --git a/test/redact-engine.test.ts b/test/redact-engine.test.ts index fe6a1f46a..8b86c471f 100644 --- a/test/redact-engine.test.ts +++ b/test/redact-engine.test.ts @@ -48,6 +48,11 @@ describe("HIGH credential patterns", () => { ["gitlab.token", "remote: glpat-" + "Ab12Cd34Ef56Gh78Ij90"], ["gitlab.token", "trigger glptt-" + "a1b2c3d4e5f6a7b8c9d0e1f2"], ["gitlab.token", "deploy gldt-" + "Zy98Xw76Vu54Ts32Rq10"], + ["groq.key", "gsk_" + "AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdEfGhIjKlMn"], + ["tavily.key", "tvly-" + "AbCdEfGhIjKlMnOpQrStUvWx"], + ["tavily.key", "tvly-dev-" + "AbCdEfGhIjKlMnOpQrStUvWx"], + ["notion.token", "ntn_" + "AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdEfGh"], + ["notion.token", "secret_" + "AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdEfGh"], ["huggingface.token", "hf_" + "AbCdEfGhIjKlMnOpQrStUvWxYz012345"], ["npm.token", "npm_" + "a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8"], ["digitalocean.token", "dop_v1_" + "0123456789abcdef".repeat(4)], @@ -261,6 +266,11 @@ describe("#1946 pattern negatives (placeholders never fire)", () => { test("short or placeholder shapes don't trip the new HIGH patterns", () => { expect(ids("glpat-xxxx")).not.toContain("gitlab.token"); expect(ids("hf_token")).not.toContain("huggingface.token"); + expect(ids("gsk_key")).not.toContain("groq.key"); + expect(ids("tvly-key")).not.toContain("tavily.key"); + expect(ids("ntn_token")).not.toContain("notion.token"); + // `secret_` is an ordinary word; only the length makes it a credential. + expect(ids("secret_value")).not.toContain("notion.token"); expect(ids("npm_install")).not.toContain("npm.token"); expect(ids("dop_v1_short")).not.toContain("digitalocean.token"); // pem header WITHOUT the GCP JSON shape stays pem.private_key only.