safelaunch 1.0.32 → 1.0.34

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/scan.js +54 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safelaunch",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
4
4
  "description": "Backend Reliability Infrastructure - catch what breaks production before it breaks",
5
5
  "main": "index.js",
6
6
  "bin": {
package/src/scan.js CHANGED
@@ -63,11 +63,31 @@ 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
+ ENV_NOT_GITIGNORED: () => ({
67
+ title: ".env is not in .gitignore",
68
+ impact: "Your .env file could be committed to git, exposing secrets to anyone with repo access.",
69
+ fix: "Add .env to your .gitignore file immediately.",
70
+ }),
71
+ NODE_MODULES_STALE: () => ({
72
+ title: "node_modules may be out of date",
73
+ impact: "package.json was modified after node_modules was last updated. You may be running old or missing dependencies.",
74
+ fix: "Run npm install to sync your dependencies.",
75
+ }),
66
76
  MISSING_NODE_MODULES: () => ({
67
77
  title: "node_modules is missing",
68
78
  impact: "The app won't start. Nothing is installed.",
69
79
  fix: "Run npm install.",
70
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
+ }),
86
+ AUDIT_HIGH: (count) => ({
87
+ title: `${count} high-severity vulnerability${count > 1 ? "ies" : "y"} in dependencies`,
88
+ impact: "Known vulnerabilities exist that could be exploited. These should be fixed before deploying.",
89
+ fix: "Run npm audit fix or check npm audit for manual fixes.",
90
+ }),
71
91
  AUDIT_CRITICAL: (count) => ({
72
92
  title: `${count} critical vulnerability${count > 1 ? "ies" : "y"} in dependencies`,
73
93
  impact: "Known exploits exist for these packages. Shipping them puts your users and infrastructure at risk.",
@@ -200,12 +220,21 @@ function checkNodeModules(cwd) {
200
220
  if (!fileExists(pkgPath)) return issues;
201
221
  try {
202
222
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
203
- const deps = Object.keys(pkg.dependencies || {});
223
+ const deps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
204
224
  if (deps.length === 0) return issues;
205
225
  } catch { return issues; }
206
- if (!fileExists(path.join(cwd, "node_modules"))) {
226
+ const nmPath = path.join(cwd, "node_modules");
227
+ if (!fileExists(nmPath)) {
207
228
  issues.push({ severity: "block", ...IMPACTS.MISSING_NODE_MODULES() });
229
+ return issues;
208
230
  }
231
+ try {
232
+ const pkgStat = fs.statSync(pkgPath);
233
+ const nmStat = fs.statSync(nmPath);
234
+ if (pkgStat.mtimeMs > nmStat.mtimeMs) {
235
+ issues.push({ severity: "warn", ...IMPACTS.NODE_MODULES_STALE() });
236
+ }
237
+ } catch {}
209
238
  return issues;
210
239
  }
211
240
 
@@ -213,12 +242,15 @@ function checkNpmAudit(cwd) {
213
242
  const issues = [];
214
243
  if (!fileExists(path.join(cwd, "package.json"))) return issues;
215
244
  try {
216
- execSync("npm audit --audit-level=critical --json", { cwd, encoding: "utf8", stdio: ["pipe","pipe","pipe"] });
245
+ execSync("npm audit --json", { cwd, encoding: "utf8", stdio: ["pipe","pipe","pipe"] });
217
246
  } catch (e) {
218
247
  try {
219
248
  const data = JSON.parse(e.stdout || "");
220
- const critCount = (data.metadata && data.metadata.vulnerabilities && data.metadata.vulnerabilities.critical) || 0;
221
- if (critCount > 0) issues.push({ severity: "warn", ...IMPACTS.AUDIT_CRITICAL(critCount) });
249
+ const vulns = (data.metadata && data.metadata.vulnerabilities) || {};
250
+ const critCount = vulns.critical || 0;
251
+ const highCount = vulns.high || 0;
252
+ if (critCount > 0) issues.push({ severity: "block", ...IMPACTS.AUDIT_CRITICAL(critCount) });
253
+ if (highCount > 0) issues.push({ severity: "warn", ...IMPACTS.AUDIT_HIGH(highCount) });
222
254
  } catch {}
223
255
  }
224
256
  return issues;
@@ -367,6 +399,22 @@ function renderInteractiveOutput(blockers, warnings, infos, elapsed) {
367
399
  return lines.join("\n");
368
400
  }
369
401
 
402
+
403
+ function checkEnvGitignored(cwd) {
404
+ const issues = [];
405
+ if (!fileExists(path.join(cwd, ".env"))) return issues;
406
+ const gitignore = readFileSafe(path.join(cwd, ".gitignore"));
407
+ if (!gitignore) {
408
+ issues.push({ severity: "block", ...IMPACTS.ENV_NOT_GITIGNORED() });
409
+ return issues;
410
+ }
411
+ const lines = gitignore.split("\n").map(l => l.trim());
412
+ const ignored = lines.some(l => l === ".env" || l === "*.env" || l === ".env*");
413
+ if (!ignored) {
414
+ issues.push({ severity: "block", ...IMPACTS.ENV_NOT_GITIGNORED() });
415
+ }
416
+ return issues;
417
+ }
370
418
  async function runScan(options = {}) {
371
419
  const { hookMode = false, quiet = false, cwd = process.cwd() } = options;
372
420
  const start = Date.now();
@@ -381,6 +429,7 @@ async function runScan(options = {}) {
381
429
  ...checkLockfileSync(cwd),
382
430
  ...checkTypeScript(cwd),
383
431
  ...checkNpmAudit(cwd),
432
+ ...checkEnvGitignored(cwd),
384
433
  ...checkNodeVersion(cwd),
385
434
  ...checkPythonVersion(cwd),
386
435
  ];