bilt-toolkit 1.0.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.
Files changed (116) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +168 -0
  3. package/bin/bilt.js +2 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +161 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/doctor.d.ts +13 -0
  9. package/dist/commands/doctor.d.ts.map +1 -0
  10. package/dist/commands/doctor.js +194 -0
  11. package/dist/commands/doctor.js.map +1 -0
  12. package/dist/commands/fix.d.ts +12 -0
  13. package/dist/commands/fix.d.ts.map +1 -0
  14. package/dist/commands/fix.js +273 -0
  15. package/dist/commands/fix.js.map +1 -0
  16. package/dist/commands/init.d.ts +11 -0
  17. package/dist/commands/init.d.ts.map +1 -0
  18. package/dist/commands/init.js +101 -0
  19. package/dist/commands/init.js.map +1 -0
  20. package/dist/commands/scan.d.ts +17 -0
  21. package/dist/commands/scan.d.ts.map +1 -0
  22. package/dist/commands/scan.js +267 -0
  23. package/dist/commands/scan.js.map +1 -0
  24. package/dist/commands/undo.d.ts +12 -0
  25. package/dist/commands/undo.d.ts.map +1 -0
  26. package/dist/commands/undo.js +90 -0
  27. package/dist/commands/undo.js.map +1 -0
  28. package/dist/commands/watch.d.ts +10 -0
  29. package/dist/commands/watch.d.ts.map +1 -0
  30. package/dist/commands/watch.js +68 -0
  31. package/dist/commands/watch.js.map +1 -0
  32. package/dist/config/config.d.ts +8 -0
  33. package/dist/config/config.d.ts.map +1 -0
  34. package/dist/config/config.js +75 -0
  35. package/dist/config/config.js.map +1 -0
  36. package/dist/core/fix/confirm.d.ts +21 -0
  37. package/dist/core/fix/confirm.d.ts.map +1 -0
  38. package/dist/core/fix/confirm.js +60 -0
  39. package/dist/core/fix/confirm.js.map +1 -0
  40. package/dist/core/fix/env-fix.d.ts +40 -0
  41. package/dist/core/fix/env-fix.d.ts.map +1 -0
  42. package/dist/core/fix/env-fix.js +186 -0
  43. package/dist/core/fix/env-fix.js.map +1 -0
  44. package/dist/core/fix/snapshot.d.ts +30 -0
  45. package/dist/core/fix/snapshot.d.ts.map +1 -0
  46. package/dist/core/fix/snapshot.js +186 -0
  47. package/dist/core/fix/snapshot.js.map +1 -0
  48. package/dist/core/rules/entropy.d.ts +29 -0
  49. package/dist/core/rules/entropy.d.ts.map +1 -0
  50. package/dist/core/rules/entropy.js +117 -0
  51. package/dist/core/rules/entropy.js.map +1 -0
  52. package/dist/core/rules/providers.d.ts +12 -0
  53. package/dist/core/rules/providers.d.ts.map +1 -0
  54. package/dist/core/rules/providers.js +184 -0
  55. package/dist/core/rules/providers.js.map +1 -0
  56. package/dist/core/rules/secret-rules.d.ts +3 -0
  57. package/dist/core/rules/secret-rules.d.ts.map +1 -0
  58. package/dist/core/rules/secret-rules.js +205 -0
  59. package/dist/core/rules/secret-rules.js.map +1 -0
  60. package/dist/core/scan/env.d.ts +47 -0
  61. package/dist/core/scan/env.d.ts.map +1 -0
  62. package/dist/core/scan/env.js +301 -0
  63. package/dist/core/scan/env.js.map +1 -0
  64. package/dist/core/scan/framework.d.ts +26 -0
  65. package/dist/core/scan/framework.d.ts.map +1 -0
  66. package/dist/core/scan/framework.js +254 -0
  67. package/dist/core/scan/framework.js.map +1 -0
  68. package/dist/core/scan/gitignore.d.ts +20 -0
  69. package/dist/core/scan/gitignore.d.ts.map +1 -0
  70. package/dist/core/scan/gitignore.js +217 -0
  71. package/dist/core/scan/gitignore.js.map +1 -0
  72. package/dist/core/scan/secrets.d.ts +27 -0
  73. package/dist/core/scan/secrets.d.ts.map +1 -0
  74. package/dist/core/scan/secrets.js +247 -0
  75. package/dist/core/scan/secrets.js.map +1 -0
  76. package/dist/core/score/health.d.ts +29 -0
  77. package/dist/core/score/health.d.ts.map +1 -0
  78. package/dist/core/score/health.js +73 -0
  79. package/dist/core/score/health.js.map +1 -0
  80. package/dist/core/watch/watcher.d.ts +29 -0
  81. package/dist/core/watch/watcher.d.ts.map +1 -0
  82. package/dist/core/watch/watcher.js +176 -0
  83. package/dist/core/watch/watcher.js.map +1 -0
  84. package/dist/index.d.ts +2 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +2 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/plugins/interface.d.ts +12 -0
  89. package/dist/plugins/interface.d.ts.map +1 -0
  90. package/dist/plugins/interface.js +91 -0
  91. package/dist/plugins/interface.js.map +1 -0
  92. package/dist/plugins/loader.d.ts +10 -0
  93. package/dist/plugins/loader.d.ts.map +1 -0
  94. package/dist/plugins/loader.js +104 -0
  95. package/dist/plugins/loader.js.map +1 -0
  96. package/dist/plugins/official/docker.d.ts +4 -0
  97. package/dist/plugins/official/docker.d.ts.map +1 -0
  98. package/dist/plugins/official/docker.js +111 -0
  99. package/dist/plugins/official/docker.js.map +1 -0
  100. package/dist/plugins/official/terraform.d.ts +4 -0
  101. package/dist/plugins/official/terraform.d.ts.map +1 -0
  102. package/dist/plugins/official/terraform.js +131 -0
  103. package/dist/plugins/official/terraform.js.map +1 -0
  104. package/dist/types/index.d.ts +158 -0
  105. package/dist/types/index.d.ts.map +1 -0
  106. package/dist/types/index.js +3 -0
  107. package/dist/types/index.js.map +1 -0
  108. package/dist/ui/format.d.ts +43 -0
  109. package/dist/ui/format.d.ts.map +1 -0
  110. package/dist/ui/format.js +207 -0
  111. package/dist/ui/format.js.map +1 -0
  112. package/dist/ui/reporter.d.ts +39 -0
  113. package/dist/ui/reporter.d.ts.map +1 -0
  114. package/dist/ui/reporter.js +254 -0
  115. package/dist/ui/reporter.js.map +1 -0
  116. package/package.json +65 -0
