bandit-cli 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/README.md +1 -0
- package/dist/cli/api.js +374 -0
- package/dist/cli/bench.js +135 -0
- package/dist/cli/doctor.js +231 -0
- package/dist/cli/env.js +108 -0
- package/dist/cli/output.js +138 -0
- package/dist/cli/ports.js +177 -0
- package/dist/core/auditor.js +32 -0
- package/dist/core/context.js +7 -0
- package/dist/core/types.js +1 -0
- package/dist/index.js +92 -0
- package/dist/rules/index.js +28 -0
- package/dist/rules/mvp.rules.js +138 -0
- package/dist/rules/phase2.rules.js +233 -0
- package/dist/rules/phase3.rules.js +261 -0
- package/dist/utils/codeScanner.js +66 -0
- package/dist/utils/fs.js +13 -0
- package/dist/utils/packageJson.js +54 -0
- package/package.json +38 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { runAudit } from "./core/auditor.js";
|
|
4
|
+
import { printHuman } from "./cli/output.js";
|
|
5
|
+
const program = new Command();
|
|
6
|
+
program
|
|
7
|
+
.name("bandit")
|
|
8
|
+
.description("The Swiss Army Knife for Backend Developers");
|
|
9
|
+
// Subcommand: audit (original behavior)
|
|
10
|
+
program
|
|
11
|
+
.command("audit")
|
|
12
|
+
.description("Audit a backend project directory for basic best-practices")
|
|
13
|
+
.argument("[path]", "Path to the project", ".")
|
|
14
|
+
.option("--json", "Print results as JSON")
|
|
15
|
+
.option("--fail-on-warn", "Exit with code 1 if warnings exist")
|
|
16
|
+
.action(async (projectPath, opts) => {
|
|
17
|
+
const results = await runAudit(projectPath);
|
|
18
|
+
if (opts.json) {
|
|
19
|
+
process.stdout.write(JSON.stringify(results, null, 2) + "\n");
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
printHuman(results);
|
|
23
|
+
}
|
|
24
|
+
const hasErrors = results.items.some((r) => r.status === "fail" && r.severity === "error");
|
|
25
|
+
const hasWarnings = results.items.some((r) => r.status === "fail" && r.severity === "warn");
|
|
26
|
+
if (hasErrors)
|
|
27
|
+
process.exit(1);
|
|
28
|
+
if (opts.failOnWarn && hasWarnings)
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
|
31
|
+
// Subcommand: ports
|
|
32
|
+
program
|
|
33
|
+
.command("ports")
|
|
34
|
+
.description("Scan active listening ports and interactively terminate locked processes")
|
|
35
|
+
.action(async () => {
|
|
36
|
+
const { runPortsCommand } = await import("./cli/ports.js");
|
|
37
|
+
await runPortsCommand();
|
|
38
|
+
});
|
|
39
|
+
// Subcommand: env
|
|
40
|
+
program
|
|
41
|
+
.command("env")
|
|
42
|
+
.description("Audit local .env configurations against .env.example")
|
|
43
|
+
.argument("[path]", "Path to the project", ".")
|
|
44
|
+
.action(async (projectPath) => {
|
|
45
|
+
const { runEnvCommand } = await import("./cli/env.js");
|
|
46
|
+
await runEnvCommand(projectPath);
|
|
47
|
+
});
|
|
48
|
+
// Subcommand: doctor
|
|
49
|
+
program
|
|
50
|
+
.command("doctor")
|
|
51
|
+
.description("Actively audit a running local server for header security, error leakage, and payload crashes")
|
|
52
|
+
.argument("[url]", "URL of the running server", "http://localhost:3000")
|
|
53
|
+
.action(async (url) => {
|
|
54
|
+
const { runDoctorCommand } = await import("./cli/doctor.js");
|
|
55
|
+
await runDoctorCommand(url);
|
|
56
|
+
});
|
|
57
|
+
// Subcommand: api
|
|
58
|
+
program
|
|
59
|
+
.command("api")
|
|
60
|
+
.description("Interactive API playfield: scan codebase routes and send test requests")
|
|
61
|
+
.argument("[path]", "Path to the project", ".")
|
|
62
|
+
.action(async (projectPath) => {
|
|
63
|
+
const { runApiCommand } = await import("./cli/api.js");
|
|
64
|
+
await runApiCommand(projectPath);
|
|
65
|
+
});
|
|
66
|
+
// Subcommand: bench
|
|
67
|
+
program
|
|
68
|
+
.command("bench")
|
|
69
|
+
.description("Benchmark an HTTP endpoint with concurrent connections load testing")
|
|
70
|
+
.argument("<url>", "Target URL to load test")
|
|
71
|
+
.option("-c, --connections <number>", "Number of concurrent connections", "10")
|
|
72
|
+
.option("-r, --requests <number>", "Total number of requests to execute", "200")
|
|
73
|
+
.action(async (url, opts) => {
|
|
74
|
+
const connections = parseInt(opts.connections, 10);
|
|
75
|
+
const requests = parseInt(opts.requests, 10);
|
|
76
|
+
if (isNaN(connections) || connections <= 0) {
|
|
77
|
+
console.error("Error: connections must be a positive integer.");
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
if (isNaN(requests) || requests <= 0) {
|
|
81
|
+
console.error("Error: requests must be a positive integer.");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const { runBenchCommand } = await import("./cli/bench.js");
|
|
85
|
+
await runBenchCommand(url, { connections, requests });
|
|
86
|
+
});
|
|
87
|
+
// Default to help if no command specified
|
|
88
|
+
if (!process.argv.slice(2).length) {
|
|
89
|
+
program.outputHelp();
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { rulePackageJsonExists } from "./mvp.rules.js";
|
|
2
|
+
import { ruleEnvExists } from "./mvp.rules.js";
|
|
3
|
+
import { ruleEnvExampleExists } from "./mvp.rules.js";
|
|
4
|
+
import { ruleEnvInGitignore } from "./mvp.rules.js";
|
|
5
|
+
import { ruleDockerfileExists } from "./mvp.rules.js";
|
|
6
|
+
import { ruleHasTestScript } from "./mvp.rules.js";
|
|
7
|
+
import { ruleSrcFolderExists, ruleDetectFramework, ruleSecurityDeps, ruleGlobalErrorHandler, } from "./phase2.rules.js";
|
|
8
|
+
import { ruleLoggingSetup, ruleEnvValidation, ruleDatabaseSetup, ruleTypeScriptStrict, ruleProductionDeps, } from "./phase3.rules.js";
|
|
9
|
+
export const rules = [
|
|
10
|
+
// Phase 1 - MVP checks
|
|
11
|
+
rulePackageJsonExists,
|
|
12
|
+
ruleEnvExists,
|
|
13
|
+
ruleEnvExampleExists,
|
|
14
|
+
ruleEnvInGitignore,
|
|
15
|
+
ruleDockerfileExists,
|
|
16
|
+
ruleHasTestScript,
|
|
17
|
+
// Phase 2 - Structure + detection + security
|
|
18
|
+
ruleSrcFolderExists,
|
|
19
|
+
ruleDetectFramework,
|
|
20
|
+
ruleSecurityDeps,
|
|
21
|
+
ruleGlobalErrorHandler,
|
|
22
|
+
// Phase 3 - Production readiness
|
|
23
|
+
ruleLoggingSetup,
|
|
24
|
+
ruleEnvValidation,
|
|
25
|
+
ruleDatabaseSetup,
|
|
26
|
+
ruleTypeScriptStrict,
|
|
27
|
+
ruleProductionDeps,
|
|
28
|
+
];
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// File: src/rules/mvp.rules.ts
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { exists, readText } from "../utils/fs.js";
|
|
4
|
+
import { readPackageJson, hasScript } from "../utils/packageJson.js";
|
|
5
|
+
export const rulePackageJsonExists = {
|
|
6
|
+
id: "pkg-json-exists",
|
|
7
|
+
title: "package.json exists",
|
|
8
|
+
severity: "error",
|
|
9
|
+
async run(ctx) {
|
|
10
|
+
const ok = exists(ctx.packageJsonPath);
|
|
11
|
+
return {
|
|
12
|
+
id: this.id,
|
|
13
|
+
title: this.title,
|
|
14
|
+
severity: this.severity,
|
|
15
|
+
status: ok ? "pass" : "fail",
|
|
16
|
+
details: ok ? undefined : "package.json not found.",
|
|
17
|
+
suggestion: ok ? undefined : "Run `npm init -y` in the project root.",
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
export const ruleEnvExists = {
|
|
22
|
+
id: "env-exists",
|
|
23
|
+
title: ".env exists",
|
|
24
|
+
severity: "warn",
|
|
25
|
+
async run(ctx) {
|
|
26
|
+
const envPath = path.join(ctx.projectPath, ".env");
|
|
27
|
+
const ok = exists(envPath);
|
|
28
|
+
return {
|
|
29
|
+
id: this.id,
|
|
30
|
+
title: this.title,
|
|
31
|
+
severity: this.severity,
|
|
32
|
+
status: ok ? "pass" : "fail",
|
|
33
|
+
details: ok ? undefined : ".env not found.",
|
|
34
|
+
suggestion: ok ? undefined : "Create a local .env file for development.",
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export const ruleEnvExampleExists = {
|
|
39
|
+
id: "env-example-exists",
|
|
40
|
+
title: ".env.example exists",
|
|
41
|
+
severity: "info",
|
|
42
|
+
async run(ctx) {
|
|
43
|
+
const envExamplePath = path.join(ctx.projectPath, ".env.example");
|
|
44
|
+
const ok = exists(envExamplePath);
|
|
45
|
+
return {
|
|
46
|
+
id: this.id,
|
|
47
|
+
title: this.title,
|
|
48
|
+
severity: this.severity,
|
|
49
|
+
status: ok ? "pass" : "fail",
|
|
50
|
+
details: ok ? undefined : ".env.example not found.",
|
|
51
|
+
suggestion: ok
|
|
52
|
+
? undefined
|
|
53
|
+
: "Create .env.example listing all required env keys (without real secrets).",
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
export const ruleEnvInGitignore = {
|
|
58
|
+
id: "env-in-gitignore",
|
|
59
|
+
title: ".env is ignored in .gitignore",
|
|
60
|
+
severity: "error",
|
|
61
|
+
async run(ctx) {
|
|
62
|
+
const gitignorePath = path.join(ctx.projectPath, ".gitignore");
|
|
63
|
+
const gitignore = readText(gitignorePath);
|
|
64
|
+
if (!gitignore) {
|
|
65
|
+
return {
|
|
66
|
+
id: this.id,
|
|
67
|
+
title: this.title,
|
|
68
|
+
severity: this.severity,
|
|
69
|
+
status: "fail",
|
|
70
|
+
details: ".gitignore not found.",
|
|
71
|
+
suggestion: "Create a .gitignore and add `.env` to it.",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const lines = gitignore
|
|
75
|
+
.split("\n")
|
|
76
|
+
.map((l) => l.trim())
|
|
77
|
+
.filter((l) => l && !l.startsWith("#"));
|
|
78
|
+
const ok = lines.includes(".env") || lines.includes(".env*");
|
|
79
|
+
return {
|
|
80
|
+
id: this.id,
|
|
81
|
+
title: this.title,
|
|
82
|
+
severity: this.severity,
|
|
83
|
+
status: ok ? "pass" : "fail",
|
|
84
|
+
details: ok ? undefined : "`.env` is not ignored.",
|
|
85
|
+
suggestion: ok
|
|
86
|
+
? undefined
|
|
87
|
+
: "Add `.env` to .gitignore to avoid leaking secrets.",
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
export const ruleDockerfileExists = {
|
|
92
|
+
id: "dockerfile-exists",
|
|
93
|
+
title: "Dockerfile exists",
|
|
94
|
+
severity: "info",
|
|
95
|
+
async run(ctx) {
|
|
96
|
+
const dockerfilePath = path.join(ctx.projectPath, "Dockerfile");
|
|
97
|
+
const ok = exists(dockerfilePath);
|
|
98
|
+
return {
|
|
99
|
+
id: this.id,
|
|
100
|
+
title: this.title,
|
|
101
|
+
severity: this.severity,
|
|
102
|
+
status: ok ? "pass" : "fail",
|
|
103
|
+
details: ok ? undefined : "Dockerfile not found.",
|
|
104
|
+
suggestion: ok
|
|
105
|
+
? undefined
|
|
106
|
+
: "If you plan to containerize this backend, add a Dockerfile.",
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
export const ruleHasTestScript = {
|
|
111
|
+
id: "test-script-exists",
|
|
112
|
+
title: "package.json contains a test script",
|
|
113
|
+
severity: "warn",
|
|
114
|
+
async run(ctx) {
|
|
115
|
+
const pkg = readPackageJson(ctx.packageJsonPath);
|
|
116
|
+
if (!pkg) {
|
|
117
|
+
return {
|
|
118
|
+
id: this.id,
|
|
119
|
+
title: this.title,
|
|
120
|
+
severity: this.severity,
|
|
121
|
+
status: "skip",
|
|
122
|
+
details: "Could not read/parse package.json.",
|
|
123
|
+
suggestion: "Fix package.json JSON syntax.",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const ok = hasScript(pkg, "test");
|
|
127
|
+
return {
|
|
128
|
+
id: this.id,
|
|
129
|
+
title: this.title,
|
|
130
|
+
severity: this.severity,
|
|
131
|
+
status: ok ? "pass" : "fail",
|
|
132
|
+
details: ok ? undefined : "No `test` script found.",
|
|
133
|
+
suggestion: ok
|
|
134
|
+
? undefined
|
|
135
|
+
: "Add a test runner (jest/vitest) and a `test` script.",
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// File: src/rules/phase2.rules.ts
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { exists } from "../utils/fs.js";
|
|
4
|
+
import { readPackageJson, hasDependency, detectFramework, } from "../utils/packageJson.js";
|
|
5
|
+
import { scanForErrorHandler } from "../utils/codeScanner.js";
|
|
6
|
+
// Rule 7: src/ folder exists
|
|
7
|
+
export const ruleSrcFolderExists = {
|
|
8
|
+
id: "src-folder-exists",
|
|
9
|
+
title: "src/ folder exists",
|
|
10
|
+
severity: "warn",
|
|
11
|
+
async run(ctx) {
|
|
12
|
+
const srcPath = path.join(ctx.projectPath, "src");
|
|
13
|
+
const ok = exists(srcPath);
|
|
14
|
+
return {
|
|
15
|
+
id: this.id,
|
|
16
|
+
title: this.title,
|
|
17
|
+
severity: this.severity,
|
|
18
|
+
status: ok ? "pass" : "fail",
|
|
19
|
+
details: ok ? undefined : "src/ folder not found.",
|
|
20
|
+
suggestion: ok
|
|
21
|
+
? undefined
|
|
22
|
+
: "Create a src/ folder to organize your backend code.",
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
// Rule 8: Detect backend framework
|
|
27
|
+
export const ruleDetectFramework = {
|
|
28
|
+
id: "detect-framework",
|
|
29
|
+
title: "Detect backend framework",
|
|
30
|
+
severity: "info",
|
|
31
|
+
async run(ctx) {
|
|
32
|
+
const pkg = readPackageJson(ctx.packageJsonPath);
|
|
33
|
+
if (!pkg) {
|
|
34
|
+
return {
|
|
35
|
+
id: this.id,
|
|
36
|
+
title: this.title,
|
|
37
|
+
severity: this.severity,
|
|
38
|
+
status: "skip",
|
|
39
|
+
details: "Could not read package.json.",
|
|
40
|
+
suggestion: "Fix package.json JSON syntax.",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const framework = detectFramework(pkg);
|
|
44
|
+
// Update context with detected framework for subsequent rules
|
|
45
|
+
ctx.framework = framework;
|
|
46
|
+
if (framework === "unknown") {
|
|
47
|
+
return {
|
|
48
|
+
id: this.id,
|
|
49
|
+
title: this.title,
|
|
50
|
+
severity: this.severity,
|
|
51
|
+
status: "fail",
|
|
52
|
+
details: "No known backend framework detected.",
|
|
53
|
+
suggestion: "Consider using a popular framework like Express, Fastify, NestJS, or Hono.",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
id: this.id,
|
|
58
|
+
title: this.title,
|
|
59
|
+
severity: this.severity,
|
|
60
|
+
status: "pass",
|
|
61
|
+
details: `Detected framework: ${framework}`,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
// Rule 9: Security dependencies check (framework-dependent)
|
|
66
|
+
export const ruleSecurityDeps = {
|
|
67
|
+
id: "security-deps",
|
|
68
|
+
title: "Security dependencies check",
|
|
69
|
+
severity: "warn",
|
|
70
|
+
async run(ctx) {
|
|
71
|
+
const pkg = readPackageJson(ctx.packageJsonPath);
|
|
72
|
+
if (!pkg) {
|
|
73
|
+
return {
|
|
74
|
+
id: this.id,
|
|
75
|
+
title: this.title,
|
|
76
|
+
severity: this.severity,
|
|
77
|
+
status: "skip",
|
|
78
|
+
details: "Could not read package.json.",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const framework = ctx.framework || detectFramework(pkg);
|
|
82
|
+
// For Express, check helmet and cors
|
|
83
|
+
if (framework === "express") {
|
|
84
|
+
const hasHelmet = hasDependency(pkg, "helmet");
|
|
85
|
+
const hasCors = hasDependency(pkg, "cors");
|
|
86
|
+
if (!hasHelmet && !hasCors) {
|
|
87
|
+
return {
|
|
88
|
+
id: this.id,
|
|
89
|
+
title: this.title,
|
|
90
|
+
severity: this.severity,
|
|
91
|
+
status: "fail",
|
|
92
|
+
details: "Missing security dependencies: helmet and cors not found.",
|
|
93
|
+
suggestion: "Install: `npm install helmet cors`",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (!hasHelmet) {
|
|
97
|
+
return {
|
|
98
|
+
id: this.id,
|
|
99
|
+
title: this.title,
|
|
100
|
+
severity: this.severity,
|
|
101
|
+
status: "fail",
|
|
102
|
+
details: "helmet is not installed.",
|
|
103
|
+
suggestion: "Install: `npm install helmet`",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (!hasCors) {
|
|
107
|
+
return {
|
|
108
|
+
id: this.id,
|
|
109
|
+
title: this.title,
|
|
110
|
+
severity: this.severity,
|
|
111
|
+
status: "fail",
|
|
112
|
+
details: "cors is not installed.",
|
|
113
|
+
suggestion: "Install: `npm install cors`",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
id: this.id,
|
|
118
|
+
title: this.title,
|
|
119
|
+
severity: this.severity,
|
|
120
|
+
status: "pass",
|
|
121
|
+
details: "helmet and cors are installed.",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// For Fastify
|
|
125
|
+
if (framework === "fastify") {
|
|
126
|
+
const hasHelmet = hasDependency(pkg, "@fastify/helmet");
|
|
127
|
+
const hasCors = hasDependency(pkg, "@fastify/cors");
|
|
128
|
+
if (!hasHelmet && !hasCors) {
|
|
129
|
+
return {
|
|
130
|
+
id: this.id,
|
|
131
|
+
title: this.title,
|
|
132
|
+
severity: this.severity,
|
|
133
|
+
status: "fail",
|
|
134
|
+
details: "Missing security dependencies: @fastify/helmet and @fastify/cors not found.",
|
|
135
|
+
suggestion: "Install: `npm install @fastify/helmet @fastify/cors`",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (!hasHelmet) {
|
|
139
|
+
return {
|
|
140
|
+
id: this.id,
|
|
141
|
+
title: this.title,
|
|
142
|
+
severity: this.severity,
|
|
143
|
+
status: "fail",
|
|
144
|
+
details: "@fastify/helmet is not installed.",
|
|
145
|
+
suggestion: "Install: `npm install @fastify/helmet`",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (!hasCors) {
|
|
149
|
+
return {
|
|
150
|
+
id: this.id,
|
|
151
|
+
title: this.title,
|
|
152
|
+
severity: this.severity,
|
|
153
|
+
status: "fail",
|
|
154
|
+
details: "@fastify/cors is not installed.",
|
|
155
|
+
suggestion: "Install: `npm install @fastify/cors`",
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
id: this.id,
|
|
160
|
+
title: this.title,
|
|
161
|
+
severity: this.severity,
|
|
162
|
+
status: "pass",
|
|
163
|
+
details: "@fastify/helmet and @fastify/cors are installed.",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// For NestJS, security is built-in but can check helmet
|
|
167
|
+
if (framework === "nest") {
|
|
168
|
+
return {
|
|
169
|
+
id: this.id,
|
|
170
|
+
title: this.title,
|
|
171
|
+
severity: this.severity,
|
|
172
|
+
status: "pass",
|
|
173
|
+
details: "NestJS has built-in security features. Consider adding helmet.",
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
// For other frameworks or unknown
|
|
177
|
+
return {
|
|
178
|
+
id: this.id,
|
|
179
|
+
title: this.title,
|
|
180
|
+
severity: this.severity,
|
|
181
|
+
status: "skip",
|
|
182
|
+
details: `Security checks not defined for framework: ${framework}`,
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
// Rule 10: Global error handler present
|
|
187
|
+
export const ruleGlobalErrorHandler = {
|
|
188
|
+
id: "global-error-handler",
|
|
189
|
+
title: "Global error handler present",
|
|
190
|
+
severity: "warn",
|
|
191
|
+
async run(ctx) {
|
|
192
|
+
const srcPath = path.join(ctx.projectPath, "src");
|
|
193
|
+
if (!exists(srcPath)) {
|
|
194
|
+
return {
|
|
195
|
+
id: this.id,
|
|
196
|
+
title: this.title,
|
|
197
|
+
severity: this.severity,
|
|
198
|
+
status: "skip",
|
|
199
|
+
details: "src/ folder not found, skipping code scan.",
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const framework = ctx.framework || "unknown";
|
|
203
|
+
// For Express, look for error handler middleware pattern
|
|
204
|
+
if (framework === "express") {
|
|
205
|
+
const found = await scanForErrorHandler(srcPath);
|
|
206
|
+
if (!found) {
|
|
207
|
+
return {
|
|
208
|
+
id: this.id,
|
|
209
|
+
title: this.title,
|
|
210
|
+
severity: this.severity,
|
|
211
|
+
status: "fail",
|
|
212
|
+
details: "No global error handler middleware found.",
|
|
213
|
+
suggestion: "Add app.use((err, req, res, next) => {...}) after all routes.",
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
id: this.id,
|
|
218
|
+
title: this.title,
|
|
219
|
+
severity: this.severity,
|
|
220
|
+
status: "pass",
|
|
221
|
+
details: "Global error handler detected.",
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
// For other frameworks, skip for now
|
|
225
|
+
return {
|
|
226
|
+
id: this.id,
|
|
227
|
+
title: this.title,
|
|
228
|
+
severity: this.severity,
|
|
229
|
+
status: "skip",
|
|
230
|
+
details: `Error handler check not implemented for framework: ${framework}`,
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
};
|