dep-inspector-cli
Advanced tools
| export declare function scanAll(options: { | ||
| ai?: boolean; | ||
| json?: boolean; | ||
| report?: boolean; | ||
| }): Promise<void>; | ||
| //# sourceMappingURL=scan-all.d.ts.map |
| {"version":3,"file":"scan-all.d.ts","sourceRoot":"","sources":["../../src/commands/scan-all.ts"],"names":[],"mappings":"AAQA,wBAAsB,OAAO,CAAC,OAAO,EAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,iBAexF"} |
| import { scanSecrets } from "./scan-secrets.js"; | ||
| import { scanDocker } from "./scan-docker.js"; | ||
| import { scanCI } from "./scan-ci.js"; | ||
| import { scanPorts } from "./scan-ports.js"; | ||
| import { scanLogs } from "./scan-logs.js"; | ||
| import { analyzeProject } from "./analyze.js"; | ||
| import chalk from "chalk"; | ||
| export async function scanAll(options) { | ||
| console.log(chalk.bold.magenta("\n๐ dep-inspector v2 โ Full DevOps Scan\n")); | ||
| console.log(chalk.gray("โ".repeat(50))); | ||
| const jsonOpt = options.json ? { json: true } : {}; | ||
| const aiOpt = options.ai ? { ai: true } : {}; | ||
| await analyzeProject({ ai: false, ...jsonOpt, depth: 2 }); | ||
| await scanSecrets({ dir: ".", ...jsonOpt, ...aiOpt }); | ||
| await scanDocker({ file: "Dockerfile", ...jsonOpt }); | ||
| await scanCI({ dir: ".github/workflows", ...jsonOpt }); | ||
| await scanPorts({ ...jsonOpt }); | ||
| await scanLogs({ ...jsonOpt }); | ||
| console.log(chalk.bold.magenta("\nโ Full scan complete.\n")); | ||
| } | ||
| //# sourceMappingURL=scan-all.js.map |
| {"version":3,"file":"scan-all.js","sourceRoot":"","sources":["../../src/commands/scan-all.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAO,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAW,cAAc,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAQ,iBAAiB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAS,gBAAgB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAA2D;IACvF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,4CAA4C,CAAC,CAAC,CAAC;IAC9E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAExC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,MAAM,KAAK,GAAK,OAAO,CAAC,EAAE,CAAG,CAAC,CAAC,EAAE,EAAE,EAAE,IAAa,EAAE,CAAG,CAAC,CAAC,EAAE,CAAC;IAE5D,MAAM,cAAc,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAC1D,MAAM,WAAW,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;IACtD,MAAM,UAAU,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACrD,MAAM,MAAM,CAAC,EAAE,GAAG,EAAE,mBAAmB,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACvD,MAAM,SAAS,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IAChC,MAAM,QAAQ,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IAE/B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC,CAAC;AAC/D,CAAC"} |
| export declare function scanCI(options: { | ||
| dir: string; | ||
| json?: boolean; | ||
| }): Promise<void>; | ||
| //# sourceMappingURL=scan-ci.d.ts.map |
| {"version":3,"file":"scan-ci.d.ts","sourceRoot":"","sources":["../../src/commands/scan-ci.ts"],"names":[],"mappings":"AA8CA,wBAAsB,MAAM,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,iBAkCpE"} |
| import fs from "fs"; | ||
| import path from "path"; | ||
| import chalk from "chalk"; | ||
| function analyzeWorkflow(filePath, content) { | ||
| const issues = []; | ||
| // 1. Hardcoded secrets in YAML | ||
| if (/password:|secret:|api_key:/i.test(content) && !content.includes("${{")) { | ||
| issues.push({ file: filePath, severity: "HIGH", message: "Possible hardcoded secret in workflow (use ${{ secrets.X }})" }); | ||
| } | ||
| // 2. Using deprecated set-output command | ||
| if (content.includes("::set-output")) { | ||
| issues.push({ file: filePath, severity: "HIGH", message: "Deprecated '::set-output' command used โ replace with $GITHUB_OUTPUT" }); | ||
| } | ||
| // 3. pull_request_target with checkout โ dangerous | ||
| if (content.includes("pull_request_target") && content.includes("actions/checkout")) { | ||
| issues.push({ file: filePath, severity: "HIGH", message: "pull_request_target + actions/checkout is a security risk (privilege escalation)" }); | ||
| } | ||
| // 4. Pinned action versions | ||
| if (/uses:\s+\S+@(main|master|latest)/i.test(content)) { | ||
| issues.push({ file: filePath, severity: "MEDIUM", message: "Actions using @main/@master/@latest โ pin to a specific commit SHA or version" }); | ||
| } | ||
| // 5. No cache step | ||
| if (!content.includes("actions/cache") && !content.includes("cache: 'npm'")) { | ||
| issues.push({ file: filePath, severity: "LOW", message: "No caching configured โ builds will be slow (add actions/cache for node_modules)" }); | ||
| } | ||
| // 6. No timeout-minutes | ||
| if (!content.includes("timeout-minutes")) { | ||
| issues.push({ file: filePath, severity: "LOW", message: "No timeout-minutes set โ stuck jobs can run indefinitely" }); | ||
| } | ||
| return issues; | ||
| } | ||
| export async function scanCI(options) { | ||
| const workflowDir = options.dir; | ||
| const allIssues = []; | ||
| if (!fs.existsSync(workflowDir)) { | ||
| console.log(chalk.yellow(`โ ๏ธ No workflows directory found at ${workflowDir}`)); | ||
| return; | ||
| } | ||
| const files = fs.readdirSync(workflowDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")); | ||
| for (const file of files) { | ||
| const fullPath = path.join(workflowDir, file); | ||
| const content = fs.readFileSync(fullPath, "utf-8"); | ||
| allIssues.push(...analyzeWorkflow(fullPath, content)); | ||
| } | ||
| if (options.json) { | ||
| console.log(JSON.stringify({ issues: allIssues, total: allIssues.length }, null, 2)); | ||
| return; | ||
| } | ||
| console.log(chalk.bold.cyan("\nโ๏ธ CI/CD Pipeline Analysis\n")); | ||
| if (allIssues.length === 0) { | ||
| console.log(chalk.green("โ All workflows look good!")); | ||
| return; | ||
| } | ||
| for (const issue of allIssues) { | ||
| const color = issue.severity === "HIGH" ? chalk.red : issue.severity === "MEDIUM" ? chalk.yellow : chalk.gray; | ||
| console.log(color(`[${issue.severity}] ${path.basename(issue.file)}: ${issue.message}`)); | ||
| } | ||
| console.log(); | ||
| } | ||
| //# sourceMappingURL=scan-ci.js.map |
| {"version":3,"file":"scan-ci.js","sourceRoot":"","sources":["../../src/commands/scan-ci.ts"],"names":[],"mappings":"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"} |
| export declare function scanDocker(options: { | ||
| file: string; | ||
| compose?: string; | ||
| json?: boolean; | ||
| }): Promise<void>; | ||
| //# sourceMappingURL=scan-docker.d.ts.map |
| {"version":3,"file":"scan-docker.d.ts","sourceRoot":"","sources":["../../src/commands/scan-docker.ts"],"names":[],"mappings":"AAmDA,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,iBA2B3F"} |
| import fs from "fs"; | ||
| import chalk from "chalk"; | ||
| function analyzeDockerfile(content) { | ||
| const issues = []; | ||
| const lines = content.split("\n"); | ||
| // 1. Running as root | ||
| const hasUser = lines.some((l) => l.trim().startsWith("USER ") && !l.includes("root")); | ||
| if (!hasUser) | ||
| issues.push({ severity: "HIGH", message: "No non-root USER defined โ container runs as root" }); | ||
| // 2. HEALTHCHECK missing | ||
| if (!content.includes("HEALTHCHECK")) { | ||
| issues.push({ severity: "MEDIUM", message: "No HEALTHCHECK instruction โ Docker can't auto-restart unhealthy containers" }); | ||
| } | ||
| // 3. latest tag usage | ||
| if (/FROM\s+\S+:latest/i.test(content)) { | ||
| issues.push({ severity: "MEDIUM", message: "Using ':latest' tag โ not reproducible, pin a specific version" }); | ||
| } | ||
| // 4. Secrets in ENV/ARG | ||
| const secretPattern = /(?:ENV|ARG)\s+(?:PASSWORD|SECRET|KEY|TOKEN|API_KEY)\s*=/i; | ||
| if (secretPattern.test(content)) { | ||
| issues.push({ severity: "HIGH", message: "Sensitive variable (PASSWORD/SECRET/KEY) hardcoded in ENV/ARG" }); | ||
| } | ||
| // 5. No .dockerignore warning (check file exists) | ||
| if (!fs.existsSync(".dockerignore")) { | ||
| issues.push({ severity: "LOW", message: ".dockerignore missing โ may include node_modules or .env in image" }); | ||
| } | ||
| // 6. npm install without --omit=dev in production | ||
| if (content.includes("npm install") && !content.includes("--omit=dev") && !content.includes("--only=production")) { | ||
| issues.push({ severity: "LOW", message: "'npm install' without --omit=dev โ devDependencies included in image" }); | ||
| } | ||
| // 7. Multi-stage โ good practice check | ||
| const fromCount = (content.match(/^FROM\s/gim) ?? []).length; | ||
| if (fromCount === 1) { | ||
| issues.push({ severity: "LOW", message: "Single-stage build โ consider multi-stage to reduce image size" }); | ||
| } | ||
| return issues; | ||
| } | ||
| export async function scanDocker(options) { | ||
| const issues = []; | ||
| if (fs.existsSync(options.file)) { | ||
| const content = fs.readFileSync(options.file, "utf-8"); | ||
| issues.push(...analyzeDockerfile(content)); | ||
| } | ||
| else { | ||
| console.log(chalk.yellow(`โ ๏ธ Dockerfile not found at ${options.file}`)); | ||
| } | ||
| if (options.json) { | ||
| console.log(JSON.stringify({ issues, total: issues.length }, null, 2)); | ||
| return; | ||
| } | ||
| console.log(chalk.bold.cyan("\n๐ณ Docker Analysis\n")); | ||
| if (issues.length === 0) { | ||
| console.log(chalk.green("โ Dockerfile looks good!")); | ||
| return; | ||
| } | ||
| for (const issue of issues) { | ||
| const color = issue.severity === "HIGH" ? chalk.red : issue.severity === "MEDIUM" ? chalk.yellow : chalk.gray; | ||
| console.log(color(`[${issue.severity}] ${issue.message}`)); | ||
| } | ||
| console.log(); | ||
| } | ||
| //# sourceMappingURL=scan-docker.js.map |
| {"version":3,"file":"scan-docker.js","sourceRoot":"","sources":["../../src/commands/scan-docker.ts"],"names":[],"mappings":"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"} |
| export declare function scanLogs(options: { | ||
| json?: boolean; | ||
| }): Promise<void>; | ||
| //# sourceMappingURL=scan-logs.d.ts.map |
| {"version":3,"file":"scan-logs.d.ts","sourceRoot":"","sources":["../../src/commands/scan-logs.ts"],"names":[],"mappings":"AAGA,wBAAsB,QAAQ,CAAC,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,iBAsDzD"} |
| import fs from "fs"; | ||
| import chalk from "chalk"; | ||
| export async function scanLogs(options) { | ||
| const issues = []; | ||
| const passed = []; | ||
| // Check if Winston or Morgan installed | ||
| const pkgPath = "package.json"; | ||
| if (!fs.existsSync(pkgPath)) { | ||
| console.log(chalk.yellow("โ ๏ธ package.json not found")); | ||
| return; | ||
| } | ||
| const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")); | ||
| const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }; | ||
| const hasWinston = "winston" in allDeps; | ||
| const hasMorgan = "morgan" in allDeps; | ||
| const hasPino = "pino" in allDeps; | ||
| if (!hasWinston && !hasMorgan && !hasPino) { | ||
| issues.push("No logger found (winston/morgan/pino) โ console.log is not production-grade"); | ||
| } | ||
| else { | ||
| if (hasWinston) | ||
| passed.push("winston detected"); | ||
| if (hasMorgan) | ||
| passed.push("morgan detected"); | ||
| if (hasPino) | ||
| passed.push("pino detected"); | ||
| } | ||
| // Check for winston transports (file rotation) | ||
| if (hasWinston) { | ||
| const hasRotation = "winston-daily-rotate-file" in allDeps; | ||
| if (!hasRotation) { | ||
| issues.push("winston-daily-rotate-file not found โ logs may grow unbounded"); | ||
| } | ||
| else { | ||
| passed.push("log rotation configured"); | ||
| } | ||
| } | ||
| // Check for LOG_LEVEL env usage | ||
| const envFile = fs.existsSync(".env") ? fs.readFileSync(".env", "utf-8") : ""; | ||
| if (!envFile.includes("LOG_LEVEL")) { | ||
| issues.push("LOG_LEVEL not set in .env โ logger may default to verbose in production"); | ||
| } | ||
| if (options.json) { | ||
| console.log(JSON.stringify({ issues, passed }, null, 2)); | ||
| return; | ||
| } | ||
| console.log(chalk.bold.cyan("\n๐ Logger Health Check\n")); | ||
| for (const p of passed) | ||
| console.log(chalk.green(`โ ${p}`)); | ||
| for (const i of issues) | ||
| console.log(chalk.yellow(`โ ๏ธ ${i}`)); | ||
| console.log(); | ||
| } | ||
| //# sourceMappingURL=scan-logs.js.map |
| {"version":3,"file":"scan-logs.js","sourceRoot":"","sources":["../../src/commands/scan-logs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAA2B;IACxD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,uCAAuC;IACvC,MAAM,OAAO,GAAG,cAAc,CAAC;IAC/B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,4BAA4B,CAAC,CAAC,CAAC;QACxD,OAAO;IACT,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAGvD,CAAC;IACF,MAAM,OAAO,GAAG,EAAE,GAAG,GAAG,CAAC,YAAY,EAAE,GAAG,GAAG,CAAC,eAAe,EAAE,CAAC;IAEhE,MAAM,UAAU,GAAG,SAAS,IAAI,OAAO,CAAC;IACxC,MAAM,SAAS,GAAI,QAAQ,IAAI,OAAO,CAAC;IACvC,MAAM,OAAO,GAAM,MAAM,IAAI,OAAO,CAAC;IAErC,IAAI,CAAC,UAAU,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1C,MAAM,CAAC,IAAI,CAAC,6EAA6E,CAAC,CAAC;IAC7F,CAAC;SAAM,CAAC;QACN,IAAI,UAAU;YAAE,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAChD,IAAI,SAAS;YAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC/C,IAAI,OAAO;YAAK,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC/C,CAAC;IAED,+CAA+C;IAC/C,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,WAAW,GAAG,2BAA2B,IAAI,OAAO,CAAC;QAC3D,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC;QAC/E,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,gCAAgC;IAChC,MAAM,OAAO,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC;IACzF,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACzD,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,CAAC;IAC3D,KAAK,MAAM,CAAC,IAAI,MAAM;QAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5D,KAAK,MAAM,CAAC,IAAI,MAAM;QAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,EAAE,CAAC;AAChB,CAAC"} |
| export declare function scanPorts(options: { | ||
| json?: boolean; | ||
| }): Promise<void>; | ||
| //# sourceMappingURL=scan-ports.d.ts.map |
| {"version":3,"file":"scan-ports.d.ts","sourceRoot":"","sources":["../../src/commands/scan-ports.ts"],"names":[],"mappings":"AAMA,wBAAsB,SAAS,CAAC,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,iBAkD1D"} |
| import { execSync } from "child_process"; | ||
| import chalk from "chalk"; | ||
| const KNOWN_SAFE_PORTS = [22, 80, 443, 3000, 3001, 4000, 5000, 8080, 8443]; | ||
| const SUSPICIOUS_PORTS = [21, 23, 25, 445, 1433, 3306, 5432, 6379, 27017]; // ftp, telnet, smtp, smb, mssql, mysql, pg, redis, mongo | ||
| export async function scanPorts(options) { | ||
| let output = ""; | ||
| try { | ||
| const isWindows = process.platform === "win32"; | ||
| output = isWindows | ||
| ? execSync("netstat -ano", { encoding: "utf-8" }) | ||
| : execSync("ss -tulnp 2>/dev/null || netstat -tulnp 2>/dev/null", { | ||
| encoding: "utf-8", | ||
| }); | ||
| } | ||
| catch { | ||
| console.log(chalk.yellow("โ ๏ธ Could not read port info")); | ||
| return; | ||
| } | ||
| const lines = output | ||
| .split("\n") | ||
| .filter((l) => l.includes("LISTEN") || l.includes("0.0.0.0")); | ||
| const openPorts = []; | ||
| for (const line of lines) { | ||
| const portMatch = line.match(/:(\d+)\s/); | ||
| if (!portMatch) | ||
| continue; | ||
| const port = parseInt(portMatch[1] ?? "0"); | ||
| const isExposed = line.includes("0.0.0.0") || line.includes("*:"); | ||
| const isSuspicious = SUSPICIOUS_PORTS.includes(port) && isExposed; | ||
| const entry = { | ||
| port, | ||
| process: line.split(/\s+/).pop() ?? "unknown", | ||
| }; | ||
| if (isSuspicious) | ||
| entry.warning = `Port ${port} is publicly exposed โ restrict to localhost`; | ||
| openPorts.push(entry); | ||
| } | ||
| if (options.json) { | ||
| console.log(JSON.stringify({ openPorts }, null, 2)); | ||
| return; | ||
| } | ||
| console.log(chalk.bold.cyan("\n๐ Port & Process Monitor\n")); | ||
| for (const p of openPorts) { | ||
| if (p.warning) { | ||
| console.log(chalk.red(`[WARN] :${p.port} โ ${p.warning}`)); | ||
| } | ||
| else { | ||
| console.log(chalk.green(`[OK] :${p.port}`)); | ||
| } | ||
| } | ||
| console.log(); | ||
| } | ||
| //# sourceMappingURL=scan-ports.js.map |
| {"version":3,"file":"scan-ports.js","sourceRoot":"","sources":["../../src/commands/scan-ports.ts"],"names":[],"mappings":"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"} |
| export declare function scanSecrets(options: { | ||
| dir: string; | ||
| json?: boolean; | ||
| ai?: boolean; | ||
| }): Promise<void>; | ||
| //# sourceMappingURL=scan-secrets.d.ts.map |
| {"version":3,"file":"scan-secrets.d.ts","sourceRoot":"","sources":["../../src/commands/scan-secrets.ts"],"names":[],"mappings":"AA4FA,wBAAsB,WAAW,CAAC,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,EAAE,CAAC,EAAE,OAAO,CAAA;CAAE,iBAgDvF"} |
| import fs from "fs"; | ||
| import path from "path"; | ||
| import chalk from "chalk"; | ||
| // Secret patterns โ no AI needed | ||
| const SECRET_PATTERNS = [ | ||
| { name: "AWS Access Key", pattern: /AKIA[0-9A-Z]{16}/, severity: "HIGH" }, | ||
| { name: "AWS Secret Key", pattern: /aws_secret_access_key\s*=\s*[^\s]{20,}/i, severity: "HIGH" }, | ||
| { name: "Private Key Block", pattern: /-----BEGIN (RSA |EC )?PRIVATE KEY-----/, severity: "HIGH" }, | ||
| { name: "Groq API Key", pattern: /gsk_[a-zA-Z0-9]{40,}/, severity: "HIGH" }, | ||
| { name: "OpenAI API Key", pattern: /sk-[a-zA-Z0-9]{32,}/, severity: "HIGH" }, | ||
| { name: "GitHub Token", pattern: /ghp_[a-zA-Z0-9]{36}/, severity: "HIGH" }, | ||
| { name: "JWT Secret hardcoded", pattern: /jwt[_-]?secret\s*[:=]\s*['"][^'"]{8,}/i, severity: "HIGH" }, | ||
| { name: "Database URL", pattern: /mongodb(\+srv)?:\/\/[^:]+:[^@]+@/i, severity: "HIGH" }, | ||
| { name: "Database URL (PG)", pattern: /postgres(ql)?:\/\/[^:]+:[^@]+@/i, severity: "HIGH" }, | ||
| { name: "Stripe Secret Key", pattern: /sk_live_[a-zA-Z0-9]{24,}/, severity: "HIGH" }, | ||
| { name: "Razorpay Secret", pattern: /rzp_live_[a-zA-Z0-9]{14,}/, severity: "HIGH" }, | ||
| { name: "Generic Password", pattern: /password\s*[:=]\s*['"][^'"]{6,}/i, severity: "MEDIUM" }, | ||
| { name: "Generic Secret", pattern: /secret\s*[:=]\s*['"][^'"]{6,}/i, severity: "MEDIUM" }, | ||
| { name: ".env file committed", pattern: /^\.env$/, severity: "HIGH" }, // filename check | ||
| ]; | ||
| const IGNORE_DIRS = ["node_modules", ".git", "dist", ".next", "build", "coverage"]; | ||
| const IGNORE_FILES = [".env", ".env.local", ".env.development", ".env.production"]; | ||
| const SCAN_EXTENSIONS = [".ts", ".js", ".tsx", ".jsx", ".env", ".json", ".yaml", ".yml", ".sh"]; | ||
| function shouldIgnore(filePath) { | ||
| const fileName = path.basename(filePath); | ||
| if (IGNORE_FILES.includes(fileName)) | ||
| return true; // ๐ add this | ||
| return IGNORE_DIRS.some((dir) => filePath.includes(`/${dir}/`)); | ||
| } | ||
| function checkEnvInGitignore() { | ||
| if (!fs.existsSync(".gitignore")) { | ||
| console.log(chalk.red("[HIGH] .gitignore missing โ .env may get committed to git")); | ||
| return; | ||
| } | ||
| const gitignore = fs.readFileSync(".gitignore", "utf-8"); | ||
| if (!gitignore.includes(".env")) { | ||
| console.log(chalk.red("[HIGH] .env is NOT in .gitignore โ risk of secret exposure in git")); | ||
| } | ||
| else { | ||
| console.log(chalk.green("โ .env is gitignored")); | ||
| } | ||
| } | ||
| function scanFile(filePath) { | ||
| const findings = []; | ||
| const ext = path.extname(filePath); | ||
| if (!SCAN_EXTENSIONS.includes(ext) && !filePath.endsWith(".env")) | ||
| return findings; | ||
| if (shouldIgnore(filePath)) | ||
| return findings; | ||
| const lines = fs.readFileSync(filePath, "utf-8").split("\n"); | ||
| lines.forEach((line, idx) => { | ||
| for (const { name, pattern, severity } of SECRET_PATTERNS) { | ||
| if (pattern.test(line)) { | ||
| findings.push({ | ||
| file: filePath, | ||
| line: idx + 1, | ||
| pattern: name, | ||
| severity, | ||
| preview: line.trim().slice(0, 80), | ||
| }); | ||
| break; | ||
| } | ||
| } | ||
| }); | ||
| return findings; | ||
| } | ||
| function walkDir(dir) { | ||
| const files = []; | ||
| if (!fs.existsSync(dir)) | ||
| return files; | ||
| const entries = fs.readdirSync(dir, { withFileTypes: true }); | ||
| for (const entry of entries) { | ||
| const fullPath = path.join(dir, entry.name); | ||
| if (entry.isDirectory() && !IGNORE_DIRS.includes(entry.name)) { | ||
| files.push(...walkDir(fullPath)); | ||
| } | ||
| else if (entry.isFile()) { | ||
| files.push(fullPath); | ||
| } | ||
| } | ||
| return files; | ||
| } | ||
| export async function scanSecrets(options) { | ||
| const targetDir = path.resolve(options.dir); | ||
| const allFiles = walkDir(targetDir); | ||
| const allFindings = []; | ||
| for (const file of allFiles) { | ||
| allFindings.push(...scanFile(file)); | ||
| } | ||
| if (options.json) { | ||
| console.log(JSON.stringify({ findings: allFindings, total: allFindings.length }, null, 2)); | ||
| return; | ||
| } | ||
| console.log(chalk.bold.cyan("\n๐ Secrets Scanner\n")); | ||
| checkEnvInGitignore(); | ||
| if (allFindings.length === 0) { | ||
| console.log(chalk.green("โ No secrets detected!")); | ||
| return; | ||
| } | ||
| const highCount = allFindings.filter((f) => f.severity === "HIGH").length; | ||
| const medCount = allFindings.filter((f) => f.severity === "MEDIUM").length; | ||
| console.log(chalk.red(`โ ${highCount} HIGH`) + " " + chalk.yellow(`โ ๏ธ ${medCount} MEDIUM`) + "\n"); | ||
| for (const f of allFindings) { | ||
| const color = f.severity === "HIGH" ? chalk.red : chalk.yellow; | ||
| console.log(color(`[${f.severity}] ${f.pattern}`)); | ||
| console.log(` File : ${f.file}:${f.line}`); | ||
| console.log(` Code : ${chalk.gray(f.preview)}\n`); | ||
| } | ||
| if (options.ai) { | ||
| const key = process.env["GROQ_API_KEY"]; | ||
| if (!key) { | ||
| console.log(chalk.gray("โน๏ธ AI insights skipped โ GROQ_API_KEY not set")); | ||
| } | ||
| else { | ||
| // dynamic import to avoid breaking non-AI usage | ||
| const { analyzeWithAI } = await import("../utils/ai.js"); | ||
| const summary = await analyzeWithAI(allFindings.map((f) => `${f.pattern} in ${f.file}:${f.line}`).join("\n")); | ||
| console.log(chalk.cyan("\n๐ก AI Insights\n") + summary); | ||
| } | ||
| } | ||
| } | ||
| //# sourceMappingURL=scan-secrets.js.map |
| {"version":3,"file":"scan-secrets.js","sourceRoot":"","sources":["../../src/commands/scan-secrets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,iCAAiC;AACjC,MAAM,eAAe,GAA6E;IAChG,EAAE,IAAI,EAAE,gBAAgB,EAAS,OAAO,EAAE,kBAAkB,EAA2B,QAAQ,EAAE,MAAM,EAAE;IACzG,EAAE,IAAI,EAAE,gBAAgB,EAAS,OAAO,EAAE,yCAAyC,EAAG,QAAQ,EAAE,MAAM,EAAE;IACxG,EAAE,IAAI,EAAE,mBAAmB,EAAM,OAAO,EAAE,wCAAwC,EAAK,QAAQ,EAAE,MAAM,EAAE;IACzG,EAAE,IAAI,EAAE,cAAc,EAAW,OAAO,EAAE,sBAAsB,EAAsB,QAAQ,EAAE,MAAM,EAAE;IACxG,EAAE,IAAI,EAAE,gBAAgB,EAAS,OAAO,EAAE,qBAAqB,EAAuB,QAAQ,EAAE,MAAM,EAAE;IACxG,EAAE,IAAI,EAAE,cAAc,EAAW,OAAO,EAAE,qBAAqB,EAAuB,QAAQ,EAAE,MAAM,EAAE;IACxG,EAAE,IAAI,EAAE,sBAAsB,EAAG,OAAO,EAAE,wCAAwC,EAAI,QAAQ,EAAE,MAAM,EAAE;IACxG,EAAE,IAAI,EAAE,cAAc,EAAW,OAAO,EAAE,mCAAmC,EAAS,QAAQ,EAAE,MAAM,EAAE;IACxG,EAAE,IAAI,EAAE,mBAAmB,EAAM,OAAO,EAAE,iCAAiC,EAAW,QAAQ,EAAE,MAAM,EAAE;IACxG,EAAE,IAAI,EAAE,mBAAmB,EAAM,OAAO,EAAE,0BAA0B,EAAkB,QAAQ,EAAE,MAAM,EAAE;IACxG,EAAE,IAAI,EAAE,iBAAiB,EAAQ,OAAO,EAAE,2BAA2B,EAAgB,QAAQ,EAAE,MAAM,EAAE;IACvG,EAAE,IAAI,EAAE,kBAAkB,EAAO,OAAO,EAAE,kCAAkC,EAAU,QAAQ,EAAE,QAAQ,EAAE;IAC1G,EAAE,IAAI,EAAE,gBAAgB,EAAS,OAAO,EAAE,gCAAgC,EAAY,QAAQ,EAAE,QAAQ,EAAE;IAC1G,EAAE,IAAI,EAAE,qBAAqB,EAAI,OAAO,EAAE,SAAS,EAAmC,QAAQ,EAAE,MAAM,EAAE,EAAE,iBAAiB;CAC5H,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AACnF,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,CAAC,CAAC;AACnF,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAUhG,SAAS,YAAY,CAAC,QAAgB;IACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC,CAAE,cAAc;IACjE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,mBAAmB;IAC1B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC,CAAC;QACpF,OAAO;IACT,CAAC;IACD,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACzD,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,mEAAmE,CAAC,CAAC,CAAC;IAC9F,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,QAAgB;IAChC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,QAAQ,CAAC;IAClF,IAAI,YAAY,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAE5C,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7D,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QAC1B,KAAK,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,eAAe,EAAE,CAAC;YAC1D,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvB,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,GAAG,GAAG,CAAC;oBACb,OAAO,EAAE,IAAI;oBACb,QAAQ;oBACR,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;iBAClC,CAAC,CAAC;gBACH,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,OAAO,CAAC,GAAW;IAC1B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7D,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;QACnC,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAsD;IACtF,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACpC,MAAM,WAAW,GAAc,EAAE,CAAC;IAElC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,WAAW,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3F,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC;IAEvD,mBAAmB,EAAE,CAAC;IAEtB,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;QACnD,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAC1E,MAAM,QAAQ,GAAI,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAC;IAE5E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,SAAS,OAAO,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,QAAQ,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;IAErG,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;QAC/D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;IAED,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC,CAAC;QAC5E,CAAC;aAAM,CAAC;YACN,gDAAgD;YAChD,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;YACzD,MAAM,OAAO,GAAG,MAAM,aAAa,CACjC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACzE,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;AACH,CAAC"} |
| export {}; | ||
| //# sourceMappingURL=config.d.ts.map |
| {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":""} |
| export {}; | ||
| //# sourceMappingURL=config.js.map |
| {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":""} |
| export {}; | ||
| //# sourceMappingURL=reporter.d.ts.map |
| {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/utils/reporter.ts"],"names":[],"mappings":""} |
| export {}; | ||
| //# sourceMappingURL=reporter.js.map |
| {"version":3,"file":"reporter.js","sourceRoot":"","sources":["../../src/utils/reporter.ts"],"names":[],"mappings":""} |
| import { scanSecrets } from "./scan-secrets.js"; | ||
| import { scanDocker } from "./scan-docker.js"; | ||
| import { scanCI } from "./scan-ci.js"; | ||
| import { scanPorts } from "./scan-ports.js"; | ||
| import { scanLogs } from "./scan-logs.js"; | ||
| import { analyzeProject } from "./analyze.js"; | ||
| import chalk from "chalk"; | ||
| export async function scanAll(options: { ai?: boolean; json?: boolean; report?: boolean }) { | ||
| console.log(chalk.bold.magenta("\n๐ dep-inspector v2 โ Full DevOps Scan\n")); | ||
| console.log(chalk.gray("โ".repeat(50))); | ||
| const jsonOpt = options.json ? { json: true as const } : {}; | ||
| const aiOpt = options.ai ? { ai: true as const } : {}; | ||
| await analyzeProject({ ai: false, ...jsonOpt, depth: 2 }); | ||
| await scanSecrets({ dir: ".", ...jsonOpt, ...aiOpt }); | ||
| await scanDocker({ file: "Dockerfile", ...jsonOpt }); | ||
| await scanCI({ dir: ".github/workflows", ...jsonOpt }); | ||
| await scanPorts({ ...jsonOpt }); | ||
| await scanLogs({ ...jsonOpt }); | ||
| console.log(chalk.bold.magenta("\nโ Full scan complete.\n")); | ||
| } |
| import fs from "fs"; | ||
| import path from "path"; | ||
| import chalk from "chalk"; | ||
| interface CIIssue { | ||
| file: string; | ||
| severity: "HIGH" | "MEDIUM" | "LOW"; | ||
| message: string; | ||
| } | ||
| function analyzeWorkflow(filePath: string, content: string): CIIssue[] { | ||
| const issues: CIIssue[] = []; | ||
| // 1. Hardcoded secrets in YAML | ||
| if (/password:|secret:|api_key:/i.test(content) && !content.includes("${{")) { | ||
| issues.push({ file: filePath, severity: "HIGH", message: "Possible hardcoded secret in workflow (use ${{ secrets.X }})" }); | ||
| } | ||
| // 2. Using deprecated set-output command | ||
| if (content.includes("::set-output")) { | ||
| issues.push({ file: filePath, severity: "HIGH", message: "Deprecated '::set-output' command used โ replace with $GITHUB_OUTPUT" }); | ||
| } | ||
| // 3. pull_request_target with checkout โ dangerous | ||
| if (content.includes("pull_request_target") && content.includes("actions/checkout")) { | ||
| issues.push({ file: filePath, severity: "HIGH", message: "pull_request_target + actions/checkout is a security risk (privilege escalation)" }); | ||
| } | ||
| // 4. Pinned action versions | ||
| if (/uses:\s+\S+@(main|master|latest)/i.test(content)) { | ||
| issues.push({ file: filePath, severity: "MEDIUM", message: "Actions using @main/@master/@latest โ pin to a specific commit SHA or version" }); | ||
| } | ||
| // 5. No cache step | ||
| if (!content.includes("actions/cache") && !content.includes("cache: 'npm'")) { | ||
| issues.push({ file: filePath, severity: "LOW", message: "No caching configured โ builds will be slow (add actions/cache for node_modules)" }); | ||
| } | ||
| // 6. No timeout-minutes | ||
| if (!content.includes("timeout-minutes")) { | ||
| issues.push({ file: filePath, severity: "LOW", message: "No timeout-minutes set โ stuck jobs can run indefinitely" }); | ||
| } | ||
| return issues; | ||
| } | ||
| export async function scanCI(options: { dir: string; json?: boolean }) { | ||
| const workflowDir = options.dir; | ||
| const allIssues: CIIssue[] = []; | ||
| if (!fs.existsSync(workflowDir)) { | ||
| console.log(chalk.yellow(`โ ๏ธ No workflows directory found at ${workflowDir}`)); | ||
| return; | ||
| } | ||
| const files = fs.readdirSync(workflowDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")); | ||
| for (const file of files) { | ||
| const fullPath = path.join(workflowDir, file); | ||
| const content = fs.readFileSync(fullPath, "utf-8"); | ||
| allIssues.push(...analyzeWorkflow(fullPath, content)); | ||
| } | ||
| if (options.json) { | ||
| console.log(JSON.stringify({ issues: allIssues, total: allIssues.length }, null, 2)); | ||
| return; | ||
| } | ||
| console.log(chalk.bold.cyan("\nโ๏ธ CI/CD Pipeline Analysis\n")); | ||
| if (allIssues.length === 0) { | ||
| console.log(chalk.green("โ All workflows look good!")); | ||
| return; | ||
| } | ||
| for (const issue of allIssues) { | ||
| const color = issue.severity === "HIGH" ? chalk.red : issue.severity === "MEDIUM" ? chalk.yellow : chalk.gray; | ||
| console.log(color(`[${issue.severity}] ${path.basename(issue.file)}: ${issue.message}`)); | ||
| } | ||
| console.log(); | ||
| } |
| import fs from "fs"; | ||
| import chalk from "chalk"; | ||
| interface DockerIssue { | ||
| severity: "HIGH" | "MEDIUM" | "LOW"; | ||
| message: string; | ||
| } | ||
| function analyzeDockerfile(content: string): DockerIssue[] { | ||
| const issues: DockerIssue[] = []; | ||
| const lines = content.split("\n"); | ||
| // 1. Running as root | ||
| const hasUser = lines.some((l) => l.trim().startsWith("USER ") && !l.includes("root")); | ||
| if (!hasUser) issues.push({ severity: "HIGH", message: "No non-root USER defined โ container runs as root" }); | ||
| // 2. HEALTHCHECK missing | ||
| if (!content.includes("HEALTHCHECK")) { | ||
| issues.push({ severity: "MEDIUM", message: "No HEALTHCHECK instruction โ Docker can't auto-restart unhealthy containers" }); | ||
| } | ||
| // 3. latest tag usage | ||
| if (/FROM\s+\S+:latest/i.test(content)) { | ||
| issues.push({ severity: "MEDIUM", message: "Using ':latest' tag โ not reproducible, pin a specific version" }); | ||
| } | ||
| // 4. Secrets in ENV/ARG | ||
| const secretPattern = /(?:ENV|ARG)\s+(?:PASSWORD|SECRET|KEY|TOKEN|API_KEY)\s*=/i; | ||
| if (secretPattern.test(content)) { | ||
| issues.push({ severity: "HIGH", message: "Sensitive variable (PASSWORD/SECRET/KEY) hardcoded in ENV/ARG" }); | ||
| } | ||
| // 5. No .dockerignore warning (check file exists) | ||
| if (!fs.existsSync(".dockerignore")) { | ||
| issues.push({ severity: "LOW", message: ".dockerignore missing โ may include node_modules or .env in image" }); | ||
| } | ||
| // 6. npm install without --omit=dev in production | ||
| if (content.includes("npm install") && !content.includes("--omit=dev") && !content.includes("--only=production")) { | ||
| issues.push({ severity: "LOW", message: "'npm install' without --omit=dev โ devDependencies included in image" }); | ||
| } | ||
| // 7. Multi-stage โ good practice check | ||
| const fromCount = (content.match(/^FROM\s/gim) ?? []).length; | ||
| if (fromCount === 1) { | ||
| issues.push({ severity: "LOW", message: "Single-stage build โ consider multi-stage to reduce image size" }); | ||
| } | ||
| return issues; | ||
| } | ||
| export async function scanDocker(options: { file: string; compose?: string; json?: boolean }) { | ||
| const issues: DockerIssue[] = []; | ||
| if (fs.existsSync(options.file)) { | ||
| const content = fs.readFileSync(options.file, "utf-8"); | ||
| issues.push(...analyzeDockerfile(content)); | ||
| } else { | ||
| console.log(chalk.yellow(`โ ๏ธ Dockerfile not found at ${options.file}`)); | ||
| } | ||
| if (options.json) { | ||
| console.log(JSON.stringify({ issues, total: issues.length }, null, 2)); | ||
| return; | ||
| } | ||
| console.log(chalk.bold.cyan("\n๐ณ Docker Analysis\n")); | ||
| if (issues.length === 0) { | ||
| console.log(chalk.green("โ Dockerfile looks good!")); | ||
| return; | ||
| } | ||
| for (const issue of issues) { | ||
| const color = issue.severity === "HIGH" ? chalk.red : issue.severity === "MEDIUM" ? chalk.yellow : chalk.gray; | ||
| console.log(color(`[${issue.severity}] ${issue.message}`)); | ||
| } | ||
| console.log(); | ||
| } |
| import fs from "fs"; | ||
| import chalk from "chalk"; | ||
| export async function scanLogs(options: { json?: boolean }) { | ||
| const issues: string[] = []; | ||
| const passed: string[] = []; | ||
| // Check if Winston or Morgan installed | ||
| const pkgPath = "package.json"; | ||
| if (!fs.existsSync(pkgPath)) { | ||
| console.log(chalk.yellow("โ ๏ธ package.json not found")); | ||
| return; | ||
| } | ||
| const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")) as { | ||
| dependencies?: Record<string, string>; | ||
| devDependencies?: Record<string, string>; | ||
| }; | ||
| const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }; | ||
| const hasWinston = "winston" in allDeps; | ||
| const hasMorgan = "morgan" in allDeps; | ||
| const hasPino = "pino" in allDeps; | ||
| if (!hasWinston && !hasMorgan && !hasPino) { | ||
| issues.push("No logger found (winston/morgan/pino) โ console.log is not production-grade"); | ||
| } else { | ||
| if (hasWinston) passed.push("winston detected"); | ||
| if (hasMorgan) passed.push("morgan detected"); | ||
| if (hasPino) passed.push("pino detected"); | ||
| } | ||
| // Check for winston transports (file rotation) | ||
| if (hasWinston) { | ||
| const hasRotation = "winston-daily-rotate-file" in allDeps; | ||
| if (!hasRotation) { | ||
| issues.push("winston-daily-rotate-file not found โ logs may grow unbounded"); | ||
| } else { | ||
| passed.push("log rotation configured"); | ||
| } | ||
| } | ||
| // Check for LOG_LEVEL env usage | ||
| const envFile = fs.existsSync(".env") ? fs.readFileSync(".env", "utf-8") : ""; | ||
| if (!envFile.includes("LOG_LEVEL")) { | ||
| issues.push("LOG_LEVEL not set in .env โ logger may default to verbose in production"); | ||
| } | ||
| if (options.json) { | ||
| console.log(JSON.stringify({ issues, passed }, null, 2)); | ||
| return; | ||
| } | ||
| console.log(chalk.bold.cyan("\n๐ Logger Health Check\n")); | ||
| for (const p of passed) console.log(chalk.green(`โ ${p}`)); | ||
| for (const i of issues) console.log(chalk.yellow(`โ ๏ธ ${i}`)); | ||
| console.log(); | ||
| } |
| import { execSync } from "child_process"; | ||
| import chalk from "chalk"; | ||
| const KNOWN_SAFE_PORTS = [22, 80, 443, 3000, 3001, 4000, 5000, 8080, 8443]; | ||
| const SUSPICIOUS_PORTS = [21, 23, 25, 445, 1433, 3306, 5432, 6379, 27017]; // ftp, telnet, smtp, smb, mssql, mysql, pg, redis, mongo | ||
| export async function scanPorts(options: { json?: boolean }) { | ||
| let output = ""; | ||
| try { | ||
| const isWindows = process.platform === "win32"; | ||
| output = isWindows | ||
| ? execSync("netstat -ano", { encoding: "utf-8" }) | ||
| : execSync("ss -tulnp 2>/dev/null || netstat -tulnp 2>/dev/null", { | ||
| encoding: "utf-8", | ||
| }); | ||
| } catch { | ||
| console.log(chalk.yellow("โ ๏ธ Could not read port info")); | ||
| return; | ||
| } | ||
| const lines = output | ||
| .split("\n") | ||
| .filter((l) => l.includes("LISTEN") || l.includes("0.0.0.0")); | ||
| const openPorts: { port: number; process: string; warning?: string }[] = []; | ||
| for (const line of lines) { | ||
| const portMatch = line.match(/:(\d+)\s/); | ||
| if (!portMatch) continue; | ||
| const port = parseInt(portMatch[1] ?? "0"); | ||
| const isExposed = line.includes("0.0.0.0") || line.includes("*:"); | ||
| const isSuspicious = SUSPICIOUS_PORTS.includes(port) && isExposed; | ||
| const entry: { port: number; process: string; warning?: string } = { | ||
| port, | ||
| process: line.split(/\s+/).pop() ?? "unknown", | ||
| }; | ||
| if (isSuspicious) | ||
| entry.warning = `Port ${port} is publicly exposed โ restrict to localhost`; | ||
| openPorts.push(entry); | ||
| } | ||
| if (options.json) { | ||
| console.log(JSON.stringify({ openPorts }, null, 2)); | ||
| return; | ||
| } | ||
| console.log(chalk.bold.cyan("\n๐ Port & Process Monitor\n")); | ||
| for (const p of openPorts) { | ||
| if (p.warning) { | ||
| console.log(chalk.red(`[WARN] :${p.port} โ ${p.warning}`)); | ||
| } else { | ||
| console.log(chalk.green(`[OK] :${p.port}`)); | ||
| } | ||
| } | ||
| console.log(); | ||
| } |
| import fs from "fs"; | ||
| import path from "path"; | ||
| import chalk from "chalk"; | ||
| // Secret patterns โ no AI needed | ||
| const SECRET_PATTERNS: { name: string; pattern: RegExp; severity: "HIGH" | "MEDIUM" | "LOW" }[] = [ | ||
| { name: "AWS Access Key", pattern: /AKIA[0-9A-Z]{16}/, severity: "HIGH" }, | ||
| { name: "AWS Secret Key", pattern: /aws_secret_access_key\s*=\s*[^\s]{20,}/i, severity: "HIGH" }, | ||
| { name: "Private Key Block", pattern: /-----BEGIN (RSA |EC )?PRIVATE KEY-----/, severity: "HIGH" }, | ||
| { name: "Groq API Key", pattern: /gsk_[a-zA-Z0-9]{40,}/, severity: "HIGH" }, | ||
| { name: "OpenAI API Key", pattern: /sk-[a-zA-Z0-9]{32,}/, severity: "HIGH" }, | ||
| { name: "GitHub Token", pattern: /ghp_[a-zA-Z0-9]{36}/, severity: "HIGH" }, | ||
| { name: "JWT Secret hardcoded", pattern: /jwt[_-]?secret\s*[:=]\s*['"][^'"]{8,}/i, severity: "HIGH" }, | ||
| { name: "Database URL", pattern: /mongodb(\+srv)?:\/\/[^:]+:[^@]+@/i, severity: "HIGH" }, | ||
| { name: "Database URL (PG)", pattern: /postgres(ql)?:\/\/[^:]+:[^@]+@/i, severity: "HIGH" }, | ||
| { name: "Stripe Secret Key", pattern: /sk_live_[a-zA-Z0-9]{24,}/, severity: "HIGH" }, | ||
| { name: "Razorpay Secret", pattern: /rzp_live_[a-zA-Z0-9]{14,}/, severity: "HIGH" }, | ||
| { name: "Generic Password", pattern: /password\s*[:=]\s*['"][^'"]{6,}/i, severity: "MEDIUM" }, | ||
| { name: "Generic Secret", pattern: /secret\s*[:=]\s*['"][^'"]{6,}/i, severity: "MEDIUM" }, | ||
| { name: ".env file committed", pattern: /^\.env$/, severity: "HIGH" }, // filename check | ||
| ]; | ||
| const IGNORE_DIRS = ["node_modules", ".git", "dist", ".next", "build", "coverage"]; | ||
| const IGNORE_FILES = [".env", ".env.local", ".env.development", ".env.production"]; | ||
| const SCAN_EXTENSIONS = [".ts", ".js", ".tsx", ".jsx", ".env", ".json", ".yaml", ".yml", ".sh"]; | ||
| interface Finding { | ||
| file: string; | ||
| line: number; | ||
| pattern: string; | ||
| severity: "HIGH" | "MEDIUM" | "LOW"; | ||
| preview: string; | ||
| } | ||
| function shouldIgnore(filePath: string): boolean { | ||
| const fileName = path.basename(filePath); | ||
| if (IGNORE_FILES.includes(fileName)) return true; // ๐ add this | ||
| return IGNORE_DIRS.some((dir) => filePath.includes(`/${dir}/`)); | ||
| } | ||
| function checkEnvInGitignore(): void { | ||
| if (!fs.existsSync(".gitignore")) { | ||
| console.log(chalk.red("[HIGH] .gitignore missing โ .env may get committed to git")); | ||
| return; | ||
| } | ||
| const gitignore = fs.readFileSync(".gitignore", "utf-8"); | ||
| if (!gitignore.includes(".env")) { | ||
| console.log(chalk.red("[HIGH] .env is NOT in .gitignore โ risk of secret exposure in git")); | ||
| } else { | ||
| console.log(chalk.green("โ .env is gitignored")); | ||
| } | ||
| } | ||
| function scanFile(filePath: string): Finding[] { | ||
| const findings: Finding[] = []; | ||
| const ext = path.extname(filePath); | ||
| if (!SCAN_EXTENSIONS.includes(ext) && !filePath.endsWith(".env")) return findings; | ||
| if (shouldIgnore(filePath)) return findings; | ||
| const lines = fs.readFileSync(filePath, "utf-8").split("\n"); | ||
| lines.forEach((line, idx) => { | ||
| for (const { name, pattern, severity } of SECRET_PATTERNS) { | ||
| if (pattern.test(line)) { | ||
| findings.push({ | ||
| file: filePath, | ||
| line: idx + 1, | ||
| pattern: name, | ||
| severity, | ||
| preview: line.trim().slice(0, 80), | ||
| }); | ||
| break; | ||
| } | ||
| } | ||
| }); | ||
| return findings; | ||
| } | ||
| function walkDir(dir: string): string[] { | ||
| const files: string[] = []; | ||
| if (!fs.existsSync(dir)) return files; | ||
| const entries = fs.readdirSync(dir, { withFileTypes: true }); | ||
| for (const entry of entries) { | ||
| const fullPath = path.join(dir, entry.name); | ||
| if (entry.isDirectory() && !IGNORE_DIRS.includes(entry.name)) { | ||
| files.push(...walkDir(fullPath)); | ||
| } else if (entry.isFile()) { | ||
| files.push(fullPath); | ||
| } | ||
| } | ||
| return files; | ||
| } | ||
| export async function scanSecrets(options: { dir: string; json?: boolean; ai?: boolean }) { | ||
| const targetDir = path.resolve(options.dir); | ||
| const allFiles = walkDir(targetDir); | ||
| const allFindings: Finding[] = []; | ||
| for (const file of allFiles) { | ||
| allFindings.push(...scanFile(file)); | ||
| } | ||
| if (options.json) { | ||
| console.log(JSON.stringify({ findings: allFindings, total: allFindings.length }, null, 2)); | ||
| return; | ||
| } | ||
| console.log(chalk.bold.cyan("\n๐ Secrets Scanner\n")); | ||
| checkEnvInGitignore(); | ||
| if (allFindings.length === 0) { | ||
| console.log(chalk.green("โ No secrets detected!")); | ||
| return; | ||
| } | ||
| const highCount = allFindings.filter((f) => f.severity === "HIGH").length; | ||
| const medCount = allFindings.filter((f) => f.severity === "MEDIUM").length; | ||
| console.log(chalk.red(`โ ${highCount} HIGH`) + " " + chalk.yellow(`โ ๏ธ ${medCount} MEDIUM`) + "\n"); | ||
| for (const f of allFindings) { | ||
| const color = f.severity === "HIGH" ? chalk.red : chalk.yellow; | ||
| console.log(color(`[${f.severity}] ${f.pattern}`)); | ||
| console.log(` File : ${f.file}:${f.line}`); | ||
| console.log(` Code : ${chalk.gray(f.preview)}\n`); | ||
| } | ||
| if (options.ai) { | ||
| const key = process.env["GROQ_API_KEY"]; | ||
| if (!key) { | ||
| console.log(chalk.gray("โน๏ธ AI insights skipped โ GROQ_API_KEY not set")); | ||
| } else { | ||
| // dynamic import to avoid breaking non-AI usage | ||
| const { analyzeWithAI } = await import("../utils/ai.js"); | ||
| const summary = await analyzeWithAI( | ||
| allFindings.map((f) => `${f.pattern} in ${f.file}:${f.line}`).join("\n") | ||
| ); | ||
| console.log(chalk.cyan("\n๐ก AI Insights\n") + summary); | ||
| } | ||
| } | ||
| } |
+52
-10
| #!/usr/bin/env node | ||
| import { Command } from "commander"; | ||
| import { analyzeProject } from "./commands/analyze.js"; | ||
| import { scanSecrets } from "./commands/scan-secrets.js"; | ||
| import { scanDocker } from "./commands/scan-docker.js"; | ||
| import { scanCI } from "./commands/scan-ci.js"; | ||
| import { scanPorts } from "./commands/scan-ports.js"; | ||
| import { scanLogs } from "./commands/scan-logs.js"; | ||
| import { scanAll } from "./commands/scan-all.js"; | ||
| const program = new Command(); | ||
| program | ||
| .name("dep-inspector") | ||
| .description("Advanced dependency analyzer with AI-powered insights") | ||
| .version("1.0.0"); | ||
| // Shared options for both default and analyze command | ||
| function addOptions(cmd) { | ||
| .description("DevOps-grade dependency & security toolkit") | ||
| .version("2.0.0"); | ||
| // V1 โ existing | ||
| function addAnalyzeOptions(cmd) { | ||
| return cmd | ||
| .option("--ai", "Enable AI insights via Groq") | ||
| .option("--json", "Output results as JSON (useful for CI/CD)") | ||
| .option("--ai", "Enable AI insights via Groq (optional)") | ||
| .option("--json", "Output as JSON") | ||
| .option("--depth <number>", "Limit dependency tree depth", parseInt); | ||
| } | ||
| // Default action โ runs when user just types: dep-inspector | ||
| addOptions(program).action((options) => analyzeProject(options)); | ||
| // Explicit analyze subcommand โ dep-inspector analyze | ||
| addOptions(program.command("analyze").description("Analyze project dependencies")).action((options) => analyzeProject(options)); | ||
| addAnalyzeOptions(program).action((options) => analyzeProject(options)); | ||
| addAnalyzeOptions(program.command("analyze").description("Analyze dependencies (V1)")).action((options) => analyzeProject(options)); | ||
| // V2 โ new scan commands | ||
| program | ||
| .command("scan:secrets") | ||
| .description("Scan for hardcoded secrets, API keys, .env leaks") | ||
| .option("--dir <path>", "Directory to scan", ".") | ||
| .option("--json", "Output as JSON") | ||
| .option("--ai", "AI-powered explanation (optional, needs GROQ_API_KEY)") | ||
| .action((options) => scanSecrets(options)); | ||
| program | ||
| .command("scan:docker") | ||
| .description("Analyze Dockerfile and docker-compose.yml for issues") | ||
| .option("--file <path>", "Path to Dockerfile", "Dockerfile") | ||
| .option("--compose <path>", "Path to docker-compose file") | ||
| .option("--json", "Output as JSON") | ||
| .action((options) => scanDocker(options)); | ||
| program | ||
| .command("scan:ci") | ||
| .description("Lint GitHub Actions workflows for security & best practices") | ||
| .option("--dir <path>", "Workflows directory", ".github/workflows") | ||
| .option("--json", "Output as JSON") | ||
| .action((options) => scanCI(options)); | ||
| program | ||
| .command("scan:ports") | ||
| .description("Check open ports and running processes") | ||
| .option("--json", "Output as JSON") | ||
| .action((options) => scanPorts(options)); | ||
| program | ||
| .command("scan:logs") | ||
| .description("Check Winston/Morgan logger configuration health") | ||
| .option("--json", "Output as JSON") | ||
| .action((options) => scanLogs(options)); | ||
| program | ||
| .command("scan:all") | ||
| .description("Run all scans and generate a full report") | ||
| .option("--ai", "AI summary (optional)") | ||
| .option("--json", "Output as JSON") | ||
| .option("--report", "Save HTML report to dep-inspector-report.html") | ||
| .action((options) => scanAll(options)); | ||
| program.parse(); | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,eAAe,CAAC;KACrB,WAAW,CAAC,uDAAuD,CAAC;KACpE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,sDAAsD;AACtD,SAAS,UAAU,CAAC,GAAY;IAC9B,OAAO,GAAG;SACP,MAAM,CAAC,MAAM,EAAE,6BAA6B,CAAC;SAC7C,MAAM,CAAC,QAAQ,EAAE,2CAA2C,CAAC;SAC7D,MAAM,CAAC,kBAAkB,EAAE,6BAA6B,EAAE,QAAQ,CAAC,CAAC;AACzE,CAAC;AAED,4DAA4D;AAC5D,UAAU,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAEjE,sDAAsD;AACtD,UAAU,CACR,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,8BAA8B,CAAC,CACvE,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAE/C,OAAO,CAAC,KAAK,EAAE,CAAC"} | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/utils/ai.ts"],"names":[],"mappings":"AAaA,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsClE"} | ||
| {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/utils/ai.ts"],"names":[],"mappings":"AAOA,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClE"} |
+15
-22
@@ -1,24 +0,18 @@ | ||
| import { ChatGroq } from "@langchain/groq"; | ||
| import Groq from "groq-sdk"; | ||
| import { config } from "dotenv"; | ||
| config(); | ||
| const model = new ChatGroq({ | ||
| model: "llama-3.1-8b-instant", | ||
| temperature: 0.3, | ||
| }); | ||
| // In-memory cache | ||
| const cache = new Map(); | ||
| export async function analyzeWithAI(input) { | ||
| try { | ||
| if (cache.has(input)) { | ||
| if (cache.has(input)) | ||
| return cache.get(input); | ||
| } | ||
| const client = new Groq({ apiKey: process.env["GROQ_API_KEY"] }); | ||
| const prompt = ` | ||
| You are a senior software security engineer and tester with high experience of at least 10 years. | ||
| Make sure to provide detailed and actionable insights for each issue, including potential impacts, security risks, and recommended fixes or alternatives. | ||
| You are a senior software security engineer with 10+ years of experience. | ||
| Provide detailed and actionable insights for each issue found. | ||
| Analyze the following dependency issues: | ||
| Analyze the following: | ||
| ${input} | ||
| Respond in this format: | ||
| 1. Issue | ||
@@ -30,12 +24,11 @@ 2. Impact | ||
| `; | ||
| const res = await model.invoke(prompt); | ||
| let finalOutput = ""; | ||
| if (typeof res.content === "string") { | ||
| finalOutput = res.content.trim(); | ||
| } | ||
| else { | ||
| finalOutput = JSON.stringify(res.content, null, 2); | ||
| } | ||
| cache.set(input, finalOutput); | ||
| return finalOutput; | ||
| const res = await client.chat.completions.create({ | ||
| model: "llama-3.3-70b-versatile", | ||
| messages: [{ role: "user", content: prompt }], | ||
| max_tokens: 500, | ||
| temperature: 0.3, | ||
| }); | ||
| const output = res.choices[0]?.message?.content?.trim() ?? "โ ๏ธ No response"; | ||
| cache.set(input, output); | ||
| return output; | ||
| } | ||
@@ -42,0 +35,0 @@ catch { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ai.js","sourceRoot":"","sources":["../../src/utils/ai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,EAAE,CAAC;AAET,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC;IACzB,KAAK,EAAE,sBAAsB;IAC7B,WAAW,EAAE,GAAG;CACjB,CAAC,CAAC;AAEH,kBAAkB;AAClB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;AAExC,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAa;IAC/C,IAAI,CAAC;QACH,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACrB,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC;QAC3B,CAAC;QAED,MAAM,MAAM,GAAG;;;;;EAKjB,KAAK;;;;;;;;;CASN,CAAC;QAEE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAEvC,IAAI,WAAW,GAAG,EAAE,CAAC;QAErB,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACpC,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACnC,CAAC;aAAM,CAAC;YACN,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACrD,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAE9B,OAAO,WAAW,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,wBAAwB,CAAC;IAClC,CAAC;AACH,CAAC"} | ||
| {"version":3,"file":"ai.js","sourceRoot":"","sources":["../../src/utils/ai.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,UAAU,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,EAAE,CAAC;AAET,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;AAExC,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAa;IAC/C,IAAI,CAAC;QACH,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC;QAE/C,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;QAEjE,MAAM,MAAM,GAAG;;;;;EAKjB,KAAK;;;;;;;;CAQN,CAAC;QAEE,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;YAC/C,KAAK,EAAE,yBAAyB;YAChC,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;YAC7C,UAAU,EAAE,GAAG;YACf,WAAW,EAAE,GAAG;SACjB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,gBAAgB,CAAC;QAC5E,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC;IAEhB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,wBAAwB,CAAC;IAClC,CAAC;AACH,CAAC"} |
+21
-13
| { | ||
| "name": "dep-inspector-cli", | ||
| "version": "1.1.0", | ||
| "description": "AI powered dependency inspector for JavaScript/TypeScript projects", | ||
| "version": "2.0.0", | ||
| "description": "DevOps-grade dependency, security & infrastructure scanner for Node.js projects", | ||
| "main": "dist/index.js", | ||
| "types": "dist/index.d.ts", | ||
| "keywords": [ | ||
| "dependency", | ||
| "inspector", | ||
| "analyzer", | ||
| "security", | ||
| "vulnerability", | ||
| "ai", | ||
| "groq", | ||
| "langchain" | ||
| ], | ||
| "dependency", | ||
| "inspector", | ||
| "analyzer", | ||
| "security", | ||
| "vulnerability", | ||
| "devops", | ||
| "security-scanner", | ||
| "secrets-scanner", | ||
| "docker", | ||
| "cli", | ||
| "ci-cd", | ||
| "github-actions", | ||
| "groq", | ||
| "ai", | ||
| "nodejs" | ||
| ], | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "dev": "ts-node src/index.ts", | ||
| "test": "echo \"Error: no test specified\" && exit 1" | ||
@@ -24,7 +33,6 @@ }, | ||
| "dependencies": { | ||
| "@langchain/groq": "^1.2.0", | ||
| "chalk": "^5.6.2", | ||
| "commander": "^14.0.3", | ||
| "dotenv": "^17.4.2", | ||
| "langchain": "^1.3.3", | ||
| "groq-sdk": "^1.2.0", | ||
| "ora": "^9.3.0", | ||
@@ -31,0 +39,0 @@ "semver": "^7.7.4", |
+170
-101
| # dep-inspector-cli | ||
| > Advanced CLI tool to analyze your Node.js project dependencies โ with vulnerability scanning, outdated version detection, dependency chain tracing, and optional AI-powered insights via Groq. | ||
| > DevOps-grade dependency, security & infrastructure scanner for Node.js projects. | ||
@@ -9,17 +9,27 @@ [](https://www.npmjs.com/package/dep-inspector-cli) | ||
| [](https://www.typescriptlang.org/) | ||
| [](https://github.com/Nevin100/Dep-inspector-nevin/pulls) | ||
| --- | ||
| ## What it does | ||
| ## What's new in v2 | ||
| - ๐ณ **Dependency tree** โ visual tree of all your dependencies with outdated version highlights | ||
| - ๐ก๏ธ **Vulnerability scan** โ runs `npm audit` and surfaces issues with severity levels | ||
| - ๐ **Dependency chains** โ traces exactly which package pulled in a vulnerable dependency | ||
| - ๐ฆ **Package info** โ shows homepage, author, repo link for each flagged package | ||
| - ๐ **Alternatives** โ suggests modern replacements for deprecated/risky packages | ||
| - ๐ค **AI insights** โ optional Groq-powered analysis with actionable fix suggestions | ||
| - ๐ **JSON output** โ machine-readable output for CI/CD pipelines | ||
| Version 2 transforms `dep-inspector` from a dependency analyzer into a full **DevOps security toolkit** โ covering secrets, Docker, CI/CD pipelines, ports, and logging. All features work **without any API key**. AI insights are optional. | ||
| --- | ||
| ## Features | ||
| | Command | What it does | | ||
| |---|---| | ||
| | `dep-inspector` | Dependency tree + vulnerability scan (v1) | | ||
| | `scan:secrets` | Detect hardcoded API keys, .env leaks, private keys | | ||
| | `scan:vulns` | npm audit wrapper with severity thresholds | | ||
| | `scan:docker` | Dockerfile & docker-compose security analysis | | ||
| | `scan:ci` | GitHub Actions workflow linting | | ||
| | `scan:ports` | Open port detection & process monitoring | | ||
| | `scan:logs` | Winston/Morgan/Pino logger health check | | ||
| | `scan:all` | Run everything, generate a full report | | ||
| --- | ||
| ## Installation | ||
@@ -35,23 +45,33 @@ | ||
| Run from your project root (where `package.json` lives): | ||
| ### Dependency Analysis (v1) | ||
| ```bash | ||
| # Basic analysis | ||
| dep-inspector | ||
| dep-inspector # Full analysis | ||
| dep-inspector analyze # Same, explicit subcommand | ||
| dep-inspector --depth 3 # Limit tree depth | ||
| dep-inspector --json # Machine-readable output | ||
| dep-inspector --ai # AI-powered insights (optional, needs GROQ_API_KEY) | ||
| ``` | ||
| # Same as above using explicit subcommand | ||
| dep-inspector analyze | ||
| ### Security Scans (v2) | ||
| # Enable AI-powered insights (requires GROQ_API_KEY) | ||
| dep-inspector --ai | ||
| ```bash | ||
| dep-inspector scan:secrets # Scan current directory | ||
| dep-inspector scan:secrets --dir ./src # Scan specific directory | ||
| dep-inspector scan:secrets --ai # With AI explanations (optional) | ||
| # Output as JSON (great for CI/CD pipelines) | ||
| dep-inspector --json | ||
| dep-inspector scan:docker # Analyze Dockerfile | ||
| dep-inspector scan:docker --file ./docker/Dockerfile | ||
| # Limit dependency tree depth (useful for large projects) | ||
| dep-inspector --depth 3 | ||
| dep-inspector scan:ci # Lint GitHub Actions workflows | ||
| dep-inspector scan:ci --dir .github/workflows | ||
| # Combine flags | ||
| dep-inspector --ai --depth 2 | ||
| dep-inspector analyze --json --depth 4 | ||
| dep-inspector scan:ports # Check open ports | ||
| dep-inspector scan:logs # Logger health check | ||
| dep-inspector scan:all # Full DevOps scan | ||
| dep-inspector scan:all --report # + saves HTML report | ||
| dep-inspector scan:all --json # + JSON output | ||
| dep-inspector scan:all --ai # + AI summary (optional) | ||
| ``` | ||
@@ -61,49 +81,53 @@ | ||
| ## Commands & Flags | ||
| ## What gets detected | ||
| | Command / Flag | Description | | ||
| |---|---| | ||
| | `dep-inspector` | Run full analysis (default) | | ||
| | `dep-inspector analyze` | Explicit analyze subcommand | | ||
| | `--ai` | Enable AI analysis via Groq LLM | | ||
| | `--json` | Output results as JSON | | ||
| | `--depth <n>` | Limit dependency tree to n levels deep | | ||
| ### `scan:secrets` | ||
| - AWS Access Keys & Secret Keys | ||
| - OpenAI, Groq, GitHub tokens | ||
| - Hardcoded JWT secrets | ||
| - MongoDB / PostgreSQL connection strings | ||
| - Stripe & Razorpay live keys | ||
| - Generic `password=` / `secret=` assignments | ||
| - Accidentally committed `.env` files | ||
| --- | ||
| ### `scan:docker` | ||
| - Container running as root (no `USER` directive) | ||
| - Missing `HEALTHCHECK` | ||
| - `:latest` tag usage (non-reproducible builds) | ||
| - Secrets hardcoded in `ENV`/`ARG` | ||
| - Missing `.dockerignore` | ||
| - `npm install` without `--omit=dev` in production | ||
| - Single-stage builds (image size warning) | ||
| ## Example Output | ||
| ### `scan:ci` | ||
| - Hardcoded secrets in workflow YAML | ||
| - Deprecated `::set-output` command | ||
| - `pull_request_target` + `actions/checkout` (privilege escalation risk) | ||
| - Actions pinned to `@main` / `@latest` instead of a version | ||
| - Missing dependency cache | ||
| - No `timeout-minutes` (stuck jobs) | ||
| ``` | ||
| ๐ณ Dependency Tree | ||
| ### `scan:ports` | ||
| - Lists all open/listening ports | ||
| - Flags database ports (Redis, MongoDB, PostgreSQL, MySQL) publicly exposed | ||
| - Flags FTP, Telnet, and other insecure services | ||
| โโโ my-project@1.0.0 | ||
| โโโ axios@0.21.1 (latest: 1.7.2) โ HIGH | ||
| โโโ express@4.18.2 | ||
| โ โโโ body-parser@1.20.1 | ||
| โ โโโ serve-static@1.15.0 | ||
| โโโ lodash@4.17.21 | ||
| ### `scan:logs` | ||
| - Detects missing logger (console.log in production) | ||
| - Checks for `winston-daily-rotate-file` (log rotation) | ||
| - Validates `LOG_LEVEL` environment variable | ||
| โ ๏ธ Vulnerability Analysis | ||
| --- | ||
| ๐ฆ axios | ||
| Severity : HIGH | ||
| Version : 0.21.1 โ 1.7.2 | ||
| โ ๏ธ Breaking change possible! | ||
| About : Promise based HTTP client | ||
| Docs : https://axios-http.com | ||
| Author : Matt Zabriskie | ||
| Repo : https://github.com/axios/axios | ||
| Alternative: โ | ||
| Tests : | ||
| - Test API calls | ||
| - Validate request/response headers | ||
| ๐ Chain: root โ axios | ||
| ## Output | ||
| ๐ก Fix Suggestions | ||
| All commands support `--json` for machine-readable output: | ||
| โ axios: npm install axios@latest | ||
| โจ Analysis Complete | ||
| ```bash | ||
| dep-inspector scan:secrets --json > secrets-report.json | ||
| dep-inspector scan:all --json > full-report.json | ||
| ``` | ||
| Severity levels: `HIGH` ยท `MEDIUM` ยท `LOW` | ||
| --- | ||
@@ -113,16 +137,13 @@ | ||
| The `--ai` flag uses [Groq](https://console.groq.com) to provide detailed security analysis for each vulnerable package. | ||
| The `--ai` flag sends findings to Groq LLM for human-readable explanations and fix suggestions. It is **completely optional** โ every scan works without it. | ||
| 1. Get a free API key at [console.groq.com](https://console.groq.com) | ||
| 2. Create a `.env` file in your project root: | ||
| ```bash | ||
| # Set once in your shell profile or .env | ||
| export GROQ_API_KEY=your_key_here | ||
| ```env | ||
| GROQ_API_KEY=your_groq_api_key_here | ||
| dep-inspector scan:secrets --ai | ||
| dep-inspector scan:all --ai | ||
| ``` | ||
| The AI will provide: | ||
| - Issue explanation | ||
| - Security impact assessment | ||
| - Recommended fix | ||
| - Alternative package suggestions | ||
| Get a free key at [console.groq.com](https://console.groq.com). If the key is missing, the tool runs normally and skips AI output with a note. | ||
@@ -133,20 +154,44 @@ --- | ||
| Use `--json` flag to integrate into your pipeline: | ||
| ```yaml | ||
| # .github/workflows/security.yml | ||
| name: Security Scan | ||
| on: [push, pull_request] | ||
| jobs: | ||
| dep-inspector: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Install dep-inspector | ||
| run: npm install -g dep-inspector-cli | ||
| - name: Scan secrets | ||
| run: dep-inspector scan:secrets --json > secrets.json | ||
| - name: Scan dependencies | ||
| run: dep-inspector --json > deps.json | ||
| - name: Lint CI workflows | ||
| run: dep-inspector scan:ci | ||
| - name: Upload reports | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: dep-inspector-reports | ||
| path: "*.json" | ||
| ``` | ||
| **Fail build on HIGH severity secrets:** | ||
| ```bash | ||
| # Generate JSON report | ||
| dep-inspector --json > dep-report.json | ||
| # Fail build if any vulnerabilities found | ||
| dep-inspector --json | node -e " | ||
| let data = ''; | ||
| process.stdin.on('data', d => data += d); | ||
| dep-inspector scan:secrets --json | node -e " | ||
| let d = ''; | ||
| process.stdin.on('data', c => d += c); | ||
| process.stdin.on('end', () => { | ||
| const r = JSON.parse(data); | ||
| const count = Object.keys(r.vulnerabilities).length; | ||
| if (count > 0) { | ||
| console.error('Found ' + count + ' vulnerabilities. Failing build.'); | ||
| process.exit(1); | ||
| } | ||
| console.log('No vulnerabilities found.'); | ||
| const { findings } = JSON.parse(d); | ||
| const high = findings.filter(f => f.severity === 'HIGH').length; | ||
| if (high > 0) { console.error(high + ' HIGH severity secrets found. Failing build.'); process.exit(1); } | ||
| console.log('No HIGH severity secrets found.'); | ||
| }); | ||
@@ -156,14 +201,27 @@ " | ||
| **GitHub Actions example:** | ||
| --- | ||
| ```yaml | ||
| - name: Check dependencies | ||
| run: dep-inspector --json > dep-report.json | ||
| ## Project Structure | ||
| - name: Upload report | ||
| uses: actions/upload-artifact@v3 | ||
| with: | ||
| name: dependency-report | ||
| path: dep-report.json | ||
| ``` | ||
| dep-inspector/ | ||
| โโโ src/ | ||
| โ โโโ index.ts # CLI entry โ all commands registered here | ||
| โ โโโ commands/ | ||
| โ โ โโโ analyze.ts # v1 dependency analysis | ||
| โ โ โโโ scan-secrets.ts # secrets & key scanner | ||
| โ โ โโโ scan-docker.ts # Dockerfile analysis | ||
| โ โ โโโ scan-ci.ts # GitHub Actions linter | ||
| โ โ โโโ scan-ports.ts # port monitor | ||
| โ โ โโโ scan-logs.ts # logger health check | ||
| โ โ โโโ scan-all.ts # full scan orchestrator | ||
| โ โโโ utils/ | ||
| โ โโโ ai.ts # optional Groq integration | ||
| โ โโโ audit.ts # npm audit wrapper | ||
| โ โโโ deps.ts # npm ls wrapper | ||
| โ โโโ tree.ts # tree printer | ||
| โ โโโ version.ts # semver comparison | ||
| โโโ package.json | ||
| โโโ tsconfig.json | ||
| ``` | ||
@@ -175,4 +233,4 @@ --- | ||
| - Node.js >= 16 | ||
| - npm (must be available in PATH) | ||
| - `GROQ_API_KEY` in `.env` (only required for `--ai` flag) | ||
| - npm in PATH | ||
| - `GROQ_API_KEY` โ only needed for `--ai` flag | ||
@@ -183,7 +241,7 @@ --- | ||
| - **TypeScript** โ fully typed codebase | ||
| - **TypeScript** โ fully typed | ||
| - **Commander.js** โ CLI argument parsing | ||
| - **Chalk** โ colored terminal output | ||
| - **Ora** โ terminal spinner | ||
| - **LangChain + Groq** โ AI analysis | ||
| - **Chalk** โ colored output | ||
| - **Ora** โ terminal spinners | ||
| - **Groq SDK** โ optional AI layer (direct, no LangChain dependency) | ||
| - **Semver** โ version comparison | ||
@@ -193,6 +251,15 @@ | ||
| ## Roadmap | ||
| - [ ] `scan:secrets` โ `.git` history scanning (catch keys that were deleted but committed) | ||
| - [ ] `scan:docker` โ docker-compose multi-service analysis | ||
| - [ ] `--report` โ full HTML report with charts | ||
| - [ ] Slack / Discord webhook alerts | ||
| - [ ] GitHub App integration (PR comments) | ||
| - [ ] Custom rule config via `.depinspectorrc` | ||
| --- | ||
| ## Contributing | ||
| Pull requests are welcome! For major changes, please open an issue first. | ||
| ```bash | ||
@@ -205,2 +272,4 @@ git clone https://github.com/Nevin100/Dep-inspector-nevin | ||
| Pull requests welcome. For major changes, open an issue first. | ||
| --- | ||
@@ -207,0 +276,0 @@ |
+60
-13
@@ -5,2 +5,8 @@ #!/usr/bin/env node | ||
| import { analyzeProject } from "./commands/analyze.js"; | ||
| import { scanSecrets } from "./commands/scan-secrets.js"; | ||
| import { scanDocker } from "./commands/scan-docker.js"; | ||
| import { scanCI } from "./commands/scan-ci.js"; | ||
| import { scanPorts } from "./commands/scan-ports.js"; | ||
| import { scanLogs } from "./commands/scan-logs.js"; | ||
| import { scanAll } from "./commands/scan-all.js"; | ||
@@ -11,21 +17,62 @@ const program = new Command(); | ||
| .name("dep-inspector") | ||
| .description("Advanced dependency analyzer with AI-powered insights") | ||
| .version("1.0.0"); | ||
| // Shared options for both default and analyze command | ||
| function addOptions(cmd: Command) { | ||
| .description("DevOps-grade dependency & security toolkit") | ||
| .version("2.0.0"); | ||
| // V1 โ existing | ||
| function addAnalyzeOptions(cmd: Command) { | ||
| return cmd | ||
| .option("--ai", "Enable AI insights via Groq") | ||
| .option("--json", "Output results as JSON (useful for CI/CD)") | ||
| .option("--ai", "Enable AI insights via Groq (optional)") | ||
| .option("--json", "Output as JSON") | ||
| .option("--depth <number>", "Limit dependency tree depth", parseInt); | ||
| } | ||
| // Default action โ runs when user just types: dep-inspector | ||
| addOptions(program).action((options) => analyzeProject(options)); | ||
| // Explicit analyze subcommand โ dep-inspector analyze | ||
| addOptions( | ||
| program.command("analyze").description("Analyze project dependencies") | ||
| addAnalyzeOptions(program).action((options) => analyzeProject(options)); | ||
| addAnalyzeOptions( | ||
| program.command("analyze").description("Analyze dependencies (V1)") | ||
| ).action((options) => analyzeProject(options)); | ||
| // V2 โ new scan commands | ||
| program | ||
| .command("scan:secrets") | ||
| .description("Scan for hardcoded secrets, API keys, .env leaks") | ||
| .option("--dir <path>", "Directory to scan", ".") | ||
| .option("--json", "Output as JSON") | ||
| .option("--ai", "AI-powered explanation (optional, needs GROQ_API_KEY)") | ||
| .action((options) => scanSecrets(options)); | ||
| program | ||
| .command("scan:docker") | ||
| .description("Analyze Dockerfile and docker-compose.yml for issues") | ||
| .option("--file <path>", "Path to Dockerfile", "Dockerfile") | ||
| .option("--compose <path>", "Path to docker-compose file") | ||
| .option("--json", "Output as JSON") | ||
| .action((options) => scanDocker(options)); | ||
| program | ||
| .command("scan:ci") | ||
| .description("Lint GitHub Actions workflows for security & best practices") | ||
| .option("--dir <path>", "Workflows directory", ".github/workflows") | ||
| .option("--json", "Output as JSON") | ||
| .action((options) => scanCI(options)); | ||
| program | ||
| .command("scan:ports") | ||
| .description("Check open ports and running processes") | ||
| .option("--json", "Output as JSON") | ||
| .action((options) => scanPorts(options)); | ||
| program | ||
| .command("scan:logs") | ||
| .description("Check Winston/Morgan logger configuration health") | ||
| .option("--json", "Output as JSON") | ||
| .action((options) => scanLogs(options)); | ||
| program | ||
| .command("scan:all") | ||
| .description("Run all scans and generate a full report") | ||
| .option("--ai", "AI summary (optional)") | ||
| .option("--json", "Output as JSON") | ||
| .option("--report", "Save HTML report to dep-inspector-report.html") | ||
| .action((options) => scanAll(options)); | ||
| program.parse(); |
+16
-25
@@ -1,2 +0,2 @@ | ||
| import { ChatGroq } from "@langchain/groq"; | ||
| import Groq from "groq-sdk"; | ||
| import { config } from "dotenv"; | ||
@@ -6,8 +6,2 @@ | ||
| const model = new ChatGroq({ | ||
| model: "llama-3.1-8b-instant", | ||
| temperature: 0.3, | ||
| }); | ||
| // In-memory cache | ||
| const cache = new Map<string, string>(); | ||
@@ -17,15 +11,14 @@ | ||
| try { | ||
| if (cache.has(input)) { | ||
| return cache.get(input)!; | ||
| } | ||
| if (cache.has(input)) return cache.get(input)!; | ||
| const client = new Groq({ apiKey: process.env["GROQ_API_KEY"] }); | ||
| const prompt = ` | ||
| You are a senior software security engineer and tester with high experience of at least 10 years. | ||
| Make sure to provide detailed and actionable insights for each issue, including potential impacts, security risks, and recommended fixes or alternatives. | ||
| You are a senior software security engineer with 10+ years of experience. | ||
| Provide detailed and actionable insights for each issue found. | ||
| Analyze the following dependency issues: | ||
| Analyze the following: | ||
| ${input} | ||
| Respond in this format: | ||
| 1. Issue | ||
@@ -38,15 +31,13 @@ 2. Impact | ||
| const res = await model.invoke(prompt); | ||
| const res = await client.chat.completions.create({ | ||
| model: "llama-3.3-70b-versatile", | ||
| messages: [{ role: "user", content: prompt }], | ||
| max_tokens: 500, | ||
| temperature: 0.3, | ||
| }); | ||
| let finalOutput = ""; | ||
| const output = res.choices[0]?.message?.content?.trim() ?? "โ ๏ธ No response"; | ||
| cache.set(input, output); | ||
| return output; | ||
| if (typeof res.content === "string") { | ||
| finalOutput = res.content.trim(); | ||
| } else { | ||
| finalOutput = JSON.stringify(res.content, null, 2); | ||
| } | ||
| cache.set(input, finalOutput); | ||
| return finalOutput; | ||
| } catch { | ||
@@ -53,0 +44,0 @@ return "โ ๏ธ AI analysis failed."; |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
109817
125.14%7
-12.5%82
95.24%1606
106.96%269
34.5%14
1300%9
28.57%+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed