dotenv-diff 2.1.6 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,14 @@
1
1
  # dotenv-diff
2
2
 
3
- Easily compare your .env, .env.example, and other environment files (like .env.local, .env.production) to detect missing, extra, empty, or mismatched variables — and ensure they’re properly ignored by Git.
3
+ ![Demo](./public/demo.gif)
4
4
 
5
- Or scan your codebase to find out which environment variables are actually used in your code, and which ones are not.
5
+ Easily scan your codebase to detect which environment variables are actually used in your code and which ones are not.
6
+
7
+ This package will:
8
+ - Scan your codebase to find environment variables in use.
9
+ - Show statistics about the usage of these variables.
10
+ - Detect missing environment variables.
11
+ - Find potential secrets in your codebase.
6
12
 
7
13
  Optimized for JavaScript/TypeScript projects and frontend frameworks including Node.js, Next.js, Vite, SvelteKit, Nuxt, Vue, and Deno. Can also be used with other project types for basic .env file comparison.
8
14
 
@@ -28,19 +34,17 @@ pnpm add -g dotenv-diff
28
34
  ```bash
29
35
  dotenv-diff
30
36
  ```
31
- ## What it checks
32
- dotenv-diff will automatically compare all matching .env* files in your project against .env.example, including:
33
- - `.env`
34
- - `.env.local`
35
- - `.env.production`
36
- - Any other .env.* file
37
37
 
38
- ## Scan your codebase for environment variable usage
38
+ This scans your entire codebase to detect which environment variables are actually used in the code — and compare them against your `.env` file.
39
+
40
+ ## Why dotenv-diff?
41
+
42
+ - **Prevent production issues**: Ensure all required environment variables are defined before deploying.
43
+ - **Avoid runtime errors**: Catch missing or misconfigured variables early in development.
44
+ - **Improve collaboration**: Keep your team aligned on necessary environment variables.
45
+ - **Enhance security**: Ensure sensitive variables are not accidentally committed to version control.
46
+ - **Scale confidently**: Perfect for turbo monorepos and multi-environment setups.
39
47
 
40
- ```bash
41
- dotenv-diff --scan-usage
42
- ```
43
- This scans your entire codebase to detect which environment variables are actually used in the code — and compare them against your `.env` file(s).
44
48
 
45
49
  ## CI/CD integration with `--ci` option
46
50
  You can scan and compare against specific environment file, eg. `.env.example`
@@ -50,30 +54,30 @@ Use the `--ci` flag for automated environments. This enables strict mode where t
50
54
 
51
55
  And the `--example` option allows you to specify which `.env.example` file to compare against.
52
56
 
53
- ### Use it in Github Actions for at turborepo, Example:
57
+ ### Use it in Github Actions Example:
54
58
 
55
59
  ```yaml
56
60
  - name: Check environment variables
57
- run: dotenv-diff --ci --scan-usage --show-unused --example .env.example --include-files \"./src/**/*,../../packages/**/*\" --ignore VITE_MODE,NODE_ENV
61
+ run: dotenv-diff --ci --example .env.example --ignore VITE_MODE,NODE_ENV
58
62
  ```
59
63
 
60
64
  You can also change the comparison file by using the `--example` flag to point to a different `.env.example` file.
61
65
 
62
66
  ```bash
63
- dotenv-diff --scan-usage --example .env.example.staging --ci
67
+ dotenv-diff --example .env.example.staging --ci
64
68
  ```
65
69
 
66
70
  ## Use it in a Turborepo Monorepo
67
71
 
68
72
  In a monorepo setup (e.g. with Turborepo), you often have multiple apps under apps/ and shared packages under packages/.
69
- You can run dotenv-diff from one app and still include files from sibling apps or packages.
73
+ You can run dotenv-diff from one app and still include files from your packages folder.
70
74
 
71
75
  For example, if you want to scan from the apps/app1 folder and also include code in packages/auth, you can do:
72
76
 
