feat(redact): block Google OAuth client secrets and Telegram bot tokens at HIGH

GOCSPX-prefixed client secrets and <bot_id>:<35-char> Telegram tokens are
never-publishable credential shapes with unambiguous formats — both now
block at HIGH like the other live-format credentials.

Contributed by @francis-eye (PR #2357).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-08-14 15:48:32 -07:00
parent d410142c2f
commit 69cc39c625
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
2 changed files with 37 additions and 0 deletions

View File

@ -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 (<bot-id>: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",

View File

@ -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(