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.
@@ -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
+ }
@@ -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
+ }