73
77
  ```json
74
78
  {
75
79
  "scripts": {
76
- "dotenv-diff": "dotenv-diff --scan-usage --example .env.example --include-files '../../packages/**/*' --ignore VITE_MODE"
80
+ "dotenv-diff": "dotenv-diff --example .env.example --include-files '../../packages/**/*' --ignore VITE_MODE"
77
81
  }
78
82
  }
79
83
  ```
@@ -81,65 +85,49 @@ For example, if you want to scan from the apps/app1 folder and also include code
81
85
  This will:
82
86
  - Compare the variables used in your `apps/app1` code against `apps/app1/.env.example`.
83
87
  - Also scan files in `../../packages`(like `packages/components/src/..`)
84
- - Ignore variables like VITE_MODE that you only use in special cases.
88
+ - Ignore variables like VITE_MODE that you only use in special cases (.env.test).
85
89
 
86
90
  ## Automatic fixes with `--fix`
87
91
 
88
- Use the `--fix` flag to automatically fix missing keys in your `.env` and remove duplicate keys.
92
+ Use the `--fix` flag to automatically fix missing keys in your `.env`.
89
93
 
90
94
  ```bash
91
95
  dotenv-diff --fix
92
96
  ```
93
97
 
94
- This will:
95
- - Add any missing keys from `.env.example` to your `.env` file with empty values
96
- - Remove duplicate keys in your `.env` file (keeping the last occurrence)
97
-
98
- ## Use --fix with scan-usage
99
-
100
- You can also combine `--fix` with `--scan-usage` to automatically add any missing keys that are used in your code but not defined in your `.env` file:
101
-
102
- ```bash
103
- dotenv-diff --scan-usage --fix
104
- ```
105
-
106
- Using `--fix`with `--scan-usage` will not detect duplicate keys, it will only add missing keys.
107
-
108
98
  ### Example workflow
109
99
 
110
- 1. You add `process.env.NEW_API_KEY` in your code.
111
- 2. You run `dotenv-diff --scan-usage --fix`.
100
+ 1. You have `process.env.NEW_API_KEY` in your code.
101
+ 2. You run `dotenv-diff --fix`.
112
102
  3. The tool automatically adds `NEW_API_KEY=` to your `.env` file.
113
103
 
114
- ## Scan your codebase for environment variables and add it directly to .env.example?
104
+ ## Show unused variables
105
+
106
+ As default, `dotenv-diff` will list variables that are defined in `.env` but never used in your codebase.
115
107
 
108
+ To disable this behavior, use the `--no-show-unused` flag:
116
109
  ```bash
117
- dotenv-diff --scan-usage --example .env.example --fix
110
+ dotenv-diff --no-show-unused
118
111
  ```
112
+ This will prevent the tool from listing variables that are defined in `.env` but not used in your codebase.
119
113
 
120
- This scans your codebase for environment variable usage and adds any missing keys directly to your `.env.example` file with empty values.
114
+ ## Show statistics
121
115
 
122
- ## Show unused variables
123
-
124
- Use `--show-unused` together with `--scan-usage` to list variables that are defined in `.env` but never used in your codebase:
125
- ```bash
126
- dotenv-diff --scan-usage --show-unused
127
- ```
128
- This will show you which variables are defined in your `.env` file but not used in your codebase. This helps you clean up unnecessary environment variables.
116
+ By default, `dotenv-diff` will show statistics about the scan, such as the number of files scanned, variables found, and unique variables used.
129
117
 
130
- ## Show scan statistics
118
+ To disable this behavior, use the `--no-show-stats` flag:
131
119
 
132
120
  ```bash
133
- dotenv-diff --show-stats
121
+ dotenv-diff --no-show-stats
134
122
  ```
135
- This will display statistics about the scan, such as the number of files scanned, variables found, and any unused variables. It provides a quick overview of your environment variable usage.
123
+ This will prevent the tool from displaying statistics about the scan.
136
124
 
137
125
  ## include or exclude specific files for scanning
138
126
 
139
127
  You can specify which files to include or exclude from the scan using the `--include-files` and `--exclude-files` options:
140
128
 
