From 8b3173b08d8c81c051f1b564a7d8bb29be786ecf Mon Sep 17 00:00:00 2001 From: luciferlive112116 <291889058+luciferlive112116@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:51:17 +0800 Subject: [PATCH] feat(enrichment): detect AI-provider and more SaaS/CI credential formats in secret-scan --- .../src/analyzers/secret-scan.ts | 85 +++++++++++++++++++ review-enrichment/test/secret-scan.test.ts | 45 ++++++++++ 2 files changed, 130 insertions(+) diff --git a/review-enrichment/src/analyzers/secret-scan.ts b/review-enrichment/src/analyzers/secret-scan.ts index 13c1cbf85b..46c809a01a 100644 --- a/review-enrichment/src/analyzers/secret-scan.ts +++ b/review-enrichment/src/analyzers/secret-scan.ts @@ -271,6 +271,91 @@ const RULES: Rule[] = [ re: /\bfo1_[A-Za-z0-9_-]{43}\b/, confidence: "high", }, + { + // Dropbox short-lived access token: `sl.` + 130-152 base64url chars. A negative-lookahead terminator + // (not `\b`) so a body ending in `-` still matches, like the SendGrid/Anthropic rules above. + kind: "dropbox_token", + re: /\bsl\.[A-Za-z0-9_-]{130,152}(?![A-Za-z0-9_-])/, + confidence: "high", + }, + { + // JFrog Artifactory API key: `AKCp8` + >=69 base62 (lookahead terminator — the body has no fixed end). + kind: "jfrog_api_key", + re: /\bAKCp8[A-Za-z0-9]{69,}(?![A-Za-z0-9])/, + confidence: "high", + }, + { + // Duffel API token: `duffel_test_`/`duffel_live_` + 43 base64url (lookahead terminator — the body may end in `-`). + kind: "duffel_token", + re: /\bduffel_(?:test|live)_[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])/, + confidence: "high", + }, + { + // EasyPost API key: `EZAK` (production) / `EZTK` (test) + 54 base62. + kind: "easypost_key", + re: /\bEZ[AT]K[A-Za-z0-9]{54}\b/, + confidence: "high", + }, + { + // Frame.io developer token: `fio-u-` + 64 base64url (lookahead terminator — the body may end in `-`). + kind: "frameio_token", + re: /\bfio-u-[A-Za-z0-9_-]{64}(?![A-Za-z0-9_-])/, + confidence: "high", + }, + { + // Contentful personal access token: `CFPAT-` + 43 base64url (lookahead terminator — the body may end in `-`). + kind: "contentful_token", + re: /\bCFPAT-[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])/, + confidence: "high", + }, + { + // SonarQube token: `sqa_`/`sqp_`/`squ_` (analysis/project/user) + 40 hex. + kind: "sonarqube_token", + re: /\bsq[apu]_[a-f0-9]{40}\b/, + confidence: "high", + }, + { + // Pulumi access token: `pul-` + 40 hex. + kind: "pulumi_token", + re: /\bpul-[a-f0-9]{40}\b/, + confidence: "high", + }, + { + // Adafruit IO key: `aio_` + 28 base62. + kind: "adafruit_io_key", + re: /\baio_[A-Za-z0-9]{28}\b/, + confidence: "high", + }, + { + // ReadMe API key: `rdme_` + >=70 lowercase-hex-ish body (lookahead terminator). + kind: "readme_api_key", + re: /\brdme_[a-z0-9]{70,}(?![a-z0-9])/, + confidence: "high", + }, + { + // Typeform personal access token: `tfp_` + >=40 base62/._- (lookahead terminator). + kind: "typeform_token", + re: /\btfp_[A-Za-z0-9._-]{40,}(?![A-Za-z0-9._-])/, + confidence: "high", + }, + { + // Sentry DSN: an ingest URL embedding a 32-hex public key against a *.sentry.io host + project id. + kind: "sentry_dsn", + re: /\bhttps:\/\/[a-f0-9]{32}@[a-z0-9.-]*sentry\.io\/[0-9]+\b/, + confidence: "high", + }, + { + // Groq API key: `gsk_` + 52 base62. + kind: "groq_api_key", + re: /\bgsk_[A-Za-z0-9]{52}\b/, + confidence: "high", + }, + { + // Perplexity API key: `pplx-` + >=40 base62 (lookahead terminator). + kind: "perplexity_api_key", + re: /\bpplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/, + confidence: "high", + }, { kind: "private_key", re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/, diff --git a/review-enrichment/test/secret-scan.test.ts b/review-enrichment/test/secret-scan.test.ts index d02d965810..acf3081e4c 100644 --- a/review-enrichment/test/secret-scan.test.ts +++ b/review-enrichment/test/secret-scan.test.ts @@ -442,3 +442,48 @@ test("scanPatch does not flag near-miss variants of the new SaaS/cloud credentia assert.equal(findings.length, 0, `near-miss should not match: ${nm}`); } }); + +test("scanPatch flags additional high-confidence AI-provider and SaaS/CI credential formats", () => { + const cases = [ + // The base64url-body formats deliberately END IN `-` to prove the rule terminates on a + // negative-lookahead, not `\b` (a `\b` terminator silently misses a real token ending in `-`). + ["dropbox_token", "sl." + b62(139) + "-"], + ["jfrog_api_key", "AKCp8" + b62(70)], + ["duffel_token", "duffel_test_" + b62(42) + "-"], + ["easypost_key", "EZAK" + b62(54)], + ["frameio_token", "fio-u-" + b62(63) + "-"], + ["contentful_token", "CFPAT-" + b62(42) + "-"], + ["sonarqube_token", "sqp_" + hex(40)], + ["pulumi_token", "pul-" + hex(40)], + ["adafruit_io_key", "aio_" + b62(28)], + ["readme_api_key", "rdme_" + hex(70)], + ["typeform_token", "tfp_" + b62(40)], + ["sentry_dsn", "https://" + hex(32) + "@o0.ingest.sentry.io/12345"], + ["groq_api_key", "gsk_" + b62(52)], + ["perplexity_api_key", "pplx-" + b62(40)], + ]; + for (const [kind, secret] of cases) { + const findings = scanPatch("src/config.ts", hunk([`const c = "${secret}";`])); + assert.equal(findings.length, 1, `${kind}: expected exactly one finding, got ${JSON.stringify(findings)}`); + assert.equal(findings[0].kind, kind, `${kind}: wrong kind`); + assert.equal(findings[0].confidence, "high", `${kind}: wrong confidence`); + } +}); + +test("scanPatch does not flag near-miss variants of the new AI/SaaS credential formats", () => { + // One char short of the fixed/minimum length must produce no finding. + const nearMisses = [ + "AKCp8" + b62(68), + "duffel_test_" + b62(42), + "EZAK" + b62(53), + "CFPAT-" + b62(42), + "pul-" + hex(39), + "aio_" + b62(27), + "gsk_" + b62(51), + "sqp_" + hex(39), + ]; + for (const nm of nearMisses) { + const findings = scanPatch("src/config.ts", hunk([`const c = "${nm}";`])); + assert.equal(findings.length, 0, `near-miss should not match: ${nm}`); + } +});