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.
- package/LICENSE +0 -0
- package/README.md +168 -0
- package/bin/bilt.js +2 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +161 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.d.ts +13 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +194 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/fix.d.ts +12 -0
- package/dist/commands/fix.d.ts.map +1 -0
- package/dist/commands/fix.js +273 -0
- package/dist/commands/fix.js.map +1 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +101 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/scan.d.ts +17 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +267 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/undo.d.ts +12 -0
- package/dist/commands/undo.d.ts.map +1 -0
- package/dist/commands/undo.js +90 -0
- package/dist/commands/undo.js.map +1 -0
- package/dist/commands/watch.d.ts +10 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +68 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +75 -0
- package/dist/config/config.js.map +1 -0
- package/dist/core/fix/confirm.d.ts +21 -0
- package/dist/core/fix/confirm.d.ts.map +1 -0
- package/dist/core/fix/confirm.js +60 -0
- package/dist/core/fix/confirm.js.map +1 -0
- package/dist/core/fix/env-fix.d.ts +40 -0
- package/dist/core/fix/env-fix.d.ts.map +1 -0
- package/dist/core/fix/env-fix.js +186 -0
- package/dist/core/fix/env-fix.js.map +1 -0
- package/dist/core/fix/snapshot.d.ts +30 -0
- package/dist/core/fix/snapshot.d.ts.map +1 -0
- package/dist/core/fix/snapshot.js +186 -0
- package/dist/core/fix/snapshot.js.map +1 -0
- package/dist/core/rules/entropy.d.ts +29 -0
- package/dist/core/rules/entropy.d.ts.map +1 -0
- package/dist/core/rules/entropy.js +117 -0
- package/dist/core/rules/entropy.js.map +1 -0
- package/dist/core/rules/providers.d.ts +12 -0
- package/dist/core/rules/providers.d.ts.map +1 -0
- package/dist/core/rules/providers.js +184 -0
- package/dist/core/rules/providers.js.map +1 -0
- package/dist/core/rules/secret-rules.d.ts +3 -0
- package/dist/core/rules/secret-rules.d.ts.map +1 -0
- package/dist/core/rules/secret-rules.js +205 -0
- package/dist/core/rules/secret-rules.js.map +1 -0
- package/dist/core/scan/env.d.ts +47 -0
- package/dist/core/scan/env.d.ts.map +1 -0
- package/dist/core/scan/env.js +301 -0
- package/dist/core/scan/env.js.map +1 -0
- package/dist/core/scan/framework.d.ts +26 -0
- package/dist/core/scan/framework.d.ts.map +1 -0
- package/dist/core/scan/framework.js +254 -0
- package/dist/core/scan/framework.js.map +1 -0
- package/dist/core/scan/gitignore.d.ts +20 -0
- package/dist/core/scan/gitignore.d.ts.map +1 -0
- package/dist/core/scan/gitignore.js +217 -0
- package/dist/core/scan/gitignore.js.map +1 -0
- package/dist/core/scan/secrets.d.ts +27 -0
- package/dist/core/scan/secrets.d.ts.map +1 -0
- package/dist/core/scan/secrets.js +247 -0
- package/dist/core/scan/secrets.js.map +1 -0
- package/dist/core/score/health.d.ts +29 -0
- package/dist/core/score/health.d.ts.map +1 -0
- package/dist/core/score/health.js +73 -0
- package/dist/core/score/health.js.map +1 -0
- package/dist/core/watch/watcher.d.ts +29 -0
- package/dist/core/watch/watcher.d.ts.map +1 -0
- package/dist/core/watch/watcher.js +176 -0
- package/dist/core/watch/watcher.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/interface.d.ts +12 -0
- package/dist/plugins/interface.d.ts.map +1 -0
- package/dist/plugins/interface.js +91 -0
- package/dist/plugins/interface.js.map +1 -0
- package/dist/plugins/loader.d.ts +10 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +104 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/official/docker.d.ts +4 -0
- package/dist/plugins/official/docker.d.ts.map +1 -0
- package/dist/plugins/official/docker.js +111 -0
- package/dist/plugins/official/docker.js.map +1 -0
- package/dist/plugins/official/terraform.d.ts +4 -0
- package/dist/plugins/official/terraform.d.ts.map +1 -0
- package/dist/plugins/official/terraform.js +131 -0
- package/dist/plugins/official/terraform.js.map +1 -0
- package/dist/types/index.d.ts +158 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/format.d.ts +43 -0
- package/dist/ui/format.d.ts.map +1 -0
- package/dist/ui/format.js +207 -0
- package/dist/ui/format.js.map +1 -0
- package/dist/ui/reporter.d.ts +39 -0
- package/dist/ui/reporter.d.ts.map +1 -0
- package/dist/ui/reporter.js +254 -0
- package/dist/ui/reporter.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../../src/core/watch/watcher.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAe,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIhF,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAyFD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GACnC,aAAa,CAoFf;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// ─── File Watcher ────────────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Watches the project directory for file changes and runs incremental
|
|
4
|
+
// secret + env scans on modified files. Uses chokidar for cross-platform
|
|
5
|
+
// file watching with 300ms debouncing.
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
import chokidar from "chokidar";
|
|
8
|
+
import fs from "node:fs/promises";
|
|
9
|
+
import { scanFileForSecrets } from "../scan/secrets.js";
|
|
10
|
+
import { SECRET_RULES } from "../rules/secret-rules.js";
|
|
11
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
12
|
+
/** Default debounce interval in milliseconds. */
|
|
13
|
+
const DEFAULT_DEBOUNCE_MS = 300;
|
|
14
|
+
/** Directories and file patterns to always ignore. */
|
|
15
|
+
const IGNORED_PATTERNS = [
|
|
16
|
+
"**/node_modules/**",
|
|
17
|
+
"**/.git/**",
|
|
18
|
+
"**/.bilt/**",
|
|
19
|
+
"**/dist/**",
|
|
20
|
+
"**/build/**",
|
|
21
|
+
"**/.next/**",
|
|
22
|
+
"**/.nuxt/**",
|
|
23
|
+
"**/.svelte-kit/**",
|
|
24
|
+
"**/__pycache__/**",
|
|
25
|
+
];
|
|
26
|
+
/** Binary file extensions to skip. */
|
|
27
|
+
const BINARY_EXTENSIONS = new Set([
|
|
28
|
+
".png",
|
|
29
|
+
".jpg",
|
|
30
|
+
".jpeg",
|
|
31
|
+
".gif",
|
|
32
|
+
".bmp",
|
|
33
|
+
".ico",
|
|
34
|
+
".webp",
|
|
35
|
+
".svg",
|
|
36
|
+
".mp3",
|
|
37
|
+
".mp4",
|
|
38
|
+
".avi",
|
|
39
|
+
".mov",
|
|
40
|
+
".webm",
|
|
41
|
+
".zip",
|
|
42
|
+
".tar",
|
|
43
|
+
".gz",
|
|
44
|
+
".rar",
|
|
45
|
+
".7z",
|
|
46
|
+
".exe",
|
|
47
|
+
".dll",
|
|
48
|
+
".so",
|
|
49
|
+
".dylib",
|
|
50
|
+
".woff",
|
|
51
|
+
".woff2",
|
|
52
|
+
".ttf",
|
|
53
|
+
".eot",
|
|
54
|
+
".otf",
|
|
55
|
+
".pdf",
|
|
56
|
+
".doc",
|
|
57
|
+
".docx",
|
|
58
|
+
".xls",
|
|
59
|
+
".xlsx",
|
|
60
|
+
".lock",
|
|
61
|
+
".map",
|
|
62
|
+
]);
|
|
63
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Check if a file path has a binary extension.
|
|
66
|
+
*/
|
|
67
|
+
function isBinaryFile(filePath) {
|
|
68
|
+
const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
|
|
69
|
+
return BINARY_EXTENSIONS.has(ext);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a debounced version of a function that coalesces rapid calls
|
|
73
|
+
* into a single invocation after a quiet period.
|
|
74
|
+
*/
|
|
75
|
+
function debounce(fn, delayMs) {
|
|
76
|
+
let timer = null;
|
|
77
|
+
return (...args) => {
|
|
78
|
+
if (timer !== null)
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
timer = setTimeout(() => {
|
|
81
|
+
timer = null;
|
|
82
|
+
fn(...args);
|
|
83
|
+
}, delayMs);
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
87
|
+
/**
|
|
88
|
+
* Start watching a project directory for file changes.
|
|
89
|
+
*
|
|
90
|
+
* On each change:
|
|
91
|
+
* 1. Read the modified file.
|
|
92
|
+
* 2. Run secret-detection rules against its content.
|
|
93
|
+
* 3. Invoke the `onEvent` callback with the resulting findings.
|
|
94
|
+
*
|
|
95
|
+
* The watcher ignores `node_modules`, `.git`, `.bilt`, `dist`, `build`,
|
|
96
|
+
* and binary files. Changes are debounced (default 300ms) to avoid
|
|
97
|
+
* flooding the callback during bulk operations like `npm install`.
|
|
98
|
+
*
|
|
99
|
+
* @param projectDir Root directory to watch.
|
|
100
|
+
* @param config Bilt configuration (used for entropy threshold & ignore patterns).
|
|
101
|
+
* @param onEvent Callback invoked with watch events.
|
|
102
|
+
* @returns A handle with a `close()` method to stop watching.
|
|
103
|
+
*/
|
|
104
|
+
export function startWatcher(projectDir, config, onEvent) {
|
|
105
|
+
// Merge user-configured ignore patterns with defaults
|
|
106
|
+
const ignorePatterns = [...IGNORED_PATTERNS, ...config.ignore];
|
|
107
|
+
// Combine built-in rules with custom rules
|
|
108
|
+
const allRules = [...SECRET_RULES, ...config.customRules];
|
|
109
|
+
const watcher = chokidar.watch(projectDir, {
|
|
110
|
+
ignored: ignorePatterns,
|
|
111
|
+
persistent: true,
|
|
112
|
+
ignoreInitial: true,
|
|
113
|
+
awaitWriteFinish: {
|
|
114
|
+
stabilityThreshold: 200,
|
|
115
|
+
pollInterval: 100,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* Process a single file event.
|
|
120
|
+
*/
|
|
121
|
+
const processFile = async (eventType, filePath) => {
|
|
122
|
+
// Skip binary files
|
|
123
|
+
if (isBinaryFile(filePath))
|
|
124
|
+
return;
|
|
125
|
+
let content;
|
|
126
|
+
try {
|
|
127
|
+
content = await fs.readFile(filePath, "utf-8");
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// File may have been deleted between the event and the read
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const findings = scanFileForSecrets(content, filePath, allRules, config.entropyThreshold);
|
|
134
|
+
const event = {
|
|
135
|
+
type: eventType,
|
|
136
|
+
file: filePath,
|
|
137
|
+
findings,
|
|
138
|
+
timestamp: new Date(),
|
|
139
|
+
};
|
|
140
|
+
onEvent(event);
|
|
141
|
+
};
|
|
142
|
+
// Create debounced handler
|
|
143
|
+
const debouncedProcess = debounce((eventType, filePath) => {
|
|
144
|
+
void processFile(eventType, filePath);
|
|
145
|
+
}, DEFAULT_DEBOUNCE_MS);
|
|
146
|
+
// Attach event handlers
|
|
147
|
+
watcher.on("add", (filePath) => {
|
|
148
|
+
debouncedProcess("add", filePath);
|
|
149
|
+
});
|
|
150
|
+
watcher.on("change", (filePath) => {
|
|
151
|
+
debouncedProcess("change", filePath);
|
|
152
|
+
});
|
|
153
|
+
watcher.on("unlink", (filePath) => {
|
|
154
|
+
const event = {
|
|
155
|
+
type: "unlink",
|
|
156
|
+
file: filePath,
|
|
157
|
+
findings: [],
|
|
158
|
+
timestamp: new Date(),
|
|
159
|
+
};
|
|
160
|
+
onEvent(event);
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
close: async () => {
|
|
164
|
+
await watcher.close();
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Stop a running watcher.
|
|
170
|
+
*
|
|
171
|
+
* @param watcher The watcher handle returned by `startWatcher`.
|
|
172
|
+
*/
|
|
173
|
+
export async function stopWatcher(watcher) {
|
|
174
|
+
await watcher.close();
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../../src/core/watch/watcher.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PluginManifest, PluginContext, PluginResult, BiltConfig } from "../types/index.js";
|
|
2
|
+
export type { PluginManifest, PluginContext, PluginResult };
|
|
3
|
+
/**
|
|
4
|
+
* Runtime type-guard: validates that an unknown value conforms to PluginManifest.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validatePlugin(plugin: unknown): plugin is PluginManifest;
|
|
7
|
+
/**
|
|
8
|
+
* Build a PluginContext from the project root and config.
|
|
9
|
+
* Collects file list, parses .env files, and gathers git info.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createPluginContext(rootDir: string, config: BiltConfig): Promise<PluginContext>;
|
|
12
|
+
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/plugins/interface.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,YAAY,EACZ,UAAU,EACX,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC;AAI5D;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,cAAc,CAexE;AAID;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,aAAa,CAAC,CA2DxB"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// ─── Plugin Interface ────────────────────────────────────────────────────────
|
|
2
|
+
// Re-exports plugin types and provides runtime validation + context creation.
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fg from "fast-glob";
|
|
6
|
+
import { simpleGit } from "simple-git";
|
|
7
|
+
// ─── Runtime Validation ──────────────────────────────────────────────────────
|
|
8
|
+
/**
|
|
9
|
+
* Runtime type-guard: validates that an unknown value conforms to PluginManifest.
|
|
10
|
+
*/
|
|
11
|
+
export function validatePlugin(plugin) {
|
|
12
|
+
if (plugin === null || typeof plugin !== "object")
|
|
13
|
+
return false;
|
|
14
|
+
const p = plugin;
|
|
15
|
+
if (typeof p["name"] !== "string" || p["name"].length === 0)
|
|
16
|
+
return false;
|
|
17
|
+
if (typeof p["version"] !== "string" || p["version"].length === 0)
|
|
18
|
+
return false;
|
|
19
|
+
if (typeof p["description"] !== "string")
|
|
20
|
+
return false;
|
|
21
|
+
if (typeof p["check"] !== "function")
|
|
22
|
+
return false;
|
|
23
|
+
// fix is optional but must be a function if present
|
|
24
|
+
if (p["fix"] !== undefined && typeof p["fix"] !== "function")
|
|
25
|
+
return false;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
// ─── Context Builder ─────────────────────────────────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* Build a PluginContext from the project root and config.
|
|
31
|
+
* Collects file list, parses .env files, and gathers git info.
|
|
32
|
+
*/
|
|
33
|
+
export async function createPluginContext(rootDir, config) {
|
|
34
|
+
// Gather all project files (respecting ignore patterns)
|
|
35
|
+
const files = await fg("**/*", {
|
|
36
|
+
cwd: rootDir,
|
|
37
|
+
dot: true,
|
|
38
|
+
ignore: config.ignore,
|
|
39
|
+
onlyFiles: true,
|
|
40
|
+
});
|
|
41
|
+
// Parse env vars from .env files
|
|
42
|
+
const envVars = new Map();
|
|
43
|
+
const envFiles = files.filter((f) => path.basename(f).startsWith(".env") && !f.includes("node_modules"));
|
|
44
|
+
for (const envFile of envFiles) {
|
|
45
|
+
const fullPath = path.join(rootDir, envFile);
|
|
46
|
+
try {
|
|
47
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
48
|
+
const vars = new Map();
|
|
49
|
+
for (const line of content.split("\n")) {
|
|
50
|
+
const trimmed = line.trim();
|
|
51
|
+
if (trimmed === "" || trimmed.startsWith("#"))
|
|
52
|
+
continue;
|
|
53
|
+
const eqIdx = trimmed.indexOf("=");
|
|
54
|
+
if (eqIdx === -1)
|
|
55
|
+
continue;
|
|
56
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
57
|
+
const value = trimmed
|
|
58
|
+
.slice(eqIdx + 1)
|
|
59
|
+
.trim()
|
|
60
|
+
.replace(/^["']|["']$/g, "");
|
|
61
|
+
vars.set(key, value);
|
|
62
|
+
}
|
|
63
|
+
envVars.set(envFile, vars);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Skip unreadable files
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Git info
|
|
70
|
+
let isRepo = false;
|
|
71
|
+
let branch;
|
|
72
|
+
try {
|
|
73
|
+
const git = simpleGit(rootDir);
|
|
74
|
+
isRepo = await git.checkIsRepo();
|
|
75
|
+
if (isRepo) {
|
|
76
|
+
const status = await git.status();
|
|
77
|
+
branch = status.current ?? undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
isRepo = false;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
rootDir,
|
|
85
|
+
files,
|
|
86
|
+
envVars,
|
|
87
|
+
git: { isRepo, branch },
|
|
88
|
+
config,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=interface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/plugins/interface.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BiltConfig, PluginManifest } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Load all plugins from:
|
|
4
|
+
* 1. `bilt-plugin-*` packages in node_modules
|
|
5
|
+
* 2. Explicit paths in config.plugins
|
|
6
|
+
*
|
|
7
|
+
* Each module is dynamically imported and validated at runtime.
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadPlugins(config: BiltConfig, rootDir: string): Promise<PluginManifest[]>;
|
|
10
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/plugins/loader.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKpE;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,cAAc,EAAE,CAAC,CA0D3B"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// ─── Plugin Loader ───────────────────────────────────────────────────────────
|
|
2
|
+
// Discovers, loads, and validates plugins from node_modules and config paths.
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promises as fs } from "node:fs";
|
|
6
|
+
import { validatePlugin } from "./interface.js";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
/**
|
|
9
|
+
* Load all plugins from:
|
|
10
|
+
* 1. `bilt-plugin-*` packages in node_modules
|
|
11
|
+
* 2. Explicit paths in config.plugins
|
|
12
|
+
*
|
|
13
|
+
* Each module is dynamically imported and validated at runtime.
|
|
14
|
+
*/
|
|
15
|
+
export async function loadPlugins(config, rootDir) {
|
|
16
|
+
const plugins = [];
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
// ── 1. Discover bilt-plugin-* in node_modules ────────────────────────
|
|
19
|
+
const nodeModulesDir = path.join(rootDir, "node_modules");
|
|
20
|
+
try {
|
|
21
|
+
const entries = await fs.readdir(nodeModulesDir, { withFileTypes: true });
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (entry.isDirectory() && entry.name.startsWith("bilt-plugin-")) {
|
|
24
|
+
const pluginPath = path.join(nodeModulesDir, entry.name);
|
|
25
|
+
const loaded = await tryLoadPlugin(pluginPath);
|
|
26
|
+
if (loaded && !seen.has(loaded.name)) {
|
|
27
|
+
seen.add(loaded.name);
|
|
28
|
+
plugins.push(loaded);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Also check @bilt/ scoped packages
|
|
32
|
+
if (entry.isDirectory() && entry.name === "@bilt") {
|
|
33
|
+
const scopedDir = path.join(nodeModulesDir, "@bilt");
|
|
34
|
+
try {
|
|
35
|
+
const scopedEntries = await fs.readdir(scopedDir, {
|
|
36
|
+
withFileTypes: true,
|
|
37
|
+
});
|
|
38
|
+
for (const se of scopedEntries) {
|
|
39
|
+
if (se.isDirectory() && se.name.startsWith("plugin-")) {
|
|
40
|
+
const pluginPath = path.join(scopedDir, se.name);
|
|
41
|
+
const loaded = await tryLoadPlugin(pluginPath);
|
|
42
|
+
if (loaded && !seen.has(loaded.name)) {
|
|
43
|
+
seen.add(loaded.name);
|
|
44
|
+
plugins.push(loaded);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Scoped directory not readable
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// node_modules doesn't exist — fine
|
|
57
|
+
}
|
|
58
|
+
// ── 2. Load explicit plugin paths from config ────────────────────────
|
|
59
|
+
for (const pluginRef of config.plugins) {
|
|
60
|
+
const resolved = path.isAbsolute(pluginRef)
|
|
61
|
+
? pluginRef
|
|
62
|
+
: path.resolve(rootDir, pluginRef);
|
|
63
|
+
const loaded = await tryLoadPlugin(resolved);
|
|
64
|
+
if (loaded && !seen.has(loaded.name)) {
|
|
65
|
+
seen.add(loaded.name);
|
|
66
|
+
plugins.push(loaded);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return plugins;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Attempt to dynamically import a plugin from a given path, validate, return.
|
|
73
|
+
* Returns null if loading fails or validation fails.
|
|
74
|
+
*/
|
|
75
|
+
async function tryLoadPlugin(pluginPath) {
|
|
76
|
+
try {
|
|
77
|
+
// Try dynamic import (ESM)
|
|
78
|
+
let mod;
|
|
79
|
+
try {
|
|
80
|
+
mod = await import(pluginPath);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Fallback to require (CJS)
|
|
84
|
+
try {
|
|
85
|
+
mod = require(pluginPath);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Unwrap default export if present
|
|
92
|
+
const exported = mod !== null && typeof mod === "object" && "default" in mod
|
|
93
|
+
? mod["default"]
|
|
94
|
+
: mod;
|
|
95
|
+
if (validatePlugin(exported)) {
|
|
96
|
+
return exported;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/plugins/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../../src/plugins/official/docker.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,sBAAsB,CAAC;AA0B9B,QAAA,MAAM,MAAM,EAAE,cAwGb,CAAC;eAEa,MAAM"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// ─── Docker Plugin ───────────────────────────────────────────────────────────
|
|
2
|
+
// Scans Dockerfiles for hardcoded secrets and validates .dockerignore.
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
// Patterns that indicate hardcoded secrets in Dockerfile ENV directives
|
|
6
|
+
const SECRET_PATTERNS = [
|
|
7
|
+
{
|
|
8
|
+
name: "API key",
|
|
9
|
+
pattern: /ENV\s+\w*(API_KEY|APIKEY|API_SECRET)\w*\s*=\s*["']?(?!(\$\{|<|your_|changeme|placeholder|xxx))\S+/i,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: "password",
|
|
13
|
+
pattern: /ENV\s+\w*(PASSWORD|PASSWD|DB_PASS)\w*\s*=\s*["']?(?!(\$\{|<|your_|changeme|placeholder|xxx))\S+/i,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "token",
|
|
17
|
+
pattern: /ENV\s+\w*(TOKEN|SECRET_KEY|PRIVATE_KEY|ACCESS_KEY)\w*\s*=\s*["']?(?!(\$\{|<|your_|changeme|placeholder|xxx))\S+/i,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "connection string",
|
|
21
|
+
pattern: /ENV\s+\w*(DATABASE_URL|REDIS_URL|MONGO_URI|CONNECTION_STRING)\w*\s*=\s*["']?(?!(\$\{|<|your_|changeme|placeholder|xxx))\S+/i,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
const plugin = {
|
|
25
|
+
name: "bilt-plugin-docker",
|
|
26
|
+
version: "1.0.0",
|
|
27
|
+
description: "Scans Dockerfiles for hardcoded secrets and validates .dockerignore configuration.",
|
|
28
|
+
async check(context) {
|
|
29
|
+
const findings = [];
|
|
30
|
+
// ── Scan Dockerfiles for ENV with hardcoded secrets ────────────────
|
|
31
|
+
const dockerfiles = context.files.filter((f) => path.basename(f) === "Dockerfile" ||
|
|
32
|
+
path.basename(f).startsWith("Dockerfile.") ||
|
|
33
|
+
f.endsWith(".dockerfile"));
|
|
34
|
+
for (const dockerFile of dockerfiles) {
|
|
35
|
+
const fullPath = path.join(context.rootDir, dockerFile);
|
|
36
|
+
let content;
|
|
37
|
+
try {
|
|
38
|
+
content = await fs.readFile(fullPath, "utf-8");
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const lines = content.split("\n");
|
|
44
|
+
for (let i = 0; i < lines.length; i++) {
|
|
45
|
+
const line = lines[i];
|
|
46
|
+
const trimmed = line.trim();
|
|
47
|
+
// Skip comments and empty lines
|
|
48
|
+
if (trimmed.startsWith("#") || trimmed === "")
|
|
49
|
+
continue;
|
|
50
|
+
// Skip ARG directives that use build-args (safe pattern)
|
|
51
|
+
if (/^ARG\s/i.test(trimmed))
|
|
52
|
+
continue;
|
|
53
|
+
for (const { name, pattern } of SECRET_PATTERNS) {
|
|
54
|
+
if (pattern.test(trimmed)) {
|
|
55
|
+
findings.push({
|
|
56
|
+
id: `docker-env-secret-${dockerFile}-${i + 1}`,
|
|
57
|
+
severity: "critical",
|
|
58
|
+
category: "plugin-finding",
|
|
59
|
+
message: `Hardcoded ${name} found in Dockerfile ENV directive`,
|
|
60
|
+
file: dockerFile,
|
|
61
|
+
line: i + 1,
|
|
62
|
+
suggestion: "Use ARG with --build-arg or mount secrets at runtime instead of hardcoding in ENV.",
|
|
63
|
+
});
|
|
64
|
+
break; // One finding per line
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// ── Check .dockerignore exists and excludes .env ──────────────────
|
|
70
|
+
if (dockerfiles.length > 0) {
|
|
71
|
+
const dockerignorePath = path.join(context.rootDir, ".dockerignore");
|
|
72
|
+
try {
|
|
73
|
+
const content = await fs.readFile(dockerignorePath, "utf-8");
|
|
74
|
+
const lines = content
|
|
75
|
+
.split("\n")
|
|
76
|
+
.map((l) => l.trim())
|
|
77
|
+
.filter((l) => l !== "" && !l.startsWith("#"));
|
|
78
|
+
const coversEnv = lines.some((l) => l === ".env" ||
|
|
79
|
+
l === ".env*" ||
|
|
80
|
+
l === ".env.*" ||
|
|
81
|
+
l === "*.env" ||
|
|
82
|
+
l === ".env.local" ||
|
|
83
|
+
l === ".env.production");
|
|
84
|
+
if (!coversEnv) {
|
|
85
|
+
findings.push({
|
|
86
|
+
id: "docker-dockerignore-env",
|
|
87
|
+
severity: "warning",
|
|
88
|
+
category: "plugin-finding",
|
|
89
|
+
message: ".dockerignore exists but does not exclude .env files — secrets may be copied into image",
|
|
90
|
+
file: ".dockerignore",
|
|
91
|
+
suggestion: "Add `.env*` to your .dockerignore to prevent copying secrets into the Docker image.",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// .dockerignore doesn't exist
|
|
97
|
+
findings.push({
|
|
98
|
+
id: "docker-no-dockerignore",
|
|
99
|
+
severity: "warning",
|
|
100
|
+
category: "plugin-finding",
|
|
101
|
+
message: "No .dockerignore found — .env files may be copied into Docker image during build",
|
|
102
|
+
file: dockerfiles[0],
|
|
103
|
+
suggestion: "Create a .dockerignore file and add `.env*` to prevent secrets from entering your image.",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { findings };
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
export default plugin;
|
|
111
|
+
//# sourceMappingURL=docker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../../../src/plugins/official/docker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terraform.d.ts","sourceRoot":"","sources":["../../../src/plugins/official/terraform.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,sBAAsB,CAAC;AAwC9B,QAAA,MAAM,MAAM,EAAE,cA0Hb,CAAC;eAEa,MAAM"}
|