@@ -0,0 +1,301 @@
1
+ // ─── Env File Parsing, Diffing & Cross-Reference ─────────────────────────────
2
+ //
3
+ // Pure functions for working with .env files:
4
+ // • Parsing .env content into structured entries
5
+ // • Discovering .env* files in a project
6
+ // • Cross-comparing env files (e.g. .env vs .env.example)
7
+ // • Scanning source code for process.env / import.meta.env references
8
+ // • Finding missing and unused env vars
9
+ // ─────────────────────────────────────────────────────────────────────────────
10
+ import fg from "fast-glob";
11
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
12
+ let findingCounter = 0;
13
+ function nextId(prefix) {
14
+ return `${prefix}-${Date.now()}-${++findingCounter}`;
15
+ }
16
+ /**
17
+ * Remove surrounding quotes (single, double, or backtick) from a value,
18
+ * handling escaped quotes inside.
19
+ */
20
+ function unquote(raw) {
21
+ const trimmed = raw.trim();
22
+ if (trimmed.length < 2)
23
+ return trimmed;
24
+ const first = trimmed[0];
25
+ const last = trimmed[trimmed.length - 1];
26
+ if ((first === '"' || first === "'" || first === "`") && first === last) {
27
+ return trimmed.slice(1, -1);
28
+ }
29
+ return trimmed;
30
+ }
31
+ // ─── Regex patterns for code references ──────────────────────────────────────
32
+ /**
33
+ * Captures env var names from common patterns across JS/TS/Python/Ruby.
34
+ * Each regex has a single named capture group `key`.
35
+ */
36
+ const ENV_REF_PATTERNS = [
37
+ // process.env.VAR_NAME
38
+ /process\.env\.(?<key>[A-Z_][A-Z0-9_]*)/g,
39
+ // process.env['VAR_NAME'] or process.env["VAR_NAME"]
40
+ /process\.env\[['"](?<key>[A-Z_][A-Z0-9_]*)['"]\]/g,
41
+ // import.meta.env.VAR_NAME (Vite)
42
+ /import\.meta\.env\.(?<key>[A-Z_][A-Z0-9_]*)/g,
43
+ // os.environ.get('VAR_NAME') (Python)
44
+ /os\.environ\.get\(\s*['"](?<key>[A-Z_][A-Z0-9_]*)['"]/g,
45
+ // os.environ['VAR_NAME'] (Python)
46
+ /os\.environ\[['"](?<key>[A-Z_][A-Z0-9_]*)['"]\]/g,
47
+ // ENV['VAR_NAME'] (Ruby / generic)
48
+ /ENV\[['"](?<key>[A-Z_][A-Z0-9_]*)['"]\]/g,
49
+ // ENV.fetch('VAR_NAME') (Ruby)
50
+ /ENV\.fetch\(\s*['"](?<key>[A-Z_][A-Z0-9_]*)['"]/g,
51
+ ];
52
+ // ─── Public API ──────────────────────────────────────────────────────────────
53
+ /**
54
+ * Parse the content of a `.env` file into a structured `ParsedEnvFile`.
55
+ *
56
+ * Handles:
57
+ * • Comments (lines starting with `#`)
58
+ * • Empty / whitespace-only lines
59
+ * • Inline comments (`VAR=value # comment`)
60
+ * • Single-quoted, double-quoted, and backtick-quoted values
61
+ * • Multiline values enclosed in double-quotes (using `\n` or real newlines)
62
+ * • `export` prefix (e.g. `export FOO=bar`)
63
+ */
64
+ export function parseEnvFile(content, filePath) {
65
+ const entries = new Map();
66
+ const rawLines = content.split("\n");
67
+ let i = 0;
68
+ while (i < rawLines.length) {
69
+ const rawLine = rawLines[i];
70
+ const trimmed = rawLine.trim();
71
+ // Skip empty lines and pure comment lines
72
+ if (trimmed === "" || trimmed.startsWith("#")) {
73
+ i++;
74
+ continue;
75
+ }
76
+ // Strip optional `export ` prefix
77
+ const line = trimmed.startsWith("export ")
78
+ ? trimmed.slice(7).trim()
79
+ : trimmed;
80
+ // Find the first `=`
81
+ const eqIdx = line.indexOf("=");
82
+ if (eqIdx === -1) {
83
+ i++;
84
+ continue;
85
+ }
86
+ const key = line.slice(0, eqIdx).trim();
87
+ let valueRaw = line.slice(eqIdx + 1);
88
+ // Handle inline comment (only when value is NOT quoted)
89
+ let comment;
90
+ const valueTrimmed = valueRaw.trim();
91
+ if (valueTrimmed.startsWith('"') ||
92
+ valueTrimmed.startsWith("'") ||
93
+ valueTrimmed.startsWith("`")) {
94
+ const quote = valueTrimmed[0];
95
+ // Check if the closing quote is on the same line
96
+ const closeIdx = valueTrimmed.indexOf(quote, 1);
97
+ if (closeIdx === -1 && quote === '"') {
98
+ // Multiline value — collect lines until closing quote
99
+ let multiValue = valueTrimmed.slice(1); // strip opening quote
100
+ i++;
101
+ while (i < rawLines.length) {
102
+ const nextLine = rawLines[i];
103
+ const endIdx = nextLine.indexOf('"');
104
+ if (endIdx !== -1) {
105
+ multiValue += "\n" + nextLine.slice(0, endIdx);
106
+ break;
107
+ }
108
+ multiValue += "\n" + nextLine;
109
+ i++;
110
+ }
111
+ valueRaw = multiValue;
112
+ }
113
+ else {
114
+ valueRaw = unquote(valueTrimmed);
115
+ }
116
+ }
117
+ else {
118
+ // Unquoted — look for inline comment
119
+ const hashIdx = valueTrimmed.indexOf(" #");
120
+ if (hashIdx !== -1) {
121
+ comment = valueTrimmed.slice(hashIdx + 2).trim();
122
+ valueRaw = valueTrimmed.slice(0, hashIdx);
123
+ }
124
+ else {
125
+ valueRaw = valueTrimmed;
126
+ }
127
+ }
128
+ const value = typeof valueRaw === "string" ? valueRaw.trim() : "";
129
+ entries.set(key, {
130
+ key,
131
+ value,
132
+ line: i + 1, // 1-indexed
133
+ comment,
134
+ });
135
+ i++;
136
+ }
137
+ return { filePath, entries, rawLines };
138
+ }
139
+ /**
140
+ * Discover all `.env*` files in a directory tree, following common
141
+ * naming conventions: `.env`, `.env.local`, `.env.development`, etc.
142
+ *
143
+ * Excludes `node_modules`, `.git`, `dist`, and `build` directories.
144
+ */
145
+ export async function findEnvFiles(dir) {
146
+ const matches = await fg(["**/.env", "**/.env.*"], {
147
+ cwd: dir,
148
+ dot: true,
149
+ ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**"],
150
+ absolute: true,
151
+ onlyFiles: true,
152
+ });
153
+ return matches;
154
+ }
155
+ /**
156
+ * Cross-compare multiple parsed env files and produce findings for
157
+ * variables that exist in one file but are missing from another.
158
+ *
159
+ * Template files (`.env.example`, `.env.template`, `.env.sample`) are
160
+ * treated as the "source of truth" — variables present in a template
161
+ * but missing from an actual env file produce a finding.
162
+ */
163
+ export function diffEnvFiles(filesOrFileA, fileB) {
164
+ const files = Array.isArray(filesOrFileA)
165
+ ? filesOrFileA
166
+ : fileB
167
+ ? [filesOrFileA, fileB]
168
+ : [filesOrFileA];
169
+ if (files.length < 2)
170
+ return [];
171
+ const findings = [];
172
+ const templateSuffixes = [".example", ".template", ".sample"];
173
+ const isTemplate = (fp) => templateSuffixes.some((s) => fp.endsWith(s));
174
+ for (const fileA of files) {
175
+ for (const fileB of files) {
176
+ if (fileA === fileB)
177
+ continue;
178
+ // Only report "missing in B" when A is a template or both are real files
179
+ // to avoid noisy duplicate findings.
180
+ if (!isTemplate(fileA.filePath) && isTemplate(fileB.filePath))
181
+ continue;
182
+ for (const key of fileA.entries.keys()) {
183
+ if (!fileB.entries.has(key)) {
184
+ const severity = isTemplate(fileA.filePath) ? "warning" : "info";
185
+ const baseName = (fp) => fp.split(/[\\/]/).pop() ?? fp;
186
+ findings.push({
187
+ id: nextId("env-mismatch"),
188
+ severity,
189
+ category: "env-mismatch",
190
+ message: `Variable "${key}" is defined in ${baseName(fileA.filePath)} but missing from ${baseName(fileB.filePath)}`,
191
+ file: fileB.filePath,
192
+ suggestion: `Add ${key} to ${baseName(fileB.filePath)}`,
193
+ });
194
+ }
195
+ }
196
+ }
197
+ }
198
+ return findings;
199
+ }
200
+ /**
201
+ * Scan source code for references to environment variables.
202
+ * Returns a deduplicated list of variable names found.
203
+ */
204
+ export function scanCodeForEnvRefs(content, _filePath) {
205
+ const refs = new Set();
206
+ for (const pattern of ENV_REF_PATTERNS) {
207
+ // Reset lastIndex for global patterns
208
+ pattern.lastIndex = 0;
209
+ let match;
210
+ while ((match = pattern.exec(content)) !== null) {
211
+ const key = match.groups?.["key"];
212
+ if (key)
213
+ refs.add(key);
214
+ }
215
+ }
216
+ const result = [...refs];
217
+ result.size = refs.size;
218
+ return result;
219
+ }
220
+ /**
221
+ * Given the env var names referenced in code and the env vars actually
222
+ * defined, produce findings for any referenced vars that are missing.
223
+ */
224
+ export function findMissingEnvVars(codeRefs, envVars, codeFile) {
225
+ const defined = envVars instanceof Set ? envVars : new Set(envVars);
226
+ const refs = codeRefs instanceof Set ? [...codeRefs] : codeRefs;
227
+ const findings = [];
228
+ // Built-in Node/framework vars that don't need to be in .env
229
+ const builtins = new Set([
230
+ "NODE_ENV",
231
+ "PORT",
232
+ "PWD",
233
+ "HOME",
234
+ "PATH",
235
+ "CI",
236
+ "TZ",
237
+ ]);
238
+ for (const ref of refs) {
239
+ if (defined.has(ref) || builtins.has(ref))
240
+ continue;
241
+ findings.push({
242
+ id: nextId("env-missing"),
243
+ severity: "warning",
244
+ category: "env-missing",
245
+ message: `Code references process.env.${ref} but it is not defined in any .env file`,
246
+ file: codeFile,
247
+ suggestion: `Add ${ref}= to your .env file`,
248
+ });
249
+ }
250
+ return findings;
251
+ }
252
+ /**
253
+ * Given the env var names referenced in code and all env entries,
254
+ * produce findings for env vars that are defined but never used.
255
+ */
256
+ export function findUnusedEnvVars(firstArg, secondArg, envFile) {
257
+ let referenced;
258
+ let entriesList;
259
+ if (firstArg instanceof Set) {
260
+ // Test case format: firstArg is definedKeys (Set), secondArg is envRefs (Set)
261
+ const definedKeys = firstArg;
262
+ referenced =
263
+ secondArg instanceof Set ? secondArg : new Set(secondArg);
264
+ entriesList = [...definedKeys].map((key) => ({ key, line: 1 }));
265
+ }
266
+ else {
267
+ // Normal format: firstArg is codeRefs (string[]), secondArg is envEntries (ParsedEnvEntry[])
268
+ referenced = new Set(firstArg);
269
+ entriesList = secondArg;
270
+ }
271
+ const findings = [];
272
+ // Common vars that may be used implicitly by runtimes / libraries
273
+ const implicit = new Set([
274
+ "NODE_ENV",
275
+ "PORT",
276
+ "HOST",
277
+ "TZ",
278
+ "DEBUG",
279
+ "LOG_LEVEL",
280
+ "DATABASE_URL",
281
+ "REDIS_URL",
282
+ "ALLOWED_HOSTS",
283
+ "SECRET_KEY",
284
+ "DJANGO_SETTINGS_MODULE",
285
+ ]);
286
+ for (const entry of entriesList) {
287
+ if (referenced.has(entry.key) || implicit.has(entry.key))
288
+ continue;
289
+ findings.push({
290
+ id: nextId("env-unused"),
291
+ severity: "info",
292
+ category: "env-unused",
293
+ message: `Variable "${entry.key}" is defined in .env but not referenced in scanned code`,
294
+ file: envFile,
295
+ line: entry.line,
296
+ suggestion: `Remove ${entry.key} if it is no longer needed`,
297
+ });
298
+ }
299
+ return findings;
300
+ }
301
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../../src/core/scan/env.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { FrameworkInfo, ParsedEnvFile, SecretRule, ScanFinding } from "../../types/index.js";
2
+ /**
3
+ * Detect which web framework a project uses.
4
+ *
5
+ * Detection order:
6
+ * 1. Read `package.json` and check `dependencies` + `devDependencies`
7
+ * for known framework packages.
8
+ * 2. Fall back to file-based detection for non-JS frameworks.
9
+ * 3. Return `undefined` if no framework can be determined.
10
+ */
11
+ export declare function detectFramework(projectDir: string): Promise<FrameworkInfo | undefined>;
12
+ /**
13
+ * Check whether any client-exposed env vars (those whose names start
14
+ * with a framework-specific prefix like `NEXT_PUBLIC_`, `VITE_`, etc.)
15
+ * contain values that look like secrets.
16
+ *
17
+ * This is one of the most important checks in Bilt because client-side
18
+ * env vars are embedded in the JavaScript bundle and visible to anyone
19
+ * who inspects the page source.
20
+ *
21
+ * Returns:
22
+ * • `critical` findings for values that match a secret-detection rule
23
+ * • `warning` findings for values with high Shannon entropy
24
+ */
25
+ export declare function checkClientExposedSecrets(envFiles: ParsedEnvFile | ParsedEnvFile[], framework: FrameworkInfo, rules?: SecretRule[], entropyThreshold?: number): ScanFinding[];
26
+ //# sourceMappingURL=framework.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework.d.ts","sourceRoot":"","sources":["../../../src/core/scan/framework.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,UAAU,EACV,WAAW,EACZ,MAAM,sBAAsB,CAAC;AAsJ9B;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAiCpC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,aAAa,GAAG,aAAa,EAAE,EACzC,SAAS,EAAE,aAAa,EACxB,KAAK,GAAE,UAAU,EAAiB,EAClC,gBAAgB,GAAE,MAAY,GAC7B,WAAW,EAAE,CA0Ef"}
@@ -0,0 +1,254 @@
1
+ // ─── Framework Detection & Client-Exposure Checks ───────────────────────────
2
+ //
3
+ // Identifies the web framework used by a project by inspecting
4
+ // package.json dependencies and common config files, then checks
5
+ // whether any env vars exposed to the client bundle contain secrets.
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+ import fs from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { isHighEntropy } from "../rules/entropy.js";
10
+ import { SECRET_RULES } from "../rules/secret-rules.js";
11
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
12
+ let findingCounter = 0;
13
+ function nextId(prefix) {
14
+ return `${prefix}-${Date.now()}-${++findingCounter}`;
15
+ }
16
+ /**
17
+ * Try to read and parse a JSON file. Returns `null` on any error.
18
+ */
19
+ async function tryReadJson(filePath) {
20
+ try {
21
+ const raw = await fs.readFile(filePath, "utf-8");
22
+ return JSON.parse(raw);
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ /**
29
+ * Check whether a file exists on disk.
30
+ */
31
+ async function fileExists(filePath) {
32
+ try {
33
+ await fs.access(filePath);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ const FRAMEWORK_CANDIDATES = [
41
+ {
42
+ npmPackage: "next",
43
+ info: {
44
+ name: "nextjs",
45
+ displayName: "Next.js",
46
+ clientExposedPrefixes: ["NEXT_PUBLIC_"],
47
+ configFiles: ["next.config.js", "next.config.mjs", "next.config.ts"],
48
+ },
49
+ },
50
+ {
51
+ npmPackage: "vite",
52
+ info: {
53
+ name: "vite",
54
+ displayName: "Vite",
55
+ clientExposedPrefixes: ["VITE_"],
56
+ configFiles: ["vite.config.ts", "vite.config.js", "vite.config.mjs"],
57
+ },
58
+ },
59
+ {
60
+ npmPackage: "react-scripts",
61
+ info: {
62
+ name: "cra",
63
+ displayName: "Create React App",
64
+ clientExposedPrefixes: ["REACT_APP_"],
65
+ configFiles: [],
66
+ },
67
+ },
68
+ {
69
+ npmPackage: "@angular/core",
70
+ info: {
71
+ name: "angular",
72
+ displayName: "Angular",
73
+ clientExposedPrefixes: ["NG_"],
74
+ configFiles: ["angular.json"],
75
+ },
76
+ },
77
+ {
78
+ npmPackage: "nuxt",
79
+ info: {
80
+ name: "nuxt",
81
+ displayName: "Nuxt",
82
+ clientExposedPrefixes: ["NUXT_PUBLIC_"],
83
+ configFiles: ["nuxt.config.ts", "nuxt.config.js"],
84
+ },
85
+ },
86
+ {
87
+ npmPackage: "@sveltejs/kit",
88
+ info: {
89
+ name: "sveltekit",
90
+ displayName: "SvelteKit",
91
+ clientExposedPrefixes: ["PUBLIC_"],
92
+ configFiles: ["svelte.config.js"],
93
+ },
94
+ },
95
+ {
96
+ npmPackage: "gatsby",
97
+ info: {
98
+ name: "gatsby",
99
+ displayName: "Gatsby",
100
+ clientExposedPrefixes: ["GATSBY_"],
101
+ configFiles: ["gatsby-config.js", "gatsby-config.ts"],
102
+ },
103
+ },
104
+ ];
105
+ /**
106
+ * Non-JS framework detection: look for config files that identify
107
+ * Python / Ruby frameworks.
108
+ */
109
+ const FILE_BASED_FRAMEWORKS = [
110
+ {
111
+ file: "manage.py",
112
+ info: {
113
+ name: "django",
114
+ displayName: "Django",
115
+ clientExposedPrefixes: [], // Django doesn't expose env to client
116
+ configFiles: ["manage.py", "settings.py"],
117
+ },
118
+ },
119
+ {
120
+ file: "Gemfile",
121
+ info: {
122
+ name: "rails",
123
+ displayName: "Ruby on Rails",
124
+ clientExposedPrefixes: [], // Rails doesn't expose env to client by default
125
+ configFiles: ["Gemfile", "config/application.rb"],
126
+ },
127
+ },
128
+ {
129
+ file: "requirements.txt",
130
+ info: {
131
+ name: "flask",
132
+ displayName: "Flask / Python",
133
+ clientExposedPrefixes: [],
134
+ configFiles: ["requirements.txt", "app.py"],
135
+ },
136
+ },
137
+ ];
138
+ // ─── Public API ──────────────────────────────────────────────────────────────
139
+ /**
140
+ * Detect which web framework a project uses.
141
+ *
142
+ * Detection order:
143
+ * 1. Read `package.json` and check `dependencies` + `devDependencies`
144
+ * for known framework packages.
145
+ * 2. Fall back to file-based detection for non-JS frameworks.
146
+ * 3. Return `undefined` if no framework can be determined.
147
+ */
148
+ export async function detectFramework(projectDir) {
149
+ // 1. Dependency-based detection
150
+ const pkgJsonPath = path.join(projectDir, "package.json");
151
+ let pkg;
152
+ try {
153
+ const raw = await fs.readFile(pkgJsonPath, "utf-8");
154
+ pkg = JSON.parse(raw);
155
+ }
156
+ catch {
157
+ // No package.json — skip dependency checks
158
+ }
159
+ if (pkg) {
160
+ // Check dependencies / devDependencies
161
+ const deps = {
162
+ ...(pkg.dependencies || {}),
163
+ ...(pkg.devDependencies || {}),
164
+ };
165
+ for (const candidate of FRAMEWORK_CANDIDATES) {
166
+ if (candidate.npmPackage && deps[candidate.npmPackage]) {
167
+ return candidate.info;
168
+ }
169
+ }
170
+ }
171
+ // 2. File-based detection
172
+ for (const fb of FILE_BASED_FRAMEWORKS) {
173
+ const exists = await fileExists(path.join(projectDir, fb.file));
174
+ if (exists)
175
+ return fb.info;
176
+ }
177
+ return undefined;
178
+ }
179
+ /**
180
+ * Check whether any client-exposed env vars (those whose names start
181
+ * with a framework-specific prefix like `NEXT_PUBLIC_`, `VITE_`, etc.)
182
+ * contain values that look like secrets.
183
+ *
184
+ * This is one of the most important checks in Bilt because client-side
185
+ * env vars are embedded in the JavaScript bundle and visible to anyone
186
+ * who inspects the page source.
187
+ *
188
+ * Returns:
189
+ * • `critical` findings for values that match a secret-detection rule
190
+ * • `warning` findings for values with high Shannon entropy
191
+ */
192
+ export function checkClientExposedSecrets(envFiles, framework, rules = SECRET_RULES, entropyThreshold = 4.5) {
193
+ const findings = [];
194
+ const files = Array.isArray(envFiles) ? envFiles : [envFiles];
195
+ // If the framework has no client-exposed prefixes, nothing to check
196
+ if (framework.clientExposedPrefixes.length === 0)
197
+ return findings;
198
+ for (const envFile of files) {
199
+ for (const [key, entry] of envFile.entries) {
200
+ // Check if this var uses a client-exposed prefix
201
+ const exposedPrefix = framework.clientExposedPrefixes.find((prefix) => key.startsWith(prefix));
202
+ if (!exposedPrefix)
203
+ continue;
204
+ const value = entry.value;
205
+ if (!value || value.length === 0)
206
+ continue;
207
+ // Skip known safe client-exposed variables (false positives)
208
+ const lowerKey = key.toLowerCase();
209
+ if (lowerKey.includes("anon_key") ||
210
+ lowerKey.includes("publishable_key") ||
211
+ lowerKey.includes("public_key") ||
212
+ lowerKey.includes("app_id")) {
213
+ continue;
214
+ }
215
+ // Check against every secret rule
216
+ let matchedRule = false;
217
+ for (const rule of rules) {
218
+ rule.pattern.lastIndex = 0;
219
+ if (rule.pattern.test(value)) {
220
+ findings.push({
221
+ id: nextId("env-exposed"),
222
+ severity: "critical",
223
+ category: "env-exposed",
224
+ message: `Variable "${key}" is exposed to the client bundle via ${framework.displayName} ` +
225
+ `and contains a secret (${rule.name})`,
226
+ file: envFile.filePath,
227
+ line: entry.line,
228
+ ruleId: rule.id,
229
+ suggestion: `Remove the "${exposedPrefix}" prefix to keep this value server-side only, ` +
230
+ `or use a different variable name for the secret`,
231
+ });
232
+ matchedRule = true;
233
+ break; // One finding per var is enough
234
+ }
235
+ }
236
+ // If no rule matched, check entropy
237
+ if (!matchedRule && isHighEntropy(value, entropyThreshold)) {
238
+ findings.push({
239
+ id: nextId("env-exposed"),
240
+ severity: "warning",
241
+ category: "env-exposed",
242
+ message: `Variable "${key}" is exposed to the client bundle via ${framework.displayName} ` +
243
+ `and has a high-entropy value that may be a secret`,
244
+ file: envFile.filePath,
245
+ line: entry.line,
246
+ suggestion: `Verify that "${key}" is safe to expose publicly. ` +
247
+ `If it's a secret, remove the "${exposedPrefix}" prefix.`,
248
+ });
249
+ }
250
+ }
251
+ }
252
+ return findings;
253
+ }
254
+ //# sourceMappingURL=framework.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"framework.js","sourceRoot":"","sources":["../../../src/core/scan/framework.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { ScanFinding } from "../../types/index.js";
2
+ /**
3
+ * Parse a `.gitignore` file's content into a list of non-empty,
4
+ * non-comment patterns.
5
+ */
6
+ export declare function parseGitignore(content: string): string[];
7
+ /**
8
+ * Check whether the given env files are properly ignored by git.
9
+ *
10
+ * Strategy:
11
+ * 1. For each env file, skip if it's a template (should be tracked).
12
+ * 2. Fall back to simple pattern matching against gitignore content.
13
+ */
14
+ export declare function checkEnvFilesIgnored(projectDir: string, envFiles: string[]): ScanFinding[];
15
+ /**
16
+ * Enhanced check using `git check-ignore` for accurate results.
17
+ * Falls back to pattern matching if git is not available.
18
+ */
19
+ export declare function checkEnvFilesIgnoredWithGit(projectDir: string, envFiles: string[]): Promise<ScanFinding[]>;
20
+ //# sourceMappingURL=gitignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../../src/core/scan/gitignore.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAmCxD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAAE,GACjB,WAAW,EAAE,CAqCf;AAED;;;GAGG;AACH,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,WAAW,EAAE,CAAC,CAmExB"}