141
129
  ```bash
142
- dotenv-diff --scan-usage --include-files '**/*.js,**/*.ts' --exclude-files '**/*.spec.ts'
130
+ dotenv-diff --include-files '**/*.js,**/*.ts' --exclude-files '**/*.spec.ts'
143
131
  ```
144
132
 
145
133
  By default, the scanner looks at JavaScript, TypeScript, Vue, and Svelte files.
@@ -147,32 +135,18 @@ The --include-files and --exclude-files options let you refine this list to focu
147
135
 
148
136
  ### Override with `--files`
149
137
 
150
- If you want to completely override the default include/exclude logic (for example, to also include test files), you can use --files:
138
+ If you want to completely override the default include/exclude logic (for example, to only include specific files), you can use --files:
151
139
  ```bash
152
- dotenv-diff --scan-usage --files '**/*.js'
140
+ dotenv-diff --files '**/*.js'
153
141
  ```
154
- This will only scan the files matching the given patterns, even if they would normally be excluded.
155
142
 
156
143
  ## Optional: Check values too
157
144
 
158
145
  ```bash
159
- dotenv-diff --check-values
146
+ dotenv-diff --check-values --compare
160
147
  ```
161
148
 
162
- When using the `--check-values` option, the tool will also compare the actual values of the variables in `.env` and `.env.example`. It will report any mismatches found and it also compares values if .env.example defines a non-empty expected value.
163
-
164
- ## Duplicate key warnings
165
-
166
- `dotenv-diff` warns when a `.env*` file contains the same key multiple times. The last occurrence wins. Suppress these warnings with `--allow-duplicates`.
167
-
168
- ## Only show specific categories
169
-
170
- Use the `--only` flag to restrict the comparison to specific categories. For example:
171
-
172
- ```bash
173
- dotenv-diff --only missing,extra
174
- ```
175
- This will only show missing and extra keys, ignoring empty, mismatched, duplicate keys and so on.
149
+ When using the `--check-values` option together with `--compare`, the tool will also compare the actual values of the variables in `.env` and `.env.example`. It will report any mismatches found and it also compares values if .env.example defines a non-empty expected value.
176
150
 
177
151
  ## Ignore specific keys
178
152
 
@@ -206,35 +180,40 @@ dotenv-diff --no-color
206
180
 
207
181
  ## Compare specific files
208
182
 
183
+ The `--compare` flag will compare all matching .env* files in your project against .env.example, including:
184
+ - `.env`
185
+ - `.env.local`
186
+ - `.env.production`
187
+ - Any other .env.* file
188
+
209
189
  Override the autoscan and compare exactly two files:
210
190
 
211
191
  ```bash
212
- dotenv-diff --env .env.staging --example .env.example.staging
192
+ dotenv-diff --compare --env .env.local --example .env.example.local
213
193
  ```
214
194
 
215
195
  You can also fix only one side. For example, force a particular `.env` file and let the tool find the matching `.env.example`:
216
196
 
217
- ```bash
218
- dotenv-diff --env .env.production
219
- ```
197
+ When using the `--compare` flag, `dotenv-diff` warns when a `.env*` file contains the same key multiple times. The last occurrence wins. Suppress these warnings with `--allow-duplicates`.
220
198
 
221
- Or provide just an example file and let the tool locate the appropriate `.env`:
199
+ Use the `--only` flag to restrict the comparison to specific categories. For example:
222
200
 
223
201
  ```bash
224
- dotenv-diff --example .env.example.production
202
+ dotenv-diff --only missing,extra
225
203
  ```
204
+ This will only show missing and extra keys, ignoring empty, mismatched, duplicate keys and so on.
226
205
 
227
206
  ## Automatically create missing files
228
207
 
229
208
  Run non-interactively in CI environments with:
230
209
 
231
210
  ```bash
232
- dotenv-diff --yes # auto-create missing files without prompts
211
+ dotenv-diff --compare --yes # auto-create missing files without prompts
233
212
  ```
234
213
 
235
214
  You can also use `-y` as a shorthand for `--yes`.
236
215
 
237
- ## Automatic file creation prompts
216
+ ### Automatic file creation prompts
238
217
 
