Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/chilly-windows-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'dotenv-diff': patch
---

fix secret detector false warnings
17 changes: 17 additions & 0 deletions packages/cli/src/core/security/secretDetectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,22 @@ function characterClassCount(v: string): number {
return count;
}

/**
* Checks if a value is a delimited identifier — lowercase words/numbers joined by
* `-`, `_`, or `.` (e.g. `secret-rotation-v2-rotate-secrets`, `inject-k8s-sa-auth-token`).
*
* These kebab/snake/dot slugs and enum constants are not credentials, but a version
* suffix like `v2` or a token like `k8s` sneaks a digit in, pushing them to two
* character classes (lowercase + digit) — which would otherwise trip the class-count
* check in {@link looksLikeSecretValue}. Rejecting the whole shape up front keeps that
* heuristic honest to its documented promise of ignoring identifier slugs.
* @param v - The value to check.
* @returns True when the value is a lowercase delimited identifier.
*/
function isDelimitedIdentifier(v: string): boolean {
return /^[a-z0-9]+([._-][a-z0-9]+)+$/.test(v);
}

/**
* Decides whether a value assigned to a suspiciously-named key actually looks like a
* credential rather than a kebab/snake-case identifier or enum slug.
Expand All @@ -306,6 +322,7 @@ function characterClassCount(v: string): number {
*/
function looksLikeSecretValue(v: string): boolean {
if (SECRET_VALUE_PREFIXES.test(v)) return true;
if (isDelimitedIdentifier(v)) return false;
if (characterClassCount(v) >= 2) return true;
return (
v.length >= SINGLE_CLASS_MIN_LENGTH &&
Expand Down
26 changes: 26 additions & 0 deletions packages/cli/test/unit/core/security/secretDetectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,32 @@ const email = "user@example.com";
expect(hasSuspiciousKeyFinding(findings)).toBe(true);
});

// Regression: a version digit (v1/v2) or token like `k8s` inside an
// otherwise lowercase kebab slug used to push it to two character classes
// (lowercase + digit) and trip the class-count check. Delimited lowercase
// identifiers must be rejected regardless of embedded digits.
it.each([
['secret-rotation-v2-rotate-secrets', 'src/queue-service.ts'],
['secret-scanning-v2-full-scan', 'src/queue-service.ts'],
['inject-k8s-sa-auth-token', 'src/gateway/types.ts'],
['infisical-secret-value-blind-index-v1', 'src/blind-index.ts'],
])(
'does not flag a lowercase delimited slug with an embedded digit (%s)',
(value, file) => {
const source = `const SECRET_KIND = "${value}";`;
const findings = detectSecretsInSource(file, source);

expect(hasSuspiciousKeyFinding(findings)).toBe(false);
},
);

it('does not flag a snake_case slug with an embedded digit', () => {
const source = 'const TOKEN_KIND = "auth_token_rotation_v2";';
const findings = detectSecretsInSource('src/enums.ts', source);

expect(hasSuspiciousKeyFinding(findings)).toBe(false);
});

it('flags a single-class value carrying a known credential prefix', () => {
// `xoxb-` prefix marks it as a real token even though it is lowercase-only
// and below the entropy threshold, so the prefix branch must admit it.
Expand Down