diff --git a/lib/redact-patterns.ts b/lib/redact-patterns.ts index 5c68e2b7a..ba85c0de3 100644 --- a/lib/redact-patterns.ts +++ b/lib/redact-patterns.ts @@ -374,6 +374,25 @@ export const PATTERNS: RedactPattern[] = [ nearRegex: /\bAC[a-f0-9]{32}\b/, nearWindow: 200, }, + { + id: "google.oauth_client_secret", + tier: "HIGH", + category: "secret", + // Distinct from google.api_key (MEDIUM): an AIza key is often a public + // client key, but a GOCSPX- client secret is never publishable — leaking + // it lets anyone impersonate the OAuth app's token exchange. + description: "Google OAuth client secret (GOCSPX-…)", + regex: /\b(GOCSPX-[A-Za-z0-9_-]{20,40})(?![A-Za-z0-9_-])/, + validate: (span) => !isPlaceholderSpan(span), + }, + { + id: "telegram.bot_token", + tier: "HIGH", + category: "secret", + description: "Telegram bot token (:AA…)", + regex: /\b([0-9]{6,16}:A[A-Za-z0-9_-]{34})(?![A-Za-z0-9_-])/, + validate: (span) => !isPlaceholderSpan(span), + }, { id: "pem.private_key", tier: "HIGH", diff --git a/test/redact-engine.test.ts b/test/redact-engine.test.ts index eebf584ce..d86693e19 100644 --- a/test/redact-engine.test.ts +++ b/test/redact-engine.test.ts @@ -54,6 +54,8 @@ describe("HIGH credential patterns", () => { "gcp.service_account", '{"private_key_id": "abc123", "private_key": "-----BEGIN PRIVATE KEY-----\\nMIIE..."}', ], + ["google.oauth_client_secret", 'client_secret: "GOCSPX-' + "Ab3xQ9zLmNp2RtVw7YkD1sHf" + '"'], + ["telegram.bot_token", "TELEGRAM_TOKEN=8326208591:AA" + "HdqRy9Lm2ZpXvKb4NcQw8TuEr6YoP1sVg"], ]; for (const [id, text] of cases) { test(`flags ${id}`, () => { @@ -167,6 +169,22 @@ describe("#1946 pattern negatives (placeholders never fire)", () => { }); }); +describe("google.oauth_client_secret / telegram.bot_token negatives", () => { + test("undersized and placeholder shapes never fire", () => { + // Length floor keeps short repo fixtures quiet (e.g. the 19-char body in + // openclaw's extensions/google/oauth.test.ts). + expect(ids("GOCSPX-FakeSecretValue123")).not.toContain("google.oauth_client_secret"); + expect(ids("GOCSPX-short")).not.toContain("google.oauth_client_secret"); + // Placeholder suppression on an otherwise correctly-sized body. + expect(ids("GOCSPX-example" + "a".repeat(17))).not.toContain("google.oauth_client_secret"); + expect(ids("1234567890:AAexample" + "a".repeat(26))).not.toContain("telegram.bot_token"); + // A plain number pair must not read as a bot token. + expect(ids("1234567890:1234567890")).not.toContain("telegram.bot_token"); + // The AIza key stays MEDIUM (google.api_key); it is not promoted here. + expect(ids("AIza" + "a".repeat(35))).not.toContain("google.oauth_client_secret"); + }); +}); + describe("PII patterns", () => { test("email flags + is autoRedactable", () => { const f = scan("ping alice@corp.io please", { repoVisibility: "private" }).findings.find(