239
218
  If one of the files is missing, `dotenv-diff` will ask if you want to create it from the other:
240
219
 
@@ -243,7 +222,7 @@ If one of the files is missing, `dotenv-diff` will ask if you want to create it
243
222
 
244
223
  This makes it quick to set up environment files without manually copying or retyping variable names.
245
224
 
246
- ## Also checks your .gitignore
225
+ ### Also checks your .gitignore
247
226
 
248
227
  `dotenv-diff` will warn you if your `.env` file is **not** ignored by Git.
249
228
  This helps prevent accidentally committing sensitive environment variables.
@@ -16,9 +16,11 @@ export function createProgram() {
16
16
  .option('--no-color', 'Disable colored output')
17
17
  .option('--only <list>', 'Comma-separated categories to only run (missing,extra,empty,mismatch,duplicate,gitignore)')
18
18
  .option('--scan-usage', 'Scan codebase for environment variable usage')
19
- .option('--include-files <patterns>', '[requires --scan-usage] Comma-separated file patterns to ADD to default scan patterns (extends default)')
20
- .option('--files <patterns>', '[requires --scan-usage] Comma-separated file patterns to scan (completely replaces default patterns)')
21
- .option('--exclude-files <patterns>', '[requires --scan-usage] Comma-separated file patterns to exclude from scan')
22
- .option('--show-unused', '[requires --scan-usage] Show variables defined in .env but not used in code')
23
- .option('--show-stats', 'Show statistics (in scan-usage mode: scan stats, in compare mode: env compare stats)');
19
+ .option('--compare', 'Compare .env and .env.example files')
20
+ .option('--include-files <patterns>', 'Comma-separated file patterns to ADD to default scan patterns (extends default)')
21
+ .option('--files <patterns>', 'Comma-separated file patterns to scan (completely replaces default patterns)')
22
+ .option('--exclude-files <patterns>', 'Comma-separated file patterns to exclude from scan')
23
+ .option('--no-show-unused', 'Do not list variables that are defined in .env but not used in code')
24
+ .option('--no-show-stats', 'Do not show statistics')
25
+ .option('--no-secrets', 'Disable secret detection during scan (enabled by default)');
24
26
  }
@@ -14,7 +14,8 @@ export async function run(program) {
14
14
  if (opts.noColor) {
15
15
  chalk.level = 0; // disable colors globally
16
16
  }
17
- if (opts.scanUsage) {
17
+ // DEFAULT: scan-usage unless --compare is set
18
+ if (!opts.compare) {
18
19
  const envPath = opts.envFlag || (fs.existsSync('.env') ? '.env' : undefined);
19
20
  const { exitWithError } = await scanUsage({
20
21
  cwd: opts.cwd,
@@ -30,6 +31,7 @@ export async function run(program) {
30
31
  showStats: opts.showStats,
31
32
  isCiMode: opts.isCiMode,
32
33
  files: opts.files,
34
+ secrets: opts.secrets,
33
35
  });
34
36
  process.exit(exitWithError ? 1 : 0);
35
37
  }
@@ -105,7 +107,6 @@ export async function run(program) {
105
107
  isCiMode: opts.isCiMode,
106
108
  });
107
109
  if (res.shouldExit) {
108
- // For JSON mode, emit an empty report to keep output machine-friendly (optional; safe).
109
110
  if (opts.json)
110
111
  console.log(JSON.stringify([], null, 2));
111
112
  process.exit(res.exitCode);
@@ -50,9 +50,6 @@ export async function compareMany(pairs, opts) {
50
50
  },
51
51
  });
52
52
  }
53
- else {
54
- // still call to keep previous hints? No—masked by --only.
55
- }
56
53
  // Duplicate detection (skip entirely if --only excludes it)
57
54
  let dupsEnv = [];
58
55
  let dupsEx = [];
@@ -34,6 +34,12 @@ export interface ScanJsonEntry {
34
34
  }>;
35
35
  comparedAgainst?: string;
36
36
  totalEnvVariables?: number;
37
+ secrets?: Array<{
38
+ file: string;
39
+ line: number;
40
+ message: string;
41
+ snippet: string;
42
+ }>;
37
43
  }
