diff --git a/review-enrichment/src/analyzers/secret-scan.ts b/review-enrichment/src/analyzers/secret-scan.ts index 7bc37a501a..d277323b5c 100644 --- a/review-enrichment/src/analyzers/secret-scan.ts +++ b/review-enrichment/src/analyzers/secret-scan.ts @@ -391,6 +391,18 @@ const RULES: Rule[] = [ re: /\bsgp_[a-fA-F0-9]{16}_[a-fA-F0-9]{40}(?![A-Za-z0-9_-])/, confidence: "high", }, + { + // Statsig server secret key: `secret-` + base62 body (SDK validates this prefix). + kind: "statsig_server_secret_key", + re: /\bsecret-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/, + confidence: "high", + }, + { + // Paddle API key: `pdl_(live|sdbx)_apikey_` + fixed 26/22/3 base62 segments. + kind: "paddle_api_key", + re: /\bpdl_(?:live|sdbx)_apikey_[a-z0-9]{26}_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{3}(?![A-Za-z0-9_-])/, + confidence: "high", + }, { // Google OAuth 2.0 client secret: `GOCSPX-` + 28 base64url chars. kind: "google_oauth_client_secret", diff --git a/review-enrichment/test/secret-scan.test.ts b/review-enrichment/test/secret-scan.test.ts index 1a539bc74a..5b260a8f1b 100644 --- a/review-enrichment/test/secret-scan.test.ts +++ b/review-enrichment/test/secret-scan.test.ts @@ -1140,6 +1140,53 @@ test("scanPatch does not flag truncated Knock/Sourcegraph tokens or identifier c ); }); +test("scanPatch flags Statsig server secret and Paddle API keys with high confidence", () => { + const fakeStatsigKey = "secret-" + "a".repeat(20); + const statsigFindings = scanPatch("src/config.ts", hunk([`const statsig = "${fakeStatsigKey}";`])); + assert.equal(statsigFindings.length, 1); + assert.equal(statsigFindings[0].kind, "statsig_server_secret_key"); + assert.equal(statsigFindings[0].confidence, "high"); + + const fakePaddleKey = ["pdl_", "live", "_apikey_", "a".repeat(26), "_", "b".repeat(22), "_", "c".repeat(3)].join(""); + const paddleFindings = scanPatch("src/config.ts", hunk([`const paddle = "${fakePaddleKey}";`])); + assert.equal(paddleFindings.length, 1); + assert.equal(paddleFindings[0].kind, "paddle_api_key"); + assert.equal(paddleFindings[0].confidence, "high"); +}); + +test("scanPatch does not flag truncated Statsig/Paddle keys or identifier continuation", () => { + assert.equal(scanPatch("src/config.ts", hunk([`const statsig = "secret-${"a".repeat(19)}";`])).length, 0); + assert.equal( + scanPatch("src/config.ts", hunk([`const statsig = "secret-${"a".repeat(20)}_suffix";`])).some((f) => f.kind === "statsig_server_secret_key"), + false, + ); + assert.equal( + scanPatch("src/config.ts", hunk([`const statsig = "secret-${"a".repeat(20)}-suffix";`])).some((f) => f.kind === "statsig_server_secret_key"), + false, + ); + + const shortPaddleKey = ["pdl_", "live", "_apikey_", "a".repeat(25), "_", "b".repeat(22), "_", "c".repeat(3)].join(""); + assert.equal( + scanPatch("src/config.ts", hunk([`const paddle = "${shortPaddleKey}";`])).some((f) => f.kind === "paddle_api_key"), + false, + ); + const paddleSuffixKey = ["pdl_", "live", "_apikey_", "a".repeat(26), "_", "b".repeat(22), "_", "c".repeat(3), "-suffix"].join(""); + assert.equal( + scanPatch("src/config.ts", hunk([`const paddle = "${paddleSuffixKey}";`])).some((f) => f.kind === "paddle_api_key"), + false, + ); + const paddleUnderscoreKey = ["pdl_", "live", "_apikey_", "a".repeat(26), "_", "b".repeat(22), "_", "c".repeat(3), "_suffix"].join(""); + assert.equal( + scanPatch("src/config.ts", hunk([`const paddle = "${paddleUnderscoreKey}";`])).some((f) => f.kind === "paddle_api_key"), + false, + ); + const paddleAlphaSuffixKey = ["pdl_", "live", "_apikey_", "a".repeat(26), "_", "b".repeat(22), "_", "c".repeat(3), "z"].join(""); + assert.equal( + scanPatch("src/config.ts", hunk([`const paddle = "${paddleAlphaSuffixKey}";`])).some((f) => f.kind === "paddle_api_key"), + false, + ); +}); + test("scanPatch flags additional high-confidence SaaS/cloud/CI credential formats", () => { const cases = [ ["google_oauth_client_secret", "GOCSPX-" + b62(28)],