@mr-aftab-ahmad-khan/depguard-cli 0.1.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 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/scanner.ts","../src/scoring.ts","../src/registry.ts","../src/top-packages.ts","../src/format.ts","../src/config.ts","../src/baseline.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { scan, audit } from \"./scanner.js\";\nimport { formatReport, formatJson, formatPackage } from \"./format.js\";\nimport { loadConfig } from \"./config.js\";\nimport { collectMaintainersFromDisk, saveBaseline } from \"./baseline.js\";\nimport { severityAtLeast } from \"./scoring.js\";\nimport type { RiskLevel } from \"./types.js\";\n\nconst program = new Command();\nprogram\n .name(\"depguard\")\n .description(\"Supply-chain security scanner for npm projects\")\n .option(\"--cwd <path>\", \"Working directory\", process.cwd());\n\nprogram\n .command(\"scan\", { isDefault: true })\n .description(\"Scan the current project's installed packages\")\n .option(\"--fail-on <severity>\", \"Exit 1 when any finding ≥ severity\")\n .option(\"--format <fmt>\", \"Output format: pretty | json\", \"pretty\")\n .option(\"--no-network\", \"Skip registry calls\")\n .option(\"--depth <depth>\", \"direct | all\", \"all\")\n .action(async (opts: { failOn?: string; format: string; network: boolean; depth: string }) => {\n const cwd = (program.opts() as { cwd: string }).cwd;\n const cfg = loadConfig(cwd);\n const failOn = (opts.failOn ?? cfg.failOn) as RiskLevel | undefined;\n const result = await scan({\n cwd,\n ignore: cfg.ignore ?? [],\n network: opts.network !== false,\n scanDepth: (opts.depth as \"direct\" | \"all\") ?? cfg.scanDepth ?? \"all\",\n ...(cfg.cacheTTL !== undefined ? { cacheTTL: cfg.cacheTTL } : {}),\n });\n\n process.stdout.write(opts.format === \"json\" ? formatJson(result) : formatReport(result));\n process.stdout.write(\"\\n\");\n\n if (failOn) {\n const hit = result.packages.some((p) => severityAtLeast(p.worstSeverity, failOn));\n if (hit) process.exit(1);\n }\n });\n\nprogram\n .command(\"baseline\")\n .description(\"Save current installed maintainers as the trusted baseline\")\n .action(() => {\n const cwd = (program.opts() as { cwd: string }).cwd;\n const baseline = collectMaintainersFromDisk(cwd);\n saveBaseline(baseline);\n process.stdout.write(`depguard: baseline saved (${Object.keys(baseline).length} packages)\\n`);\n });\n\nprogram\n .command(\"audit <package>\")\n .description(\"Audit a single package by name\")\n .action(async (name: string) => {\n const result = await audit(name);\n if (!result) {\n process.stderr.write(`depguard: could not fetch metadata for \"${name}\"\\n`);\n process.exit(1);\n }\n process.stdout.write(formatPackage(result) + \"\\n\");\n });\n\nprogram.parseAsync(process.argv).catch((err) => {\n process.stderr.write(`depguard: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import { existsSync, readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport {\n findTyposquatCandidate,\n maxSeverity,\n scoreInstallScript,\n} from \"./scoring.js\";\nimport { fetchPackument, type RegistryPackument } from \"./registry.js\";\nimport { TOP_PACKAGES } from \"./top-packages.js\";\nimport type {\n Finding,\n PackageRisk,\n RiskLevel,\n ScanOptions,\n ScanResult,\n} from \"./types.js\";\n\ninterface InstalledPackage {\n name: string;\n version: string;\n dir: string;\n pkgJson: {\n name?: string;\n version?: string;\n scripts?: Record<string, string>;\n maintainers?: Array<{ name: string; email?: string }>;\n };\n}\n\nfunction readJsonSafe(file: string): Record<string, unknown> | undefined {\n try {\n return JSON.parse(readFileSync(file, \"utf8\")) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n\nfunction findInstalled(root: string, depth: \"direct\" | \"all\"): InstalledPackage[] {\n const nm = join(root, \"node_modules\");\n if (!existsSync(nm)) return [];\n const out: InstalledPackage[] = [];\n const visited = new Set<string>();\n\n const walk = (dir: string, level: number): void => {\n if (visited.has(dir)) return;\n visited.add(dir);\n if (!existsSync(dir)) return;\n for (const entry of readdirSync(dir)) {\n if (entry.startsWith(\".\")) continue;\n const pkgDir = join(dir, entry);\n try {\n const stat = statSync(pkgDir);\n if (!stat.isDirectory()) continue;\n } catch {\n continue;\n }\n if (entry.startsWith(\"@\")) {\n for (const sub of readdirSync(pkgDir)) {\n handle(join(pkgDir, sub), `${entry}/${sub}`, level);\n }\n } else {\n handle(pkgDir, entry, level);\n }\n }\n };\n\n const handle = (pkgDir: string, name: string, level: number): void => {\n const pj = readJsonSafe(join(pkgDir, \"package.json\"));\n if (!pj) return;\n out.push({\n name: (pj.name as string) ?? name,\n version: (pj.version as string) ?? \"0.0.0\",\n dir: pkgDir,\n pkgJson: pj as InstalledPackage[\"pkgJson\"],\n });\n if (depth === \"all\" && existsSync(join(pkgDir, \"node_modules\"))) {\n walk(join(pkgDir, \"node_modules\"), level + 1);\n }\n };\n\n walk(nm, 0);\n return out;\n}\n\nfunction loadBaseline(): Record<string, { maintainers: string[] }> {\n const file = join(homedir(), \".depguard\", \"baseline.json\");\n if (!existsSync(file)) return {};\n try {\n return JSON.parse(readFileSync(file, \"utf8\")) as Record<string, { maintainers: string[] }>;\n } catch {\n return {};\n }\n}\n\nconst INSTALL_SCRIPT_FIELDS = [\"preinstall\", \"install\", \"postinstall\", \"preuninstall\", \"preprepare\", \"prepare\"] as const;\n\nfunction scriptFindings(pkg: InstalledPackage): Finding[] {\n const findings: Finding[] = [];\n const scripts = pkg.pkgJson.scripts ?? {};\n for (const field of INSTALL_SCRIPT_FIELDS) {\n const src = scripts[field];\n if (!src) continue;\n const { score, reasons } = scoreInstallScript(src);\n if (score === 0) continue;\n const severity: RiskLevel =\n score >= 8 ? \"critical\" : score >= 5 ? \"high\" : score >= 3 ? \"medium\" : \"low\";\n findings.push({\n rule: `install-script:${field}`,\n severity,\n message: `${field} script is suspicious (${reasons.join(\"; \")})`,\n score,\n });\n }\n return findings;\n}\n\nfunction maintainerFindings(\n pkg: InstalledPackage,\n packument: RegistryPackument | undefined,\n baseline: Record<string, { maintainers: string[] }>,\n): Finding[] {\n if (!packument?.maintainers) return [];\n const now = packument.maintainers.map((m) => m.name).sort();\n const known = baseline[pkg.name]?.maintainers;\n if (!known) return [];\n const added = now.filter((m) => !known.includes(m));\n if (added.length === 0) return [];\n return [{\n rule: \"maintainer-change\",\n severity: \"high\",\n message: `New maintainer(s) added since baseline: ${added.join(\", \")}`,\n score: 6,\n }];\n}\n\nfunction versionAnomalyFindings(\n pkg: InstalledPackage,\n packument: RegistryPackument | undefined,\n): Finding[] {\n if (!packument?.time) return [];\n const out: Finding[] = [];\n const time = packument.time;\n const versions = Object.keys(time).filter((k) => k !== \"created\" && k !== \"modified\");\n\n const sevenDays = 7 * 24 * 60 * 60 * 1000;\n const major = new Map<number, string[]>();\n for (const v of versions) {\n const m = /^(\\d+)\\./.exec(v);\n if (!m) continue;\n const key = Number(m[1]);\n const arr = major.get(key) ?? [];\n arr.push(v);\n major.set(key, arr);\n }\n const keys = [...major.keys()].sort((a, b) => a - b);\n for (let i = 1; i < keys.length; i++) {\n const prev = keys[i - 1]!;\n const cur = keys[i]!;\n const prevReleases = major.get(prev)!.map((v) => Date.parse(time[v] ?? \"\")).filter(Number.isFinite);\n const curReleases = major.get(cur)!.map((v) => Date.parse(time[v] ?? \"\")).filter(Number.isFinite);\n if (prevReleases.length === 0 || curReleases.length === 0) continue;\n const lastPrev = Math.max(...prevReleases);\n const firstCur = Math.min(...curReleases);\n if (firstCur - lastPrev < sevenDays) {\n out.push({\n rule: \"version-anomaly\",\n severity: \"medium\",\n message: `Major bump v${prev} → v${cur} within 7 days (potential takeover)`,\n score: 4,\n });\n }\n }\n\n const created = Date.parse(time.created ?? \"\");\n if (Number.isFinite(created) && Date.now() - created < 30 * 24 * 60 * 60 * 1000) {\n out.push({\n rule: \"young-package\",\n severity: \"low\",\n message: \"Package was first published less than 30 days ago\",\n score: 2,\n });\n }\n return out;\n}\n\nfunction typosquatFindings(name: string): Finding[] {\n const sug = findTyposquatCandidate(name, TOP_PACKAGES);\n if (!sug) return [];\n return [{\n rule: \"typosquat\",\n severity: \"critical\",\n message: `Name closely resembles \"${sug}\" — likely typosquat`,\n score: 10,\n }];\n}\n\nexport async function scan(options: ScanOptions = {}): Promise<ScanResult> {\n const cwd = options.cwd ?? process.cwd();\n const depth = options.scanDepth ?? \"all\";\n const ignore = new Set(options.ignore ?? []);\n const baseline = loadBaseline();\n const startedAt = new Date().toISOString();\n const packages: PackageRisk[] = [];\n\n const installed = findInstalled(cwd, depth).filter((p) => !ignore.has(p.name));\n for (const pkg of installed) {\n const findings: Finding[] = [];\n findings.push(...scriptFindings(pkg));\n findings.push(...typosquatFindings(pkg.name));\n\n if (options.network !== false) {\n const packument = await fetchPackument(pkg.name, { ttlMs: options.cacheTTL ?? 60 * 60 * 1000 });\n findings.push(...maintainerFindings(pkg, packument, baseline));\n findings.push(...versionAnomalyFindings(pkg, packument));\n }\n\n if (findings.length === 0) continue;\n packages.push({\n name: pkg.name,\n version: pkg.version,\n worstSeverity: maxSeverity(findings),\n totalScore: findings.reduce((s, f) => s + f.score, 0),\n findings,\n });\n }\n\n packages.sort((a, b) => b.totalScore - a.totalScore);\n\n return {\n packages,\n scannedAt: startedAt,\n generatedAt: new Date().toISOString(),\n totalPackages: installed.length,\n };\n}\n\nexport async function audit(name: string): Promise<PackageRisk | undefined> {\n const packument = await fetchPackument(name);\n if (!packument) return undefined;\n const findings: Finding[] = [];\n findings.push(...typosquatFindings(name));\n return {\n name,\n version: packument[\"dist-tags\"]?.latest ?? \"0.0.0\",\n worstSeverity: maxSeverity(findings),\n totalScore: findings.reduce((s, f) => s + f.score, 0),\n findings,\n };\n}\n","import type { Finding, RiskLevel } from \"./types.js\";\n\nconst SEVERITY_ORDER: Record<RiskLevel, number> = {\n info: 0,\n low: 1,\n medium: 2,\n high: 3,\n critical: 4,\n};\n\nexport function maxSeverity(findings: Finding[]): RiskLevel {\n let worst: RiskLevel = \"info\";\n for (const f of findings) {\n if (SEVERITY_ORDER[f.severity] > SEVERITY_ORDER[worst]) worst = f.severity;\n }\n return worst;\n}\n\nexport function severityAtLeast(a: RiskLevel, b: RiskLevel): boolean {\n return SEVERITY_ORDER[a] >= SEVERITY_ORDER[b];\n}\n\nexport interface ScriptScore {\n score: number;\n reasons: string[];\n}\n\nexport function scoreInstallScript(script: string): ScriptScore {\n const reasons: string[] = [];\n let score = 0;\n const lower = script.toLowerCase();\n\n if (/(curl|wget)\\s+[^|]+\\|\\s*(sh|bash|zsh|node)/i.test(script)) {\n score += 5;\n reasons.push(\"curl/wget piped to a shell or node\");\n }\n if (/\\bbase64\\b/.test(lower) && /\\b(eval|exec)\\b/.test(lower)) {\n score += 5;\n reasons.push(\"base64 combined with eval/exec\");\n }\n if (/\\bnew\\s+Function\\s*\\(/.test(script)) {\n score += 3;\n reasons.push(\"dynamic `new Function()` invocation\");\n }\n if (/process\\.env(\\.|\\[)/.test(script)) {\n score += 2;\n reasons.push(\"reads process.env at install time\");\n }\n if (/https?:\\/\\/(?!registry\\.npmjs\\.org|nodejs\\.org|github\\.com)[^\\s'\"`]+/i.test(script)) {\n score += 3;\n reasons.push(\"network call to a non-trusted domain\");\n }\n if (/[A-Za-z0-9+/]{200,}={0,2}/.test(script)) {\n score += 3;\n reasons.push(\"very long opaque token (possible obfuscation)\");\n }\n if (script.length > 300 && !script.includes(\"\\n\")) {\n score += 2;\n reasons.push(\"obfuscated one-liner > 300 chars\");\n }\n\n return { score: Math.min(score, 10), reasons };\n}\n\nexport function levenshtein(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n const prev = new Array<number>(b.length + 1);\n const curr = new Array<number>(b.length + 1);\n for (let j = 0; j <= b.length; j++) prev[j] = j;\n for (let i = 1; i <= a.length; i++) {\n curr[0] = i;\n for (let j = 1; j <= b.length; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n curr[j] = Math.min(curr[j - 1]! + 1, prev[j]! + 1, prev[j - 1]! + cost);\n }\n for (let j = 0; j <= b.length; j++) prev[j] = curr[j]!;\n }\n return prev[b.length]!;\n}\n\nexport function findTyposquatCandidate(\n name: string,\n topPackages: readonly string[],\n): string | undefined {\n if (topPackages.includes(name)) return undefined;\n for (const top of topPackages) {\n if (Math.abs(top.length - name.length) > 2) continue;\n const dist = levenshtein(name, top);\n if (dist > 0 && dist <= 2) return top;\n }\n return undefined;\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface RegistryPackument {\n name: string;\n maintainers?: Array<{ name: string; email?: string }>;\n time?: Record<string, string>;\n versions?: Record<string, unknown>;\n \"dist-tags\"?: Record<string, string>;\n}\n\ninterface CacheEntry {\n expiresAt: number;\n data: RegistryPackument;\n}\n\nconst CACHE_DIR = join(homedir(), \".depguard\", \"cache\");\n\nfunction cacheFile(name: string): string {\n const safe = name.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n return join(CACHE_DIR, `${safe}.json`);\n}\n\nfunction readCache(name: string): RegistryPackument | undefined {\n const file = cacheFile(name);\n if (!existsSync(file)) return undefined;\n try {\n const entry = JSON.parse(readFileSync(file, \"utf8\")) as CacheEntry;\n if (entry.expiresAt > Date.now()) return entry.data;\n } catch {\n return undefined;\n }\n return undefined;\n}\n\nfunction writeCache(name: string, data: RegistryPackument, ttlMs: number): void {\n if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true });\n const entry: CacheEntry = { expiresAt: Date.now() + ttlMs, data };\n writeFileSync(cacheFile(name), JSON.stringify(entry), \"utf8\");\n}\n\nexport interface FetchOptions {\n ttlMs?: number;\n fetcher?: (url: string) => Promise<{ ok: boolean; status: number; json(): Promise<unknown> }>;\n}\n\nexport async function fetchPackument(\n name: string,\n opts: FetchOptions = {},\n): Promise<RegistryPackument | undefined> {\n const ttl = opts.ttlMs ?? 60 * 60 * 1000;\n const cached = readCache(name);\n if (cached) return cached;\n const fetcher = opts.fetcher ?? (globalThis.fetch as typeof fetch);\n if (!fetcher) return undefined;\n try {\n const res = await fetcher(`https://registry.npmjs.org/${encodeURIComponent(name).replace(\"%40\", \"@\")}`);\n if (!res.ok) return undefined;\n const data = (await res.json()) as RegistryPackument;\n writeCache(name, data, ttl);\n return data;\n } catch {\n return undefined;\n }\n}\n","/**\n * Hardcoded top npm packages used for typosquat detection.\n * Updated periodically; sourced from npm download counts.\n */\nexport const TOP_PACKAGES: readonly string[] = [\n \"react\", \"react-dom\", \"next\", \"vue\", \"svelte\", \"angular\", \"lodash\", \"axios\",\n \"express\", \"fastify\", \"koa\", \"hono\", \"moment\", \"date-fns\", \"dayjs\", \"luxon\",\n \"typescript\", \"ts-node\", \"tsx\", \"tsup\", \"esbuild\", \"webpack\", \"vite\", \"rollup\",\n \"parcel\", \"babel\", \"eslint\", \"prettier\", \"jest\", \"vitest\", \"mocha\", \"chai\",\n \"sinon\", \"ava\", \"cypress\", \"playwright\", \"puppeteer\", \"redux\", \"zustand\",\n \"jotai\", \"mobx\", \"recoil\", \"rxjs\", \"graphql\", \"apollo-client\", \"apollo-server\",\n \"prisma\", \"drizzle-orm\", \"kysely\", \"knex\", \"typeorm\", \"sequelize\", \"mongoose\",\n \"pg\", \"mysql2\", \"sqlite3\", \"better-sqlite3\", \"redis\", \"ioredis\", \"bullmq\",\n \"kafkajs\", \"amqplib\", \"socket.io\", \"ws\", \"uws\", \"polka\", \"fastify\", \"h3\",\n \"commander\", \"yargs\", \"minimist\", \"inquirer\", \"prompts\", \"ora\", \"chalk\",\n \"picocolors\", \"kleur\", \"boxen\", \"cli-table3\", \"figlet\", \"fs-extra\", \"fast-glob\",\n \"glob\", \"globby\", \"rimraf\", \"del\", \"execa\", \"shelljs\", \"cross-env\", \"dotenv\",\n \"joi\", \"yup\", \"zod\", \"ajv\", \"valibot\", \"superstruct\", \"io-ts\", \"class-validator\",\n \"passport\", \"jsonwebtoken\", \"jose\", \"bcrypt\", \"bcryptjs\", \"argon2\", \"uuid\",\n \"nanoid\", \"ulid\", \"cuid\", \"shortid\", \"ms\", \"humanize-duration\", \"pluralize\",\n \"marked\", \"remark\", \"rehype\", \"showdown\", \"turndown\", \"cheerio\", \"jsdom\",\n \"playwright-core\", \"node-fetch\", \"got\", \"ky\", \"openai\", \"anthropic\", \"ai\",\n \"@modelcontextprotocol/sdk\", \"langchain\", \"llamaindex\", \"tiktoken\", \"gpt-3-encoder\",\n \"tailwindcss\", \"postcss\", \"autoprefixer\", \"sass\", \"stylus\", \"less\", \"styled-components\",\n \"@emotion/react\", \"@emotion/styled\", \"framer-motion\", \"react-router-dom\",\n \"react-query\", \"@tanstack/react-query\", \"swr\", \"trpc\", \"@trpc/server\",\n \"@trpc/client\", \"winston\", \"pino\", \"bunyan\", \"morgan\", \"debug\", \"sharp\", \"jimp\",\n \"canvas\", \"puppeteer-core\", \"playwright-chromium\", \"exceljs\", \"xlsx\", \"pdfkit\",\n \"pdf-lib\", \"pdfmake\", \"node-cron\", \"agenda\", \"bull\", \"node-schedule\",\n \"@sentry/node\", \"@sentry/browser\", \"@sentry/react\", \"stripe\", \"@stripe/stripe-js\",\n \"twilio\", \"nodemailer\", \"resend\", \"aws-sdk\", \"@aws-sdk/client-s3\", \"@aws-sdk/client-dynamodb\",\n \"googleapis\", \"firebase\", \"firebase-admin\", \"supabase\", \"@supabase/supabase-js\",\n \"mongodb\", \"mongoose\", \"elasticsearch\", \"@elastic/elasticsearch\", \"puppeteer-extra\",\n \"playwright-extra\", \"discord.js\", \"telegraf\", \"node-telegram-bot-api\",\n \"slack-sdk\", \"@slack/web-api\", \"fast-xml-parser\", \"xml2js\", \"yaml\", \"toml\",\n \"ini\", \"msgpack-lite\", \"protobufjs\", \"grpc\", \"@grpc/grpc-js\",\n];\n","import pc from \"picocolors\";\nimport type { PackageRisk, RiskLevel, ScanResult } from \"./types.js\";\n\nconst sevColor: Record<RiskLevel, (s: string) => string> = {\n info: pc.gray,\n low: pc.blue,\n medium: pc.yellow,\n high: pc.magenta,\n critical: pc.red,\n};\n\nexport function formatReport(result: ScanResult): string {\n if (result.packages.length === 0) {\n return pc.green(`✓ No risk findings across ${result.totalPackages} packages`);\n }\n const lines: string[] = [];\n lines.push(pc.bold(`depguard report — ${result.packages.length} package(s) with findings (of ${result.totalPackages} scanned)`));\n for (const p of result.packages) {\n lines.push(\"\");\n lines.push(` ${sevColor[p.worstSeverity](p.worstSeverity.toUpperCase())} ${pc.bold(p.name)}@${p.version} score=${p.totalScore}`);\n for (const f of p.findings) {\n lines.push(` - ${sevColor[f.severity](f.severity)} ${f.rule}: ${f.message}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\nexport function formatJson(result: ScanResult): string {\n return JSON.stringify(result, null, 2);\n}\n\nexport function formatPackage(p: PackageRisk): string {\n const lines: string[] = [];\n lines.push(`${sevColor[p.worstSeverity](p.worstSeverity.toUpperCase())} ${pc.bold(p.name)}@${p.version} score=${p.totalScore}`);\n for (const f of p.findings) {\n lines.push(` - ${sevColor[f.severity](f.severity)} ${f.rule}: ${f.message}`);\n }\n return lines.join(\"\\n\");\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { RiskLevel } from \"./types.js\";\n\nexport interface DepguardConfig {\n failOn?: RiskLevel;\n ignore?: string[];\n baselinePath?: string;\n cacheTTL?: number;\n scanDepth?: \"direct\" | \"all\";\n}\n\nexport function loadConfig(cwd: string): DepguardConfig {\n const file = join(cwd, \".depguardrc.json\");\n if (!existsSync(file)) return {};\n try {\n return JSON.parse(readFileSync(file, \"utf8\")) as DepguardConfig;\n } catch {\n return {};\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport interface BaselineEntry {\n maintainers: string[];\n version: string;\n}\nexport type Baseline = Record<string, BaselineEntry>;\n\nconst BASELINE_DIR = join(homedir(), \".depguard\");\nconst BASELINE_FILE = join(BASELINE_DIR, \"baseline.json\");\n\nexport function loadBaseline(): Baseline {\n if (!existsSync(BASELINE_FILE)) return {};\n try {\n return JSON.parse(readFileSync(BASELINE_FILE, \"utf8\")) as Baseline;\n } catch {\n return {};\n }\n}\n\nexport function saveBaseline(b: Baseline): void {\n if (!existsSync(BASELINE_DIR)) mkdirSync(BASELINE_DIR, { recursive: true });\n writeFileSync(BASELINE_FILE, JSON.stringify(b, null, 2) + \"\\n\", \"utf8\");\n}\n\nexport function collectMaintainersFromDisk(cwd: string): Baseline {\n const baseline: Baseline = {};\n const nm = join(cwd, \"node_modules\");\n if (!existsSync(nm)) return baseline;\n for (const entry of readdirSync(nm)) {\n if (entry.startsWith(\".\")) continue;\n const dir = join(nm, entry);\n try {\n const pkg = JSON.parse(readFileSync(join(dir, \"package.json\"), \"utf8\")) as {\n name?: string;\n version?: string;\n maintainers?: Array<{ name: string }>;\n };\n if (pkg.name && pkg.version) {\n baseline[pkg.name] = {\n maintainers: (pkg.maintainers ?? []).map((m) => m.name).sort(),\n version: pkg.version,\n };\n }\n } catch {\n // ignore unreadable packages\n }\n }\n return baseline;\n}\n"],"mappings":"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","names":["import_node_fs","import_node_path","import_node_os","pc","import_node_fs","import_node_path","import_node_fs","import_node_path","import_node_os"]}
package/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.js ADDED
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ audit,
4
+ formatJson,
5
+ formatPackage,
6
+ formatReport,
7
+ loadConfig,
8
+ scan,
9
+ severityAtLeast
10
+ } from "./chunk-BH7GFJ3G.js";
11
+
12
+ // src/cli.ts
13
+ import { Command } from "commander";
14
+
15
+ // src/baseline.ts
16
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "fs";
17
+ import { join } from "path";
18
+ import { homedir } from "os";
19
+ var BASELINE_DIR = join(homedir(), ".depguard");
20
+ var BASELINE_FILE = join(BASELINE_DIR, "baseline.json");
21
+ function saveBaseline(b) {
22
+ if (!existsSync(BASELINE_DIR)) mkdirSync(BASELINE_DIR, { recursive: true });
23
+ writeFileSync(BASELINE_FILE, JSON.stringify(b, null, 2) + "\n", "utf8");
24
+ }
25
+ function collectMaintainersFromDisk(cwd) {
26
+ const baseline = {};
27
+ const nm = join(cwd, "node_modules");
28
+ if (!existsSync(nm)) return baseline;
29
+ for (const entry of readdirSync(nm)) {
30
+ if (entry.startsWith(".")) continue;
31
+ const dir = join(nm, entry);
32
+ try {
33
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
34
+ if (pkg.name && pkg.version) {
35
+ baseline[pkg.name] = {
36
+ maintainers: (pkg.maintainers ?? []).map((m) => m.name).sort(),
37
+ version: pkg.version
38
+ };
39
+ }
40
+ } catch {
41
+ }
42
+ }
43
+ return baseline;
44
+ }
45
+
46
+ // src/cli.ts
47
+ var program = new Command();
48
+ program.name("depguard").description("Supply-chain security scanner for npm projects").option("--cwd <path>", "Working directory", process.cwd());
49
+ program.command("scan", { isDefault: true }).description("Scan the current project's installed packages").option("--fail-on <severity>", "Exit 1 when any finding \u2265 severity").option("--format <fmt>", "Output format: pretty | json", "pretty").option("--no-network", "Skip registry calls").option("--depth <depth>", "direct | all", "all").action(async (opts) => {
50
+ const cwd = program.opts().cwd;
51
+ const cfg = loadConfig(cwd);
52
+ const failOn = opts.failOn ?? cfg.failOn;
53
+ const result = await scan({
54
+ cwd,
55
+ ignore: cfg.ignore ?? [],
56
+ network: opts.network !== false,
57
+ scanDepth: opts.depth ?? cfg.scanDepth ?? "all",
58
+ ...cfg.cacheTTL !== void 0 ? { cacheTTL: cfg.cacheTTL } : {}
59
+ });
60
+ process.stdout.write(opts.format === "json" ? formatJson(result) : formatReport(result));
61
+ process.stdout.write("\n");
62
+ if (failOn) {
63
+ const hit = result.packages.some((p) => severityAtLeast(p.worstSeverity, failOn));
64
+ if (hit) process.exit(1);
65
+ }
66
+ });
67
+ program.command("baseline").description("Save current installed maintainers as the trusted baseline").action(() => {
68
+ const cwd = program.opts().cwd;
69
+ const baseline = collectMaintainersFromDisk(cwd);
70
+ saveBaseline(baseline);
71
+ process.stdout.write(`depguard: baseline saved (${Object.keys(baseline).length} packages)
72
+ `);
73
+ });
74
+ program.command("audit <package>").description("Audit a single package by name").action(async (name) => {
75
+ const result = await audit(name);
76
+ if (!result) {
77
+ process.stderr.write(`depguard: could not fetch metadata for "${name}"
78
+ `);
79
+ process.exit(1);
80
+ }
81
+ process.stdout.write(formatPackage(result) + "\n");
82
+ });
83
+ program.parseAsync(process.argv).catch((err) => {
84
+ process.stderr.write(`depguard: ${err instanceof Error ? err.message : String(err)}
85
+ `);
86
+ process.exit(1);
87
+ });
88
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/baseline.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { scan, audit } from \"./scanner.js\";\nimport { formatReport, formatJson, formatPackage } from \"./format.js\";\nimport { loadConfig } from \"./config.js\";\nimport { collectMaintainersFromDisk, saveBaseline } from \"./baseline.js\";\nimport { severityAtLeast } from \"./scoring.js\";\nimport type { RiskLevel } from \"./types.js\";\n\nconst program = new Command();\nprogram\n .name(\"depguard\")\n .description(\"Supply-chain security scanner for npm projects\")\n .option(\"--cwd <path>\", \"Working directory\", process.cwd());\n\nprogram\n .command(\"scan\", { isDefault: true })\n .description(\"Scan the current project's installed packages\")\n .option(\"--fail-on <severity>\", \"Exit 1 when any finding ≥ severity\")\n .option(\"--format <fmt>\", \"Output format: pretty | json\", \"pretty\")\n .option(\"--no-network\", \"Skip registry calls\")\n .option(\"--depth <depth>\", \"direct | all\", \"all\")\n .action(async (opts: { failOn?: string; format: string; network: boolean; depth: string }) => {\n const cwd = (program.opts() as { cwd: string }).cwd;\n const cfg = loadConfig(cwd);\n const failOn = (opts.failOn ?? cfg.failOn) as RiskLevel | undefined;\n const result = await scan({\n cwd,\n ignore: cfg.ignore ?? [],\n network: opts.network !== false,\n scanDepth: (opts.depth as \"direct\" | \"all\") ?? cfg.scanDepth ?? \"all\",\n ...(cfg.cacheTTL !== undefined ? { cacheTTL: cfg.cacheTTL } : {}),\n });\n\n process.stdout.write(opts.format === \"json\" ? formatJson(result) : formatReport(result));\n process.stdout.write(\"\\n\");\n\n if (failOn) {\n const hit = result.packages.some((p) => severityAtLeast(p.worstSeverity, failOn));\n if (hit) process.exit(1);\n }\n });\n\nprogram\n .command(\"baseline\")\n .description(\"Save current installed maintainers as the trusted baseline\")\n .action(() => {\n const cwd = (program.opts() as { cwd: string }).cwd;\n const baseline = collectMaintainersFromDisk(cwd);\n saveBaseline(baseline);\n process.stdout.write(`depguard: baseline saved (${Object.keys(baseline).length} packages)\\n`);\n });\n\nprogram\n .command(\"audit <package>\")\n .description(\"Audit a single package by name\")\n .action(async (name: string) => {\n const result = await audit(name);\n if (!result) {\n process.stderr.write(`depguard: could not fetch metadata for \"${name}\"\\n`);\n process.exit(1);\n }\n process.stdout.write(formatPackage(result) + \"\\n\");\n });\n\nprogram.parseAsync(process.argv).catch((err) => {\n process.stderr.write(`depguard: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport interface BaselineEntry {\n maintainers: string[];\n version: string;\n}\nexport type Baseline = Record<string, BaselineEntry>;\n\nconst BASELINE_DIR = join(homedir(), \".depguard\");\nconst BASELINE_FILE = join(BASELINE_DIR, \"baseline.json\");\n\nexport function loadBaseline(): Baseline {\n if (!existsSync(BASELINE_FILE)) return {};\n try {\n return JSON.parse(readFileSync(BASELINE_FILE, \"utf8\")) as Baseline;\n } catch {\n return {};\n }\n}\n\nexport function saveBaseline(b: Baseline): void {\n if (!existsSync(BASELINE_DIR)) mkdirSync(BASELINE_DIR, { recursive: true });\n writeFileSync(BASELINE_FILE, JSON.stringify(b, null, 2) + \"\\n\", \"utf8\");\n}\n\nexport function collectMaintainersFromDisk(cwd: string): Baseline {\n const baseline: Baseline = {};\n const nm = join(cwd, \"node_modules\");\n if (!existsSync(nm)) return baseline;\n for (const entry of readdirSync(nm)) {\n if (entry.startsWith(\".\")) continue;\n const dir = join(nm, entry);\n try {\n const pkg = JSON.parse(readFileSync(join(dir, \"package.json\"), \"utf8\")) as {\n name?: string;\n version?: string;\n maintainers?: Array<{ name: string }>;\n };\n if (pkg.name && pkg.version) {\n baseline[pkg.name] = {\n maintainers: (pkg.maintainers ?? []).map((m) => m.name).sort(),\n version: pkg.version,\n };\n }\n } catch {\n // ignore unreadable packages\n }\n }\n return baseline;\n}\n"],"mappings":"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","names":[]}