38
44
  /**
39
45
  * Scans codebase for environment variable usage and compares with .env file
@@ -125,6 +125,10 @@ export async function scanUsage(opts) {
125
125
  console.log(chalk.green('✅ Auto-fix applied: no changes needed.'));
126
126
  console.log();
127
127
  }
128
+ if (scanResult.missing.length > 0 && !opts.json && !opts.fix) {
129
+ console.log(chalk.gray('💡 Tip: Run with `--fix` to add missing keys'));
130
+ console.log();
131
+ }
128
132
  return result;
129
133
  }
130
134
  /**
@@ -172,6 +176,14 @@ function createJsonOutput(scanResult, opts, comparedAgainst, totalEnvVariables)
172
176
  missing: missingGrouped,
173
177
  unused: scanResult.unused,
174
178
  };
179
+ if (scanResult.secrets?.length) {
180
+ output.secrets = scanResult.secrets.map((s) => ({
181
+ file: s.file,
182
+ line: s.line,
183
+ message: s.message,
184
+ snippet: s.snippet,
185
+ }));
186
+ }
175
187
  // Add comparison info if we compared against a file
176
188
  if (comparedAgainst) {
177
189
  output.comparedAgainst = comparedAgainst;
@@ -198,15 +210,15 @@ function outputToConsole(scanResult, opts, comparedAgainst) {
198
210
  }
199
211
  // Show stats if requested
200
212
  if (opts.showStats) {
201
- console.log(chalk.bold('📊 Scan Statistics:'));
202
- console.log(chalk.gray(` Files scanned: ${scanResult.stats.filesScanned}`));
203
- console.log(chalk.gray(` Total usages found: ${scanResult.stats.totalUsages}`));
204
- console.log(chalk.gray(` Unique variables: ${scanResult.stats.uniqueVariables}`));
213
+ console.log(chalk.blue('📊 Scan Statistics:'));
214
+ console.log(chalk.blue.dim(` Files scanned: ${scanResult.stats.filesScanned}`));
215
+ console.log(chalk.blue.dim(` Total usages found: ${scanResult.stats.totalUsages}`));
216
+ console.log(chalk.blue.dim(` Unique variables: ${scanResult.stats.uniqueVariables}`));
205
217
  console.log();
206
218
  }
207
219
  // Always show found variables when not comparing or when no missing variables
208
220
  if (!comparedAgainst || scanResult.missing.length === 0) {
209
- console.log(chalk.green(`✅ Found ${scanResult.stats.uniqueVariables} unique environment variables in use`));
221
+ console.log(chalk.blue(`🌐 Found ${scanResult.stats.uniqueVariables} unique environment variables in use`));
210
222
  console.log();
211
223
  // List all variables found (if any)
212
224
  if (scanResult.stats.uniqueVariables > 0) {
@@ -225,7 +237,7 @@ function outputToConsole(scanResult, opts, comparedAgainst) {
225
237
  if (opts.showStats) {
226
238
  const displayUsages = usages.slice(0, 2);
227
239
  displayUsages.forEach((usage) => {
228
- console.log(chalk.gray(` Used in: ${usage.file}:${usage.line} (${usage.pattern})`));
240
+ console.log(chalk.blue.dim(` Used in: ${usage.file}:${usage.line} (${usage.pattern})`));
229
241
  });
230
242
  if (usages.length > 2) {
231
243
  console.log(chalk.gray(` ... and ${usages.length - 2} more locations`));
@@ -273,6 +285,22 @@ function outputToConsole(scanResult, opts, comparedAgainst) {
273
285
  });
274
286
  console.log();
275
287
  }
288
+ if (scanResult.secrets && scanResult.secrets.length > 0) {
289
+ console.log(chalk.yellow('🔒 Potential secrets detected in codebase:'));
290
+ const byFile = new Map();
291
+ for (const f of scanResult.secrets) {
292
+ if (!byFile.has(f.file))
293
+ byFile.set(f.file, []);
294
+ byFile.get(f.file).push(f);
295
+ }
296
+ for (const [file, findings] of byFile) {
297
+ console.log(chalk.bold(` ${file}`));
298
+ for (const f of findings) {
299
+ console.log(chalk.yellow(` - Line ${f.line}: ${f.message}\n ${chalk.dim(f.snippet)}`));
300
+ }
301
+ }
302
+ console.log();
303
+ }
276
304
  // Success message for env file comparison
277
305
  if (comparedAgainst && scanResult.missing.length === 0) {
278
306
  console.log(chalk.green(`✅ All used environment variables are defined in ${comparedAgainst}`));
@@ -281,9 +309,5 @@ function outputToConsole(scanResult, opts, comparedAgainst) {
281
309
  }
282
310
  console.log();
283
311
  }
284
- if (scanResult.missing.length > 0 && !opts.json && !opts.fix) {
285
- console.log(chalk.gray('💡 Tip: Run with `--fix` to add these missing keys to your .env file automatically.'));
286
- console.log();
287
- }
288
312
  return { exitWithError };
289
313
  }
@@ -26,12 +26,15 @@ export function normalizeOptions(raw) {
26
26
  const json = Boolean(raw.json);
27
27
  const onlyParsed = parseCategories(raw.only, '--only');
28
28
  const only = onlyParsed.length ? onlyParsed : undefined;
29
- const scanUsage = Boolean(raw.scanUsage);
29
+ const noColor = Boolean(raw.noColor);
30
+ const compare = Boolean(raw.compare);
31
+ const scanUsage = raw.scanUsage ?? !compare;
30
32
  const includeFiles = parseList(raw.includeFiles);
31
33
  const excludeFiles = parseList(raw.excludeFiles);
32
- const showUnused = Boolean(raw.showUnused);
33
- const showStats = Boolean(raw.showStats);
34
+ const showUnused = raw.showUnused === false ? false : true;
35
+ const showStats = raw.showStats === false ? false : true;
34
36
  const files = parseList(raw.files);
37
+ const secrets = raw.secrets === false ? false : true;
35
38
  const ignore = parseList(raw.ignore);
36
39
  const ignoreRegex = [];
37
40
  for (const pattern of parseList(raw.ignoreRegex)) {
@@ -62,11 +65,14 @@ export function normalizeOptions(raw) {
62
65
  ignoreRegex,
63
66
  cwd,
64
67
  only,
68
+ compare,
69
+ noColor,
65
70
  scanUsage,
66
71
  includeFiles,
67
72
  excludeFiles,
68
73
  showUnused,
69
74
  showStats,
70
75
  files,
76
+ secrets,
71
77
  };
72
78
  }
@@ -13,6 +13,7 @@ export type Options = {
13
13
  ignoreRegex: RegExp[];
14
14
  cwd: string;
15
15
  only?: Category[];
16
+ compare: boolean;
16
17
  scanUsage: boolean;
17
18
  includeFiles: string[];
18
19
  excludeFiles: string[];
@@ -20,6 +21,7 @@ export type Options = {
20
21
  showStats: boolean;
21
22
  files?: string[];
22
23
  noColor?: boolean;
24
+ secrets: boolean;
23
25
  };
24
26
  export type RawOptions = {
25
27
  checkValues?: boolean;
@@ -33,12 +35,15 @@ export type RawOptions = {
33
35
  ignore?: string | string[];
34
36
  ignoreRegex?: string | string[];
35
37
  only?: string | string[];
38
+ compare?: boolean;
39
+ noColor?: boolean;
36
40
  scanUsage?: boolean;
37
41
  includeFiles?: string | string[];
38
42
  excludeFiles?: string | string[];
39
43
  showUnused?: boolean;
40
44
  showStats?: boolean;
41
45
  files?: string | string[];
46
+ secrets?: boolean;
42
47
  };
43
48
  export type CompareJsonEntry = {
44
49
  env: string;
@@ -0,0 +1 @@
1
+ export declare function shannonEntropyNormalized(s: string): number;
@@ -0,0 +1,16 @@
1
+ export function shannonEntropyNormalized(s) {
2
+ if (!s)
3
+ return 0;
4
+ const freq = new Map();
5
+ for (const ch of s)
6
+ freq.set(ch, (freq.get(ch) ?? 0) + 1);
7
+ const len = s.length;
8
+ let H = 0;
9
+ for (const [, c] of freq) {
10
+ const p = c / len;
11
+ H += -p * Math.log2(p);
12
+ }
13
+ // Antag alfabet ~72 tegn (A-Za-z0-9+/_- mv.)
14
+ const maxH = Math.log2(72);
15
+ return Math.min(1, H / maxH);
16
+ }
@@ -0,0 +1,8 @@
1
+ export type SecretFinding = {
2
+ file: string;
3
+ line: number;
4
+ kind: 'pattern' | 'entropy';
5
+ message: string;
6
+ snippet: string;
7
+ };
8
+ export declare function detectSecretsInSource(file: string, source: string): SecretFinding[];
@@ -0,0 +1,75 @@
1
+ import { shannonEntropyNormalized } from './entropy.js';
2
+ const SUSPICIOUS_KEYS = /\b(pass(word)?|secret|token|apikey|api_key|key|auth|bearer|private|client_secret|access[_-]?token)\b/i;
3
+ const PROVIDER_PATTERNS = [
4
+ /\bAKIA[0-9A-Z]{16}\b/, // AWS access key id
5
+ /\bASIA[0-9A-Z]{16}\b/, // AWS temp key
6
+ /\bghp_[0-9A-Za-z]{30,}\b/, // GitHub token
7
+ /\bsk_live_[0-9a-zA-Z]{24,}\b/, // Stripe live secret
8
+ /\bsk_test_[0-9a-zA-Z]{24,}\b/, // Stripe test secret
9
+ /\bAIza[0-9A-Za-z\-_]{20,}\b/, // Google API key
10
+ ];
11
+ const LONG_LITERAL = /["'`]{1}([A-Za-z0-9+/_\-]{24,})["'`]{1}/g;
12
+ function looksHarmlessLiteral(s) {
13
+ return /^https?:\/\//i.test(s) || /\S+@\S+/.test(s);
14
+ }
15
+ // Undgå støj fra typiske test/fixture/mock filer (meget konservativ liste)
16
+ function isProbablyTestPath(p) {
17
+ return (/\b(__tests__|__mocks__|fixtures|sandbox|samples)\b/i.test(p) ||
18
+ /\.(spec|test)\.[jt]sx?$/.test(p));
19
+ }
20
+ const DEFAULT_SECRET_THRESHOLD = 0.9;
21
+ export function detectSecretsInSource(file, source) {
22
+ const threshold = isProbablyTestPath(file) ? 0.95 : DEFAULT_SECRET_THRESHOLD;
23
+ const findings = [];
24
+ const lines = source.split(/\r?\n/);
25
+ for (let i = 0; i < lines.length; i++) {
26
+ const lineNo = i + 1;
27
+ const line = lines[i];
28
+ // 1) Suspicious key literal assignments
29
+ if (SUSPICIOUS_KEYS.test(line)) {
30
+ const m = line.match(/=\s*["'`](.+?)["'`]/);
31
+ if (m && m[1] && !looksHarmlessLiteral(m[1]) && m[1].length >= 12) {
32
+ findings.push({
33
+ file,
34
+ line: lineNo,
35
+ kind: 'pattern',
36
+ message: 'matches password/secret/token-like literal assignment',
37
+ snippet: line.trim().slice(0, 180),
38
+ });
39
+ }
40
+ }
41
+ // 2) Provider patterns
42
+ for (const rx of PROVIDER_PATTERNS) {
43
+ if (rx.test(line)) {
44
+ findings.push({
45
+ file,
46
+ line: lineNo,
47
+ kind: 'pattern',
48
+ message: 'matches known provider key pattern',
49
+ snippet: line.trim().slice(0, 180),
50
+ });
51
+ }
52
+ }
53
+ // 3) High-entropy long literals
54
+ LONG_LITERAL.lastIndex = 0;
55
+ let lm;
56
+ while ((lm = LONG_LITERAL.exec(line))) {
57
+ const literal = lm[1];
58
+ if (looksHarmlessLiteral(literal))
59
+ continue;
60
+ if (literal.length < 32)
61
+ continue; // ekstra støjfilter
62
+ const ent = shannonEntropyNormalized(literal);
63
+ if (ent >= threshold) {
64
+ findings.push({
65
+ file,
66
+ line: lineNo,
67
+ kind: 'entropy',
68
+ message: `found high-entropy string (len ${literal.length}, H≈${ent.toFixed(2)})`,
69
+ snippet: line.trim().slice(0, 180),
70
+ });
71
+ }
72
+ }
73
+ }
74
+ return findings;
75
+ }
package/dist/src/index.js CHANGED
@@ -1,2 +1,6 @@
1
1
  export { parseEnvFile } from './lib/parseEnv.js';
2
2
  export { diffEnv } from './lib/diffEnv.js';
3
+ process.env.API_KEY = '1234asdasdasasdsdfdfaasddsa5';
4
+ process.env.NEW_API_KEY = '67890';
5
+ const password = '3fg400asipkfoemkfmojwpajwmdklaosjfiop';
6
+ const service = 'http://my-service.com';
@@ -1,3 +1,4 @@
1
+ import { type SecretFinding } from '../core/secretDetectors.js';
1
2
  export interface EnvUsage {
2
3
  variable: string;
3
4
  file: string;
@@ -13,6 +14,7 @@ export interface ScanOptions {
13
14
  ignore: string[];
14
15
  ignoreRegex: RegExp[];
15
16
  files?: string[];
17
+ secrets?: boolean;
16
18
  }
17
19
  export interface ScanResult {
18
20
  used: EnvUsage[];
@@ -23,6 +25,7 @@ export interface ScanResult {
23
25
  totalUsages: number;
24
26
  uniqueVariables: number;
25
27
  };
28
+ secrets: SecretFinding[];
26
29
  }
27
30
  /**
28
31
  * Scans the codebase for environment variable usage based on the provided options.
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import fsSync from 'fs';
4
+ import { detectSecretsInSource, } from '../core/secretDetectors.js';
4
5
  // Framework-specific patterns for finding environment variable usage
5
6
  const ENV_PATTERNS = [
6
7
  {
@@ -74,11 +75,24 @@ export async function scanCodebase(opts) {
74
75
  });
75
76
  const allUsages = [];
76
77
  let filesScanned = 0;
78
+ const allSecrets = [];
77
79
  for (const filePath of files) {
78
80
  try {
79
81
  const content = await fs.readFile(filePath, 'utf-8');
80
82
  const fileUsages = await scanFile(filePath, content, opts);
81
83
  allUsages.push(...fileUsages);
84
+ if (opts.secrets) {
85
+ try {
86
+ // Brug relativ path i findings, så output matcher dine øvrige prints
87
+ const relativePath = path.relative(opts.cwd, filePath);
88
+ const sec = detectSecretsInSource(relativePath, content);
89
+ if (sec.length)
90
+ allSecrets.push(...sec);
91
+ }
92
+ catch {
93
+ // aldrig fail scannet pga. detector; bare fortsæt
94
+ }
95
+ }
82
96
  filesScanned++;
83
97
  }
84
98
  catch {
@@ -94,6 +108,7 @@ export async function scanCodebase(opts) {
94
108
  used: filteredUsages,
95
109
  missing: [],
96
110
  unused: [],
111
+ secrets: allSecrets,
97
112
  stats: {
98
113
  filesScanned,
99
114
  totalUsages: filteredUsages.length,
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dotenv-diff",
3
- "version": "2.1.6",
3
+ "version": "2.2.0",
4
4
  "type": "module",
5
- "description": "Find differences between .env and .env.example / .env.* files. And optionally scan your codebase to find environment variables in use.",
5
+ "description": "scan your codebase to find environment variables in use.",
6
6
  "bin": {
7
7
  "dotenv-diff": "dist/bin/dotenv-diff.js"
8
8
  },