Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions review-enrichment/src/analyzers/secret-scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,50 @@ const RULES: Rule[] = [
re: /https:\/\/chat\.googleapis\.com\/v1\/spaces\/[A-Za-z0-9_-]+\/messages\?[^\s"']*key=/,
confidence: "high",
},
{
// Netlify build-hook URL — the trailing id triggers a production build, like the webhook rules above.
kind: "netlify_build_hook_url",
re: /https:\/\/api\.netlify\.com\/build_hooks\/[0-9a-f]{24}/,
confidence: "high",
},
{
// Vercel deploy-hook URL — `.../deploy/prj_<id>/<token>`; the trailing token triggers a deployment.
kind: "vercel_deploy_hook_url",
re: /https:\/\/api\.vercel\.com\/v1\/integrations\/deploy\/prj_[A-Za-z0-9]+\/[A-Za-z0-9]+/,
confidence: "high",
},
{
// Render deploy-hook URL — `.../deploy/srv-<id>?key=<key>`; the `key` query param triggers a deployment.
kind: "render_deploy_hook_url",
re: /https:\/\/api\.render\.com\/deploy\/srv-[A-Za-z0-9]+\?key=[A-Za-z0-9_-]+/,
confidence: "high",
},
{
// Healthchecks.io ping URL — the UUID is the check's secret address; a leak lets anyone suppress its alerts.
kind: "healthchecks_ping_url",
re: /https:\/\/hc-ping\.com\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/,
confidence: "high",
},
{
// Pipedream workflow trigger — the subdomain of `.m.pipedream.net` is the endpoint's secret address. The
// negative lookahead stops the host matching a look-alike suffix domain (`.m.pipedream.net.evil.com`).
kind: "pipedream_webhook_url",
re: /https:\/\/[a-z0-9]+\.m\.pipedream\.net(?![a-z0-9.])/,
confidence: "high",
},
{
// Azure Logic App callback URL — the `sig=` query param is a SAS that authorizes triggering the workflow.
kind: "azure_logic_app_url",
re: /https:\/\/[a-z0-9.-]+\.logic\.azure\.com(?::443)?\/workflows\/[^\s"']*sig=/,
confidence: "high",
},
{
// Google Apps Script web-app URL — the `/macros/s/<deployment-id>/exec` id is a capability address for the
// deployed script (anyone with the URL can invoke it).
kind: "google_apps_script_url",
re: /https:\/\/script\.google\.com\/macros\/s\/[A-Za-z0-9_-]{30,}\/exec/,
confidence: "high",
},
{
kind: "private_key",
re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/,
Expand Down
39 changes: 39 additions & 0 deletions review-enrichment/test/secret-scan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1573,3 +1573,42 @@ test("scanPatch does not flag near-misses of the webhook-URL and token formats",
assert.equal(findings.length, 0, `near-miss should not match: ${nm}`);
}
});

test("scanPatch flags deployment-trigger and webhook URLs that embed a secret", () => {
const uuid = [hex(8), hex(4), hex(4), hex(4), hex(12)].join("-");
const cases = [
["netlify_build_hook_url", "https://api.netlify.com/build_hooks/" + hex(24)],
["vercel_deploy_hook_url", "https://api.vercel.com/v1/integrations/deploy/prj_" + b62(12) + "/" + b62(24)],
["render_deploy_hook_url", "https://api.render.com/deploy/srv-" + b62(20) + "?key=" + b62(20)],
["healthchecks_ping_url", "https://hc-ping.com/" + uuid],
["pipedream_webhook_url", "https://" + b62(12).toLowerCase() + ".m.pipedream.net"],
[
"azure_logic_app_url",
"https://prod-01.westus.logic.azure.com/workflows/" + hex(32) + "/triggers/manual/paths/invoke?sig=" + b62(24),
],
["google_apps_script_url", "https://script.google.com/macros/s/" + b62(40) + "/exec"],
];
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-misses of the deployment-hook URL formats", () => {
const nearMisses = [
// Vendor API/docs/dashboard URLs that carry no secret path segment — nothing to leak.
"https://api.netlify.com/api/v1/sites",
"https://vercel.com/docs/deploy-hooks",
"https://render.com/docs/deploy-hooks",
// A Render deploy URL WITHOUT the `key` query param is not the triggerable secret URL.
"https://api.render.com/deploy/srv-" + b62(20),
// A look-alike host is not the real Pipedream endpoint host.
"https://example.m.pipedream.net.evil.com/path",
];
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}`);
}
});
Loading