safelaunch 1.0.31 → 1.0.33
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/package.json +1 -1
- package/src/scan.js +96 -11
package/package.json
CHANGED
package/src/scan.js
CHANGED
|
@@ -63,16 +63,41 @@ const IMPACTS = {
|
|
|
63
63
|
impact: "Different tools read different copies. Behaviour is unpredictable — one service may use the wrong value.",
|
|
64
64
|
fix: `Remove the duplicate ${name} entry from your .env file.`,
|
|
65
65
|
}),
|
|
66
|
+
NODE_MODULES_STALE: () => ({
|
|
67
|
+
title: "node_modules may be out of date",
|
|
68
|
+
impact: "package.json was modified after node_modules was last updated. You may be running old or missing dependencies.",
|
|
69
|
+
fix: "Run npm install to sync your dependencies.",
|
|
70
|
+
}),
|
|
66
71
|
MISSING_NODE_MODULES: () => ({
|
|
67
72
|
title: "node_modules is missing",
|
|
68
73
|
impact: "The app won't start. Nothing is installed.",
|
|
69
74
|
fix: "Run npm install.",
|
|
70
75
|
}),
|
|
76
|
+
AUDIT_HIGH: (count) => ({
|
|
77
|
+
title: `${count} high-severity vulnerability${count > 1 ? "ies" : "y"} in dependencies`,
|
|
78
|
+
impact: "Known vulnerabilities exist that could be exploited. These should be fixed before deploying.",
|
|
79
|
+
fix: "Run npm audit fix or check npm audit for manual fixes.",
|
|
80
|
+
}),
|
|
81
|
+
AUDIT_HIGH: (count) => ({
|
|
82
|
+
title: `${count} high-severity vulnerability${count > 1 ? "ies" : "y"} in dependencies`,
|
|
83
|
+
impact: "Known vulnerabilities exist that could be exploited. These should be fixed before deploying.",
|
|
84
|
+
fix: "Run npm audit fix or check npm audit for manual fixes.",
|
|
85
|
+
}),
|
|
71
86
|
AUDIT_CRITICAL: (count) => ({
|
|
72
87
|
title: `${count} critical vulnerability${count > 1 ? "ies" : "y"} in dependencies`,
|
|
73
88
|
impact: "Known exploits exist for these packages. Shipping them puts your users and infrastructure at risk.",
|
|
74
89
|
fix: "Run npm audit fix or check npm audit for manual fixes.",
|
|
75
90
|
}),
|
|
91
|
+
PYTHON_MISMATCH: (expected, actual) => ({
|
|
92
|
+
title: `Python version mismatch (expected: ${expected}, running: ${actual})`,
|
|
93
|
+
impact: "Code that works locally may fail in CI or production if they use the expected version.",
|
|
94
|
+
fix: `Switch to Python ${expected} or update .python-version to match your runtime.`,
|
|
95
|
+
}),
|
|
96
|
+
NODE_ENGINES_MISMATCH: (expected, actual) => ({
|
|
97
|
+
title: `Node version mismatch (package.json engines: ${expected}, running: ${actual})`,
|
|
98
|
+
impact: "Your package.json declares a required Node version that differs from what is running.",
|
|
99
|
+
fix: `Switch to Node ${expected} or update the engines field in package.json.`,
|
|
100
|
+
}),
|
|
76
101
|
NODE_MISMATCH: (expected, actual) => ({
|
|
77
102
|
title: `Node version mismatch (.nvmrc: ${expected}, running: ${actual})`,
|
|
78
103
|
impact: "Code that works locally may fail in CI or production if they use the expected version.",
|
|
@@ -190,12 +215,21 @@ function checkNodeModules(cwd) {
|
|
|
190
215
|
if (!fileExists(pkgPath)) return issues;
|
|
191
216
|
try {
|
|
192
217
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
193
|
-
const deps = Object.keys(pkg.dependencies
|
|
218
|
+
const deps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
|
|
194
219
|
if (deps.length === 0) return issues;
|
|
195
220
|
} catch { return issues; }
|
|
196
|
-
|
|
221
|
+
const nmPath = path.join(cwd, "node_modules");
|
|
222
|
+
if (!fileExists(nmPath)) {
|
|
197
223
|
issues.push({ severity: "block", ...IMPACTS.MISSING_NODE_MODULES() });
|
|
224
|
+
return issues;
|
|
198
225
|
}
|
|
226
|
+
try {
|
|
227
|
+
const pkgStat = fs.statSync(pkgPath);
|
|
228
|
+
const nmStat = fs.statSync(nmPath);
|
|
229
|
+
if (pkgStat.mtimeMs > nmStat.mtimeMs) {
|
|
230
|
+
issues.push({ severity: "warn", ...IMPACTS.NODE_MODULES_STALE() });
|
|
231
|
+
}
|
|
232
|
+
} catch {}
|
|
199
233
|
return issues;
|
|
200
234
|
}
|
|
201
235
|
|
|
@@ -203,12 +237,15 @@ function checkNpmAudit(cwd) {
|
|
|
203
237
|
const issues = [];
|
|
204
238
|
if (!fileExists(path.join(cwd, "package.json"))) return issues;
|
|
205
239
|
try {
|
|
206
|
-
execSync("npm audit --
|
|
240
|
+
execSync("npm audit --json", { cwd, encoding: "utf8", stdio: ["pipe","pipe","pipe"] });
|
|
207
241
|
} catch (e) {
|
|
208
242
|
try {
|
|
209
243
|
const data = JSON.parse(e.stdout || "");
|
|
210
|
-
const
|
|
211
|
-
|
|
244
|
+
const vulns = (data.metadata && data.metadata.vulnerabilities) || {};
|
|
245
|
+
const critCount = vulns.critical || 0;
|
|
246
|
+
const highCount = vulns.high || 0;
|
|
247
|
+
if (critCount > 0) issues.push({ severity: "block", ...IMPACTS.AUDIT_CRITICAL(critCount) });
|
|
248
|
+
if (highCount > 0) issues.push({ severity: "warn", ...IMPACTS.AUDIT_HIGH(highCount) });
|
|
212
249
|
} catch {}
|
|
213
250
|
}
|
|
214
251
|
return issues;
|
|
@@ -216,16 +253,63 @@ function checkNpmAudit(cwd) {
|
|
|
216
253
|
|
|
217
254
|
function checkNodeVersion(cwd) {
|
|
218
255
|
const issues = [];
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
if (
|
|
224
|
-
|
|
256
|
+
const actual = process.version.replace(/^v/, "");
|
|
257
|
+
|
|
258
|
+
// Check .nvmrc
|
|
259
|
+
const nvmrc = readFileSafe(path.join(cwd, ".nvmrc"));
|
|
260
|
+
if (nvmrc) {
|
|
261
|
+
const expected = nvmrc.trim().replace(/^v/, "");
|
|
262
|
+
if (expected.split(".")[0] !== actual.split(".")[0]) {
|
|
263
|
+
issues.push({ severity: "warn", ...IMPACTS.NODE_MISMATCH(expected, actual) });
|
|
264
|
+
}
|
|
225
265
|
}
|
|
266
|
+
|
|
267
|
+
// Check package.json engines.node
|
|
268
|
+
const pkgRaw = readFileSafe(path.join(cwd, "package.json"));
|
|
269
|
+
if (pkgRaw) {
|
|
270
|
+
try {
|
|
271
|
+
const pkg = JSON.parse(pkgRaw);
|
|
272
|
+
const enginesNode = pkg.engines && pkg.engines.node;
|
|
273
|
+
if (enginesNode) {
|
|
274
|
+
const match = enginesNode.match(/(\d+)/);
|
|
275
|
+
if (match && match[1] !== actual.split(".")[0]) {
|
|
276
|
+
issues.push({ severity: "warn", ...IMPACTS.NODE_ENGINES_MISMATCH(enginesNode, actual) });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} catch {}
|
|
280
|
+
}
|
|
281
|
+
|
|
226
282
|
return issues;
|
|
227
283
|
}
|
|
228
284
|
|
|
285
|
+
function checkPythonVersion(cwd) {
|
|
286
|
+
const issues = [];
|
|
287
|
+
const actual = exec("python3 --version") || exec("python --version");
|
|
288
|
+
if (!actual) return issues;
|
|
289
|
+
const actualVer = actual.replace(/^Python\s+/i, "").trim();
|
|
290
|
+
|
|
291
|
+
// Check .python-version
|
|
292
|
+
const pyVer = readFileSafe(path.join(cwd, ".python-version"));
|
|
293
|
+
if (pyVer) {
|
|
294
|
+
const expected = pyVer.trim();
|
|
295
|
+
if (expected.split(".")[0] !== actualVer.split(".")[0]) {
|
|
296
|
+
issues.push({ severity: "warn", ...IMPACTS.PYTHON_MISMATCH(expected, actualVer) });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check runtime.txt (Heroku/Render style)
|
|
301
|
+
const runtimeTxt = readFileSafe(path.join(cwd, "runtime.txt"));
|
|
302
|
+
if (runtimeTxt && runtimeTxt.startsWith("python-")) {
|
|
303
|
+
const expected = runtimeTxt.replace("python-", "").trim();
|
|
304
|
+
if (expected.split(".")[0] !== actualVer.split(".")[0]) {
|
|
305
|
+
issues.push({ severity: "warn", ...IMPACTS.PYTHON_MISMATCH(expected, actualVer) });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return issues;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
229
313
|
function renderHookOutput(blockers, warnings, infos, elapsed) {
|
|
230
314
|
const lines = [];
|
|
231
315
|
const hr = gray("─".repeat(49));
|
|
@@ -325,6 +409,7 @@ async function runScan(options = {}) {
|
|
|
325
409
|
...checkTypeScript(cwd),
|
|
326
410
|
...checkNpmAudit(cwd),
|
|
327
411
|
...checkNodeVersion(cwd),
|
|
412
|
+
...checkPythonVersion(cwd),
|
|
328
413
|
];
|
|
329
414
|
const blockers = allIssues.filter((i) => i.severity === "block");
|
|
330
415
|
const warnings = allIssues.filter((i) => i.severity === "warn");
|