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
41 changes: 41 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,47 @@ const RULES: Rule[] = [
re: /https:\/\/chat\.googleapis\.com\/v1\/spaces\/[A-Za-z0-9_-]+\/messages\?[^\s"']*key=/,
confidence: "high",
},
{
// Managed-database connection strings that embed credentials, like the `cloudinary_url` rule above:
// `scheme://<user>:<password>@<host>`. Each requires a NON-empty user:password pair (so an angle-bracket
// `<user>:<password>` docs placeholder does not match — `<`/`>` are excluded) AND a distinctive provider
// host, so an ordinary `postgres://user:pass@localhost` (no managed host) is never flagged. The host is
// followed by a negative lookahead so a look-alike suffix host (`…mongodb.net.evil.com`) can't match.
kind: "mongodb_atlas_uri",
re: /\bmongodb(?:\+srv)?:\/\/[^\s:/<>]+:[^\s@/<>]+@[a-z0-9.-]+\.mongodb\.net(?![a-z0-9.-])/i,
confidence: "high",
},
{
// Neon serverless-Postgres connection string (`…@<host>.neon.tech`).
kind: "neon_postgres_uri",
re: /\bpostgres(?:ql)?:\/\/[^\s:/<>]+:[^\s@/<>]+@[a-z0-9.-]+\.neon\.tech(?![a-z0-9.-])/i,
confidence: "high",
},
{
// Supabase Postgres connection string (`…@db.<ref>.supabase.co`).
kind: "supabase_postgres_uri",
re: /\bpostgres(?:ql)?:\/\/[^\s:/<>]+:[^\s@/<>]+@[a-z0-9.-]+\.supabase\.co(?![a-z0-9.-])/i,
confidence: "high",
},
{
// Upstash Redis connection string (`rediss://default:<password>@<host>.upstash.io`). Upstash uses the
// default user, so the user segment may be empty.
kind: "upstash_redis_uri",
re: /\brediss?:\/\/[^\s:/<>]*:[^\s@/<>]+@[a-z0-9.-]+\.upstash\.io(?![a-z0-9.-])/i,
confidence: "high",
},
{
// PlanetScale MySQL connection string (`…@<region>.connect.psdb.cloud`).
kind: "planetscale_mysql_uri",
re: /\bmysql:\/\/[^\s:/<>]+:[^\s@/<>]+@[a-z0-9.-]+\.psdb\.cloud(?![a-z0-9.-])/i,
confidence: "high",
},
{
// CockroachDB Cloud connection string (`…@<host>.cockroachlabs.cloud`).
kind: "cockroachdb_uri",
re: /\bpostgres(?:ql)?:\/\/[^\s:/<>]+:[^\s@/<>]+@[a-z0-9.-]+\.cockroachlabs\.cloud(?![a-z0-9.-])/i,
confidence: "high",
},
{
// Netlify build-hook URL — the trailing id triggers a production build, like the webhook rules above.
kind: "netlify_build_hook_url",
Expand Down
38 changes: 38 additions & 0 deletions review-enrichment/test/secret-scan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1615,3 +1615,41 @@ test("scanPatch does not flag near-misses of the deployment-hook URL formats", (
assert.equal(findings.length, 0, `near-miss should not match: ${nm}`);
}
});

test("scanPatch flags managed-database connection strings that embed credentials", () => {
// `<user>:<password>` fragments joined into a URI at test time — never a contiguous real secret in source.
const u = "dbuser";
const p = "s3cr3tP" + "w0rd1234";
const cases = [
["mongodb_atlas_uri", "mongodb+srv://" + u + ":" + p + "@cluster0.ab12c.mongodb.net/mydb"],
["neon_postgres_uri", "postgresql://" + u + ":" + p + "@ep-cool-name-123.us-east-2.aws.neon.tech/neondb"],
["supabase_postgres_uri", "postgres://" + u + ":" + p + "@db.abcdefghij.supabase.co:5432/postgres"],
["upstash_redis_uri", "rediss://default:" + p + "@apn1-cool-cat-12345.upstash.io:6379"],
["planetscale_mysql_uri", "mysql://" + u + ":" + p + "@aws.connect.psdb.cloud/mydb"],
["cockroachdb_uri", "postgresql://" + u + ":" + p + "@cool-cluster-123.abc.cockroachlabs.cloud:26257/defaultdb"],
];
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 connection-string placeholders or non-managed hosts", () => {
const p = "s3cr3tP" + "w0rd1234";
const nearMisses = [
// Angle-bracket docs placeholders — `<user>`/`<password>` are excluded by the rule's charset.
"mongodb+srv://<user>:<password>@cluster0.ab12c.mongodb.net/mydb",
"postgresql://<user>:<password>@ep-x.neon.tech/db",
// A plain local/self-hosted host is not a managed provider, so nothing is flagged.
"postgres://dbuser:" + p + "@localhost:5432/postgres",
"redis://default:" + p + "@127.0.0.1:6379",
// A look-alike suffix host must not match via the provider host prefix.
"mongodb+srv://dbuser:" + p + "@cluster0.ab12c.mongodb.net.evil.com/mydb",
];
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