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,261 @@
|
|
|
1
|
+
// File: src/rules/phase3.rules.ts
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { exists, readText } from "../utils/fs.js";
|
|
4
|
+
import { readPackageJson, hasDependency, getDependenciesInWrongScope, } from "../utils/packageJson.js";
|
|
5
|
+
import { scanForLoggingUsage } from "../utils/codeScanner.js";
|
|
6
|
+
// Rule 11: Logging setup detection
|
|
7
|
+
export const ruleLoggingSetup = {
|
|
8
|
+
id: "logging-setup",
|
|
9
|
+
title: "Logging setup detected",
|
|
10
|
+
severity: "warn",
|
|
11
|
+
async run(ctx) {
|
|
12
|
+
const pkg = readPackageJson(ctx.packageJsonPath);
|
|
13
|
+
if (!pkg) {
|
|
14
|
+
return {
|
|
15
|
+
id: this.id,
|
|
16
|
+
title: this.title,
|
|
17
|
+
severity: this.severity,
|
|
18
|
+
status: "skip",
|
|
19
|
+
details: "Could not read package.json.",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// Check for professional logging libraries
|
|
23
|
+
const loggingLibs = ["pino", "winston", "bunyan", "log4js"];
|
|
24
|
+
const detected = loggingLibs.filter((lib) => hasDependency(pkg, lib, true));
|
|
25
|
+
if (detected.length === 0) {
|
|
26
|
+
return {
|
|
27
|
+
id: this.id,
|
|
28
|
+
title: this.title,
|
|
29
|
+
severity: this.severity,
|
|
30
|
+
status: "fail",
|
|
31
|
+
details: "No professional logging library detected.",
|
|
32
|
+
suggestion: "Install a production-grade logger: `npm install pino` or `npm install winston`",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Optional: Check if logging is actually used in code
|
|
36
|
+
const srcPath = path.join(ctx.projectPath, "src");
|
|
37
|
+
if (exists(srcPath)) {
|
|
38
|
+
const isUsed = await scanForLoggingUsage(srcPath, detected);
|
|
39
|
+
if (!isUsed) {
|
|
40
|
+
return {
|
|
41
|
+
id: this.id,
|
|
42
|
+
title: this.title,
|
|
43
|
+
severity: this.severity,
|
|
44
|
+
status: "fail",
|
|
45
|
+
details: `Logging library installed (${detected.join(", ")}) but not imported/used in code.`,
|
|
46
|
+
suggestion: `Import and use ${detected[0]} in your application code.`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
id: this.id,
|
|
52
|
+
title: this.title,
|
|
53
|
+
severity: this.severity,
|
|
54
|
+
status: "pass",
|
|
55
|
+
details: `Detected logging: ${detected.join(", ")}`,
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
// Rule 12: Environment variable validation
|
|
60
|
+
export const ruleEnvValidation = {
|
|
61
|
+
id: "env-validation",
|
|
62
|
+
title: "Environment variable validation",
|
|
63
|
+
severity: "warn",
|
|
64
|
+
async run(ctx) {
|
|
65
|
+
const pkg = readPackageJson(ctx.packageJsonPath);
|
|
66
|
+
if (!pkg) {
|
|
67
|
+
return {
|
|
68
|
+
id: this.id,
|
|
69
|
+
title: this.title,
|
|
70
|
+
severity: this.severity,
|
|
71
|
+
status: "skip",
|
|
72
|
+
details: "Could not read package.json.",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Check for env validation libraries
|
|
76
|
+
const envValidationLibs = ["zod", "joi", "envsafe", "dotenv-safe", "yup"];
|
|
77
|
+
const detected = envValidationLibs.filter((lib) => hasDependency(pkg, lib, true));
|
|
78
|
+
if (detected.length === 0) {
|
|
79
|
+
return {
|
|
80
|
+
id: this.id,
|
|
81
|
+
title: this.title,
|
|
82
|
+
severity: this.severity,
|
|
83
|
+
status: "fail",
|
|
84
|
+
details: "No environment variable validation library detected.",
|
|
85
|
+
suggestion: "Add env validation to prevent runtime crashes:\n\t\t`npm install zod` and validate process.env at startup",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
id: this.id,
|
|
90
|
+
title: this.title,
|
|
91
|
+
severity: this.severity,
|
|
92
|
+
status: "pass",
|
|
93
|
+
details: `Detected env validation: ${detected.join(", ")}`,
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
// Rule 13: Database setup detection
|
|
98
|
+
export const ruleDatabaseSetup = {
|
|
99
|
+
id: "database-setup",
|
|
100
|
+
title: "Database setup detected",
|
|
101
|
+
severity: "info",
|
|
102
|
+
async run(ctx) {
|
|
103
|
+
const pkg = readPackageJson(ctx.packageJsonPath);
|
|
104
|
+
if (!pkg) {
|
|
105
|
+
return {
|
|
106
|
+
id: this.id,
|
|
107
|
+
title: this.title,
|
|
108
|
+
severity: this.severity,
|
|
109
|
+
status: "skip",
|
|
110
|
+
details: "Could not read package.json.",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Check for ORMs and database clients
|
|
114
|
+
const databases = [
|
|
115
|
+
{ name: "Prisma", pkg: "@prisma/client" },
|
|
116
|
+
{ name: "TypeORM", pkg: "typeorm" },
|
|
117
|
+
{ name: "Drizzle", pkg: "drizzle-orm" },
|
|
118
|
+
{ name: "Mongoose", pkg: "mongoose" },
|
|
119
|
+
{ name: "Sequelize", pkg: "sequelize" },
|
|
120
|
+
{ name: "Knex", pkg: "knex" },
|
|
121
|
+
{ name: "PostgreSQL (pg)", pkg: "pg" },
|
|
122
|
+
{ name: "MySQL", pkg: "mysql2" },
|
|
123
|
+
];
|
|
124
|
+
const detected = databases.filter((db) => hasDependency(pkg, db.pkg, true));
|
|
125
|
+
if (detected.length === 0) {
|
|
126
|
+
return {
|
|
127
|
+
id: this.id,
|
|
128
|
+
title: this.title,
|
|
129
|
+
severity: this.severity,
|
|
130
|
+
status: "fail",
|
|
131
|
+
details: "No database ORM or client detected.",
|
|
132
|
+
suggestion: "Most backends need a database. Consider Prisma, TypeORM, or a database client.",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// Check for migration setup (Prisma specific)
|
|
136
|
+
const hasMigrations = detected.some((db) => db.name === "Prisma") &&
|
|
137
|
+
exists(path.join(ctx.projectPath, "prisma", "migrations"));
|
|
138
|
+
const migrationInfo = hasMigrations ? " (with migrations)" : "";
|
|
139
|
+
return {
|
|
140
|
+
id: this.id,
|
|
141
|
+
title: this.title,
|
|
142
|
+
severity: this.severity,
|
|
143
|
+
status: "pass",
|
|
144
|
+
details: `Detected database: ${detected.map((db) => db.name).join(", ")}${migrationInfo}`,
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
// Rule 14: TypeScript strict mode check
|
|
149
|
+
export const ruleTypeScriptStrict = {
|
|
150
|
+
id: "typescript-strict",
|
|
151
|
+
title: "TypeScript strict mode enabled",
|
|
152
|
+
severity: "warn",
|
|
153
|
+
async run(ctx) {
|
|
154
|
+
const tsconfigPath = path.join(ctx.projectPath, "tsconfig.json");
|
|
155
|
+
if (!exists(tsconfigPath)) {
|
|
156
|
+
return {
|
|
157
|
+
id: this.id,
|
|
158
|
+
title: this.title,
|
|
159
|
+
severity: this.severity,
|
|
160
|
+
status: "skip",
|
|
161
|
+
details: "tsconfig.json not found (not a TypeScript project).",
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const content = readText(tsconfigPath);
|
|
165
|
+
if (!content) {
|
|
166
|
+
return {
|
|
167
|
+
id: this.id,
|
|
168
|
+
title: this.title,
|
|
169
|
+
severity: this.severity,
|
|
170
|
+
status: "skip",
|
|
171
|
+
details: "Could not read tsconfig.json.",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
let tsconfig;
|
|
175
|
+
try {
|
|
176
|
+
// Remove comments for JSON parsing (basic approach)
|
|
177
|
+
const jsonContent = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
|
|
178
|
+
tsconfig = JSON.parse(jsonContent);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return {
|
|
182
|
+
id: this.id,
|
|
183
|
+
title: this.title,
|
|
184
|
+
severity: this.severity,
|
|
185
|
+
status: "skip",
|
|
186
|
+
details: "Could not parse tsconfig.json.",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const compilerOptions = tsconfig.compilerOptions || {};
|
|
190
|
+
const isStrict = compilerOptions.strict === true;
|
|
191
|
+
if (!isStrict) {
|
|
192
|
+
return {
|
|
193
|
+
id: this.id,
|
|
194
|
+
title: this.title,
|
|
195
|
+
severity: this.severity,
|
|
196
|
+
status: "fail",
|
|
197
|
+
details: "TypeScript strict mode is not enabled.",
|
|
198
|
+
suggestion: 'Enable strict mode in tsconfig.json: set "strict": true in compilerOptions',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
id: this.id,
|
|
203
|
+
title: this.title,
|
|
204
|
+
severity: this.severity,
|
|
205
|
+
status: "pass",
|
|
206
|
+
details: "TypeScript strict mode is enabled.",
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
// Rule 15: Production dependencies audit
|
|
211
|
+
export const ruleProductionDeps = {
|
|
212
|
+
id: "production-deps-audit",
|
|
213
|
+
title: "Production dependencies audit",
|
|
214
|
+
severity: "error",
|
|
215
|
+
async run(ctx) {
|
|
216
|
+
const pkg = readPackageJson(ctx.packageJsonPath);
|
|
217
|
+
if (!pkg) {
|
|
218
|
+
return {
|
|
219
|
+
id: this.id,
|
|
220
|
+
title: this.title,
|
|
221
|
+
severity: this.severity,
|
|
222
|
+
status: "skip",
|
|
223
|
+
details: "Could not read package.json.",
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// Dev tools that should NOT be in production dependencies
|
|
227
|
+
const devToolPatterns = [
|
|
228
|
+
"nodemon",
|
|
229
|
+
"ts-node",
|
|
230
|
+
"tsx",
|
|
231
|
+
"@types/",
|
|
232
|
+
"eslint",
|
|
233
|
+
"prettier",
|
|
234
|
+
"jest",
|
|
235
|
+
"vitest",
|
|
236
|
+
"mocha",
|
|
237
|
+
"chai",
|
|
238
|
+
"@testing-library",
|
|
239
|
+
"webpack-dev-server",
|
|
240
|
+
"vite",
|
|
241
|
+
];
|
|
242
|
+
const wrongDeps = getDependenciesInWrongScope(pkg, devToolPatterns);
|
|
243
|
+
if (wrongDeps.length === 0) {
|
|
244
|
+
return {
|
|
245
|
+
id: this.id,
|
|
246
|
+
title: this.title,
|
|
247
|
+
severity: this.severity,
|
|
248
|
+
status: "pass",
|
|
249
|
+
details: "No dev tools found in production dependencies.",
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
id: this.id,
|
|
254
|
+
title: this.title,
|
|
255
|
+
severity: this.severity,
|
|
256
|
+
status: "fail",
|
|
257
|
+
details: `Dev tools found in dependencies (should be in devDependencies): ${wrongDeps.join(", ")}`,
|
|
258
|
+
suggestion: `Move to devDependencies: ${wrongDeps.map((dep) => `npm install -D ${dep}`).join(" && ")}`,
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// File: src/utils/codeScanner.ts
|
|
2
|
+
import { readText } from "./fs.js";
|
|
3
|
+
import fg from "fast-glob";
|
|
4
|
+
/**
|
|
5
|
+
* Scans source files for Express global error handler pattern:
|
|
6
|
+
* app.use((err, req, res, next) => ...)
|
|
7
|
+
*/
|
|
8
|
+
export async function scanForErrorHandler(srcPath) {
|
|
9
|
+
// Search for .ts and .js files in src/
|
|
10
|
+
const files = await fg(["**/*.ts", "**/*.js"], {
|
|
11
|
+
cwd: srcPath,
|
|
12
|
+
absolute: true,
|
|
13
|
+
ignore: ["node_modules", "dist", "build"],
|
|
14
|
+
});
|
|
15
|
+
// Pattern to detect error handler middleware (4 params with err as first)
|
|
16
|
+
// Look for variations:
|
|
17
|
+
// - app.use((err, req, res, next) =>
|
|
18
|
+
// - app.use(function(err, req, res, next)
|
|
19
|
+
// - (err, req, res, next) => with error handling inside
|
|
20
|
+
const errorHandlerPatterns = [
|
|
21
|
+
/app\.use\s*\(\s*\(\s*err\s*,\s*req\s*,\s*res\s*,\s*next\s*\)/i,
|
|
22
|
+
/app\.use\s*\(\s*function\s*\(\s*err\s*,\s*req\s*,\s*res\s*,\s*next\s*\)/i,
|
|
23
|
+
/app\.use\s*\(\s*async\s*\(\s*err\s*,\s*req\s*,\s*res\s*,\s*next\s*\)/i,
|
|
24
|
+
];
|
|
25
|
+
for (const file of files) {
|
|
26
|
+
const content = readText(file);
|
|
27
|
+
if (!content)
|
|
28
|
+
continue;
|
|
29
|
+
// Check if any error handler pattern is found
|
|
30
|
+
for (const pattern of errorHandlerPatterns) {
|
|
31
|
+
if (pattern.test(content)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Scans source files to check if any of the logging libraries are imported/used
|
|
40
|
+
*/
|
|
41
|
+
export async function scanForLoggingUsage(srcPath, loggingLibs) {
|
|
42
|
+
// Search for .ts and .js files in src/
|
|
43
|
+
const files = await fg(["**/*.ts", "**/*.js"], {
|
|
44
|
+
cwd: srcPath,
|
|
45
|
+
absolute: true,
|
|
46
|
+
ignore: ["node_modules", "dist", "build"],
|
|
47
|
+
});
|
|
48
|
+
// Create import patterns for each logging library
|
|
49
|
+
// e.g., import pino from 'pino', require('winston'), etc.
|
|
50
|
+
const importPatterns = loggingLibs.flatMap((lib) => [
|
|
51
|
+
new RegExp(`import\\s+.*from\\s+['"]${lib}['"]`, "i"),
|
|
52
|
+
new RegExp(`require\\s*\\(\\s*['"]${lib}['"]\\s*\\)`, "i"),
|
|
53
|
+
]);
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
const content = readText(file);
|
|
56
|
+
if (!content)
|
|
57
|
+
continue;
|
|
58
|
+
// Check if any import pattern is found
|
|
59
|
+
for (const pattern of importPatterns) {
|
|
60
|
+
if (pattern.test(content)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// File: src/utils/fs.ts
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
export function exists(filePath) {
|
|
4
|
+
return fs.existsSync(filePath);
|
|
5
|
+
}
|
|
6
|
+
export function readText(filePath) {
|
|
7
|
+
try {
|
|
8
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// File: src/utils/packageJson.ts
|
|
2
|
+
import { readText } from "./fs.js";
|
|
3
|
+
export function readPackageJson(packageJsonPath) {
|
|
4
|
+
const txt = readText(packageJsonPath);
|
|
5
|
+
if (!txt)
|
|
6
|
+
return null;
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(txt);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function hasScript(pkg, name) {
|
|
15
|
+
return Boolean(pkg?.scripts?.[name]);
|
|
16
|
+
}
|
|
17
|
+
export function hasDependency(pkg, name, checkDev = false) {
|
|
18
|
+
const inDeps = Boolean(pkg?.dependencies?.[name]);
|
|
19
|
+
const inDevDeps = checkDev ? Boolean(pkg?.devDependencies?.[name]) : false;
|
|
20
|
+
return inDeps || inDevDeps;
|
|
21
|
+
}
|
|
22
|
+
export function detectFramework(pkg) {
|
|
23
|
+
if (hasDependency(pkg, "express"))
|
|
24
|
+
return "express";
|
|
25
|
+
if (hasDependency(pkg, "fastify"))
|
|
26
|
+
return "fastify";
|
|
27
|
+
if (hasDependency(pkg, "@nestjs/core"))
|
|
28
|
+
return "nest";
|
|
29
|
+
if (hasDependency(pkg, "hono"))
|
|
30
|
+
return "hono";
|
|
31
|
+
return "unknown";
|
|
32
|
+
}
|
|
33
|
+
export function hasAnyDependency(pkg, names, checkDev = false) {
|
|
34
|
+
return names.some((name) => hasDependency(pkg, name, checkDev));
|
|
35
|
+
}
|
|
36
|
+
export function getDependenciesInWrongScope(pkg, devToolPatterns) {
|
|
37
|
+
const prodDeps = pkg?.dependencies || {};
|
|
38
|
+
const wrongDeps = [];
|
|
39
|
+
for (const dep of Object.keys(prodDeps)) {
|
|
40
|
+
// Check if this dependency matches any dev tool pattern
|
|
41
|
+
const isDevTool = devToolPatterns.some((pattern) => {
|
|
42
|
+
if (pattern.endsWith("/")) {
|
|
43
|
+
// Pattern like "@types/" - check if dep starts with it
|
|
44
|
+
return dep.startsWith(pattern);
|
|
45
|
+
}
|
|
46
|
+
// Exact match
|
|
47
|
+
return dep === pattern;
|
|
48
|
+
});
|
|
49
|
+
if (isDevTool) {
|
|
50
|
+
wrongDeps.push(dep);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return wrongDeps;
|
|
54
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bandit-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"dev": "tsx src/index.ts",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"prepublishOnly": "npm run build"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"bin": {
|
|
21
|
+
"bandit": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@clack/prompts": "^1.5.1",
|
|
25
|
+
"chalk": "^5.6.2",
|
|
26
|
+
"commander": "^14.0.3",
|
|
27
|
+
"fast-glob": "^3.3.3",
|
|
28
|
+
"js-yaml": "^4.1.1",
|
|
29
|
+
"ora": "^9.2.0",
|
|
30
|
+
"zod": "^4.3.6"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/js-yaml": "^4.0.9",
|
|
34
|
+
"@types/node": "^25.2.0",
|
|
35
|
+
"tsx": "^4.21.0",
|
|
36
|
+
"typescript": "^5.9.3"
|
|
37
|
+
}
|
|
38
|
+
}
|