Skip to content

Commit e664d23

Browse files
authored
fix: skip minified files in secret detection (#357)
* fix: skip minified files in secret detection * fix: replaced severity with reason in secret detection * fix: replaced severity with reason in secret detection * fix: added TZ to default exclude keys
1 parent 101fc04 commit e664d23

File tree

11 files changed

+114
-20
lines changed

11 files changed

+114
-20
lines changed

.changeset/cyan-states-fall.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'dotenv-diff': patch
3+
---
4+
5+
added TZ to default exclude keys

.changeset/olive-carrots-share.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'dotenv-diff': patch
3+
---
4+
5+
replaced severity with reason for secret detection output

.changeset/sparkly-places-allow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'dotenv-diff': patch
3+
---
4+
5+
skip minified files in secret detection

packages/cli/src/core/filterIgnoredKeys.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const DEFAULT_EXCLUDE_KEYS = [
1414
'CI',
1515
'GITHUB_ACTIONS',
1616
'INIT_CWD',
17+
'TZ',
1718
];
1819

1920
/**

packages/cli/src/core/security/secretDetectors.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ const HARMLESS_URLS = [
5555
const HARMLESS_ATTRIBUTE_KEYS =
5656
/\b(trackingId|trackingContext|data-testid|data-test|aria-label)\b/i;
5757

58+
// Ignore minified files
59+
function isLikelyMinified(line: string): boolean {
60+
return line.length > 500; // Extremely long line, likely minified
61+
}
62+
5863
// Checks if a line is an HTML text node or tag
5964
function isHtmlTextNode(line: string): boolean {
6065
const trimmed = line.trim();
@@ -285,6 +290,9 @@ export function detectSecretsInSource(
285290
// Check if line has ignore comment
286291
if (hasIgnoreComment(line)) continue;
287292

293+
// Ignore likely minified / bundled lines before any secret detection
294+
if (isLikelyMinified(line)) continue;
295+
288296
// Check for HTTPS URLs
289297
HTTPS_PATTERN.lastIndex = 0;
290298
let httpsMatch: RegExpExecArray | null;

packages/cli/src/ui/scan/printExampleWarnings.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ export function printExampleWarnings(
2323

2424
for (const w of warnings) {
2525
const severityColor = w.severity === 'high' ? error : warning;
26-
console.log(
27-
`${label(padLabel(w.key))}${severityColor(`${w.reason} [${w.severity}]`)}`,
28-
);
26+
console.log(`${label(padLabel(w.key))}${severityColor(w.reason)}`);
2927
}
3028

3129
console.log(`${divider}`);

packages/cli/src/ui/scan/printSecrets.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function printSecrets(
6161
for (const f of findings) {
6262
const color = getSeverityColor(f.severity);
6363
console.log(
64-
`${label(padLabel(f.severity.toUpperCase()))}${color(`${normalizePath(f.file)}:${f.line}`)}`,
64+
`${label(padLabel(f.message))}${color(`${normalizePath(f.file)}:${f.line}`)}`,
6565
);
6666
}
6767
}

packages/cli/test/e2e/cli.autoscan.e2e.test.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('no-flag autoscan', () => {
3939
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
4040
fs.writeFileSync(
4141
path.join(cwd, 'src', 'index.ts'),
42-
`const apiKey = process.env.API_KEY;`.trimStart(),
42+
'const apiKey = process.env.API_KEY;'.trimStart(),
4343
);
4444

4545
const res = runCli(cwd, []);
@@ -58,7 +58,7 @@ describe('no-flag autoscan', () => {
5858
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
5959
fs.writeFileSync(
6060
path.join(cwd, 'src', 'index.ts'),
61-
`const apiKey = process.env.API_KEY;`.trimStart(),
61+
'const apiKey = process.env.API_KEY;'.trimStart(),
6262
);
6363

6464
const res = runCli(cwd, ['--fix']);
@@ -116,7 +116,7 @@ describe('no-flag autoscan', () => {
116116
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
117117
fs.writeFileSync(
118118
path.join(cwd, 'src', 'index.ts'),
119-
`const url = "https://ingenfejl.com";`,
119+
'const url = "https://ingenfejl.com";',
120120
);
121121

122122
const res = runCli(cwd, []);
@@ -216,7 +216,7 @@ describe('no-flag autoscan', () => {
216216
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
217217
fs.writeFileSync(
218218
path.join(cwd, 'src', 'index.js'),
219-
`const key = proccess.env.API_KEY`,
219+
'const key = proccess.env.API_KEY',
220220
);
221221

222222
fs.writeFileSync(
@@ -235,7 +235,7 @@ describe('no-flag autoscan', () => {
235235
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
236236
fs.writeFileSync(
237237
path.join(cwd, 'src', 'index.js'),
238-
`console.log('hello');`,
238+
'console.log(\'hello\');',
239239
);
240240

241241
fs.writeFileSync(path.join(cwd, '.env.example'), 'API_KEY=EXAMPLE_KEY\n');
@@ -251,7 +251,7 @@ describe('no-flag autoscan', () => {
251251
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
252252
fs.writeFileSync(
253253
path.join(cwd, 'src', 'index.js'),
254-
`const key = proccess.env.API_KEY`,
254+
'const key = proccess.env.API_KEY',
255255
);
256256

257257
fs.writeFileSync(
@@ -262,7 +262,6 @@ describe('no-flag autoscan', () => {
262262
const res = runCli(cwd, ['--example', '.env.example']);
263263
expect(res.status).toBe(1);
264264
expect(res.stdout).toContain('▸ Potential secrets in .env.example');
265-
expect(res.stdout).toContain('[high]');
266265
});
267266

268267
it('will ingore files with excludeFiles option in config', () => {
@@ -282,7 +281,7 @@ describe('no-flag autoscan', () => {
282281
fs.mkdirSync(path.join(cwd, 'src', 'ignore'), { recursive: true });
283282
fs.writeFileSync(
284283
path.join(cwd, 'src', 'ignore', 'index.ts'),
285-
`const secret = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";`,
284+
'const secret = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";',
286285
);
287286

288287
const res = runCli(cwd, []);
@@ -307,7 +306,7 @@ describe('no-flag autoscan', () => {
307306
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
308307
fs.writeFileSync(
309308
path.join(cwd, 'src', 'secret.ts'),
310-
`const secret = "sk_live_123456789";`,
309+
'const secret = "sk_live_123456789";',
311310
);
312311

313312
const res = runCli(cwd, []);
@@ -323,7 +322,7 @@ describe('no-flag autoscan', () => {
323322
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
324323
fs.writeFileSync(
325324
path.join(cwd, 'src', 'index.ts'),
326-
`const db = process.env.DATABASE_URL;`,
325+
'const db = process.env.DATABASE_URL;',
327326
);
328327

329328
const res = runCli(cwd, []);
@@ -339,7 +338,7 @@ describe('It will prompt to ask to create .env file is no .env files are found',
339338
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
340339
fs.writeFileSync(
341340
path.join(cwd, 'src', 'index.ts'),
342-
`const apiKey = process.env.API_KEY;`,
341+
'const apiKey = process.env.API_KEY;',
343342
);
344343

345344
const res = runCli(cwd, ['--yes']);
@@ -354,7 +353,7 @@ describe('It will prompt to ask to create .env file is no .env files are found',
354353
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
355354
fs.writeFileSync(
356355
path.join(cwd, 'src', 'index.ts'),
357-
`const apiKey = process.env.API_KEY;`,
356+
'const apiKey = process.env.API_KEY;',
358357
);
359358

360359
const res = runCli(cwd, ['--ci']);
@@ -372,7 +371,7 @@ describe('It will prompt to ask to create .env file is no .env files are found',
372371
fs.writeFileSync(path.join(cwd, '.env'), 'EXISTING_KEY=value\n');
373372
fs.writeFileSync(
374373
path.join(cwd, 'src', 'index.ts'),
375-
`const apiKey = process.env.API_KEY;`,
374+
'const apiKey = process.env.API_KEY;',
376375
);
377376

378377
const res = runCli(cwd, ['--yes']);
@@ -389,7 +388,7 @@ describe('It will prompt to ask to create .env file is no .env files are found',
389388
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
390389
fs.writeFileSync(
391390
path.join(cwd, 'src', 'index.ts'),
392-
`const apiKey = process.env.API_KEY;`,
391+
'const apiKey = process.env.API_KEY;',
393392
);
394393

395394
const res = runCli(cwd, ['--yes', '--json']);
@@ -411,7 +410,7 @@ describe('It will prompt to ask to create .env file is no .env files are found',
411410
fs.mkdirSync(path.join(subdir, 'src'), { recursive: true });
412411
fs.writeFileSync(
413412
path.join(subdir, 'src', 'index.ts'),
414-
`const apiKey = process.env.API_KEY;`,
413+
'const apiKey = process.env.API_KEY;',
415414
);
416415

417416
const res = runCli(subdir, ['--yes']);

packages/cli/test/unit/core/security/secretDetectors.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,5 +527,43 @@ const email = "user@example.com";
527527
expect(findings).toHaveLength(0);
528528
});
529529
});
530+
describe('minified file detection', () => {
531+
it('should ignore lines over 500 chars (likely minified)', () => {
532+
// Real minified line from a bundled file — contains URLs and identifiers
533+
// that would otherwise trigger URL and entropy warnings
534+
const source =
535+
'const WORKSPACES_DOCS_URL="https://www.sanity.io/docs/workspaces",useWorkspaceAuthStates=createHookFromObservableFactory((workspaces)=>combineLatest(workspaces.map((workspace)=>workspace.auth.state.pipe(map((state)=>[workspace.name,state])))).pipe(map((entries)=>Object.fromEntries(entries)))),STATE_TITLES={notAuthenticated:"Not authenticated",authenticated:"Authenticated",error:"Error"},COOKIE_NAME="sanity_workspace",DEFAULT_TIMEOUT=3e4,RETRY_ATTEMPTS=3,BASE_PATH="/v2021-06-07",API_VERSION="2021-06-07";';
536+
expect(source.length).toBeGreaterThan(500); // confirm the line is actually long
537+
const findings = detectSecretsInSource('test.ts', source);
538+
expect(findings).toHaveLength(0);
539+
});
540+
541+
it('should ignore suspicious-looking strings inside minified lines', () => {
542+
// Minified code with a token field — should not warn because the line is minified
543+
const source =
544+
'var n=function(){return Math.random().toString(36).substr(2,9)},t={apiUrl:"https://api.example.com/v1",timeout:3e4,retry:3,token:"placeholder",headers:{"Content-Type":"application/json",Accept:"application/json"},endpoints:{auth:"/auth",users:"/users",data:"/data"},utils:{encode:function(e){return btoa(e)},decode:function(e){return atob(e)},hash:function(e){return e.split("").reduce((function(e,n){return(e=(e<<5)-e+n.charCodeAt(0))&e}),0)}},extra:"paddingToEnsureThisLineExceedsFiveHundredCharactersAsRequiredByTheMinifiedLineDetectionLogicInOurSecretScanner"};';
545+
expect(source.length).toBeGreaterThan(500);
546+
const findings = detectSecretsInSource('test.ts', source);
547+
expect(findings).toHaveLength(0);
548+
});
549+
550+
it('should still detect secrets on normal-length lines', () => {
551+
// A short, normal line with a real secret should still be caught
552+
const source =
553+
'const token = "ghp_1234567890abcdefghijklmnopqrstuvwxyz";';
554+
expect(source.length).toBeLessThan(500);
555+
const findings = detectSecretsInSource('test.ts', source);
556+
expect(findings.length).toBeGreaterThan(0);
557+
});
558+
559+
it('should not ignore a 499-char line', () => {
560+
// Just under the threshold — should still be scanned normally
561+
const padding = 'x'.repeat(380);
562+
const source = `const token = "ghp_1234567890abcdefghijklmnopqrstuvwxyz"; // ${padding}`;
563+
expect(source.length).toBeLessThan(500);
564+
const findings = detectSecretsInSource('test.ts', source);
565+
expect(findings.length).toBeGreaterThan(0);
566+
});
567+
});
530568
});
531569
});

packages/cli/test/unit/ui/scan/printExampleWarnings.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ describe('printExampleWarnings', () => {
6363
String(call[0]).includes('TOKEN'),
6464
),
6565
).toBe(true);
66+
67+
expect(
68+
logSpy.mock.calls.some((call: [string]) =>
69+
String(call[0]).includes('Looks like a real API key'),
70+
),
71+
).toBe(true);
72+
73+
expect(
74+
logSpy.mock.calls.some(
75+
(call: [string]) =>
76+
String(call[0]).includes('[high]') ||
77+
String(call[0]).includes('[medium]'),
78+
),
79+
).toBe(false);
6680
});
6781

6882
it('uses warning indicator when strict is false and no high severity exists', () => {

0 commit comments

Comments
 (0)