vigile-scan 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +50 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../package.json","../src/index.ts","../src/discovery/claude-desktop.ts","../src/discovery/utils.ts","../src/discovery/cursor.ts","../src/discovery/claude-code.ts","../src/discovery/windsurf.ts","../src/discovery/vscode.ts","../src/discovery/openclaw.ts","../src/discovery/skills.ts","../src/discovery/index.ts","../src/scanner/patterns.ts","../src/scoring/trust-score.ts","../src/scanner/index.ts","../src/scanner/skill-patterns.ts","../src/scanner/skill-scanner.ts","../src/sentinel/sentinel.ts","../src/sentinel/sentinel-patterns.ts","../src/output/terminal.ts","../src/output/json.ts","../src/api/auth.ts","../src/api/client.ts","../src/scanner/baas/secret-patterns.ts","../src/scanner/baas/bundle-analyzer.ts","../src/scanner/baas/supabase-scanner.ts","../src/scanner/baas/firebase-scanner.ts","../src/scanner/baas/cve-detector.ts","../src/scanner/baas/vibe-app-scanner.ts"],"sourcesContent":["{\r\n \"name\": \"vigile-scan\",\r\n \"version\": \"2.1.0\",\r\n \"description\": \"Security scanner for AI agent tools — detect tool poisoning, permission abuse, and supply chain attacks in MCP servers and agent skills\",\r\n \"main\": \"dist/index.js\",\r\n \"bin\": {\r\n \"vigile-scan\": \"dist/index.js\",\r\n \"vigile\": \"dist/index.js\"\r\n },\r\n \"files\": [\r\n \"dist\",\r\n \"README.md\",\r\n \"LICENSE\"\r\n ],\r\n \"scripts\": {\r\n \"build\": \"tsup && chmod +x dist/index.js\",\r\n \"dev\": \"tsup --watch\",\r\n \"start\": \"node dist/index.js\",\r\n \"lint\": \"eslint src/\",\r\n \"typecheck\": \"tsc --noEmit\",\r\n \"test\": \"vitest run\",\r\n \"test:watch\": \"vitest\",\r\n \"test:coverage\": \"vitest run --coverage\",\r\n \"prepublishOnly\": \"npm run build\"\r\n },\r\n \"keywords\": [\r\n \"mcp\",\r\n \"security\",\r\n \"ai-agent\",\r\n \"scanner\",\r\n \"trust\",\r\n \"model-context-protocol\",\r\n \"tool-poisoning\",\r\n \"ai-security\",\r\n \"agent-skills\",\r\n \"skill-scanning\",\r\n \"prompt-injection\",\r\n \"data-exfiltration\",\r\n \"claude\",\r\n \"cursor\",\r\n \"copilot\"\r\n ],\r\n \"author\": \"Vigile AI\",\r\n \"license\": \"Apache-2.0\",\r\n \"homepage\": \"https://vigile.dev\",\r\n \"repository\": {\r\n \"type\": \"git\",\r\n \"url\": \"git+https://github.com/Vigile-ai/vigile-scan.git\"\r\n },\r\n \"bugs\": {\r\n \"url\": \"https://github.com/Vigile-ai/vigile-scan/issues\"\r\n },\r\n \"publishConfig\": {\r\n \"access\": \"public\"\r\n },\r\n \"engines\": {\r\n \"node\": \">=18.0.0\"\r\n },\r\n \"dependencies\": {\r\n \"chalk\": \"^5.3.0\",\r\n \"commander\": \"^12.1.0\",\r\n \"glob\": \"^11.0.0\",\r\n \"ora\": \"^8.1.0\"\r\n },\r\n \"devDependencies\": {\r\n \"@types/node\": \"^20.11.0\",\r\n \"tsup\": \"^8.0.0\",\r\n \"typescript\": \"^5.4.0\",\r\n \"vitest\": \"^2.0.0\"\r\n }\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Main Entry Point\r\n// ============================================================\r\n// Usage: npx vigile-scan [options]\r\n//\r\n// The AI agent security scanner. Discovers MCP server configs\r\n// and agent skill files on your machine, scans them for\r\n// security issues, and outputs trust scores.\r\n//\r\n// v0.2.0: Added API integration — scan results upload to the\r\n// Vigile registry, authentication, and Sentinel API sessions.\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport { writeFile } from 'fs/promises';\r\nimport { discoverAllServers, discoverAllSkills } from './discovery/index.js';\r\nimport { scanServer } from './scanner/index.js';\r\nimport { scanSkill } from './scanner/skill-scanner.js';\r\nimport {\r\n SentinelEngine,\r\n getSentinelFeatures,\r\n SENTINEL_MARKETING,\r\n} from './sentinel/index.js';\r\nimport type { SentinelReport, NetworkEvent } from './sentinel/index.js';\r\nimport {\r\n printBanner,\r\n printServerResult,\r\n printSkillResult,\r\n printSummary,\r\n printSentinelReport,\r\n printSentinelUpgrade,\r\n printNoServersFound,\r\n printNoSkillsFound,\r\n printNothingFound,\r\n printAuthStatus,\r\n printAuthLoginSuccess,\r\n printUploadSuccess,\r\n printUploadSkipped,\r\n} from './output/terminal.js';\r\nimport { formatJSON } from './output/json.js';\r\nimport { getAuthenticatedClient, authLogin, authStatus, authLogout } from './api/auth.js';\r\nimport { scanVibeApp, scanSupabase, scanFirebase } from './scanner/baas/index.js';\r\nimport type { VigileApiClient } from './api/client.js';\r\nimport type {\r\n ScanOptions,\r\n ScanSummary,\r\n ScanResult,\r\n SkillScanResult,\r\n MCPClient,\r\n UploadSummary,\r\n} from './types/index.js';\r\n\r\n// Read version dynamically so it never goes stale.\r\n// eslint-disable-next-line @typescript-eslint/no-require-imports\r\nconst VERSION: string = require('../package.json').version;\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name('vigile-scan')\r\n .description(\r\n 'Security scanner for AI agent tools — detect tool poisoning, permission abuse, and supply chain attacks in MCP servers and agent skills',\r\n )\r\n .version(VERSION);\r\n\r\n// Shared options for scan commands\r\nfunction addScanOptions(cmd: Command): Command {\r\n return cmd\r\n .option('-j, --json', 'Output results as JSON')\r\n .option('-v, --verbose', 'Show detailed findings and score breakdown')\r\n .option('-c, --config <path>', 'Path to a custom MCP config file')\r\n .option('-o, --output <path>', 'Write results to a file')\r\n .option(\r\n '--client <client>',\r\n 'Only scan a specific client (claude-desktop, cursor, claude-code, windsurf, vscode, openclaw)',\r\n )\r\n .option('-s, --skills', 'Scan agent skills only (SKILL.md, .mdc rules, CLAUDE.md, etc.)')\r\n .option('-a, --all', 'Scan both MCP servers and agent skills')\r\n .option('--sentinel', 'Enable Sentinel runtime monitoring (Pro+ feature)')\r\n .option('--sentinel-server <name>', 'Monitor a specific MCP server by name')\r\n .option('--sentinel-duration <seconds>', 'Monitoring duration in seconds (default: 120)', parseInt)\r\n .option('--no-upload', 'Skip uploading scan results to Vigile API')\r\n .option('--supabase <url>', 'Scan a Supabase project URL for RLS issues and exposed keys')\r\n .option('--supabase-key <key>', 'Supabase anon key (auto-detected from bundles if omitted)')\r\n .option('--firebase <url>', 'Scan a Firebase project URL for rules issues and exposed keys')\r\n .option('--app <url>', 'Scan a deployed web app for exposed secrets and BaaS misconfigs');\r\n}\r\n\r\naddScanOptions(\r\n program\r\n .command('scan')\r\n .description('Scan MCP server configurations and agent skill files on this machine'),\r\n).action(async (options: ScanOptions) => {\r\n await runScan(options);\r\n});\r\n\r\n// Default command (no subcommand) also runs scan\r\naddScanOptions(program).action(async (options: ScanOptions) => {\r\n if (!process.argv.slice(2).includes('scan') && !process.argv.slice(2).includes('auth')) {\r\n await runScan(options);\r\n }\r\n});\r\n\r\n// ── Auth subcommand ──\r\nconst authCmd = program\r\n .command('auth')\r\n .description('Manage Vigile API authentication');\r\n\r\nauthCmd\r\n .command('login')\r\n .description('Authenticate with your Vigile API key')\r\n .argument('[token]', 'API key (vgl_...) or JWT token. If omitted, reads from VIGILE_TOKEN env var.')\r\n .action(async (token?: string) => {\r\n const resolvedToken = token || process.env.VIGILE_TOKEN;\r\n if (!resolvedToken) {\r\n console.log(chalk.red(' No token provided. Pass a token argument or set VIGILE_TOKEN env var.'));\r\n console.log(chalk.gray(' Usage: vigile-scan auth login <vgl_your_api_key>'));\r\n console.log(chalk.gray(' Get an API key at https://vigile.dev/account'));\r\n process.exit(1);\r\n }\r\n\r\n const spinner = ora('Validating token...').start();\r\n const result = await authLogin(resolvedToken);\r\n\r\n if (result.success && result.user) {\r\n spinner.succeed('Token validated');\r\n printAuthLoginSuccess(result.user.email, result.user.tier);\r\n } else {\r\n spinner.fail('Authentication failed');\r\n console.log(chalk.red(` Error: ${result.error || 'Unknown error'}`));\r\n process.exit(1);\r\n }\r\n });\r\n\r\nauthCmd\r\n .command('status')\r\n .description('Show current authentication status')\r\n .action(async () => {\r\n const result = await authStatus();\r\n printAuthStatus({\r\n authenticated: result.authenticated,\r\n source: result.source,\r\n email: result.user?.email,\r\n tier: result.user?.tier,\r\n name: result.user?.name || undefined,\r\n error: result.error,\r\n });\r\n });\r\n\r\nauthCmd\r\n .command('logout')\r\n .description('Clear stored credentials')\r\n .action(async () => {\r\n await authLogout();\r\n console.log(chalk.green(' Logged out. Credentials removed from ~/.vigile/config.json'));\r\n console.log('');\r\n });\r\n\r\n// ============================================================\r\n// Main Scan Flow\r\n// ============================================================\r\n\r\nasync function runScan(options: ScanOptions): Promise<void> {\r\n const isJSON = options.json ?? false;\r\n const scanMCP = !options.skills; // Scan MCP unless --skills only\r\n const scanSkills = options.skills || options.all; // Scan skills if --skills or --all\r\n\r\n if (!isJSON) {\r\n printBanner();\r\n }\r\n\r\n const results: ScanResult[] = [];\r\n const skillResults: SkillScanResult[] = [];\r\n\r\n // ── Step 1: Discover & scan MCP servers ──\r\n if (scanMCP) {\r\n const spinner = isJSON ? null : ora('Discovering MCP configurations...').start();\r\n const discovery = await discoverAllServers(options.client as MCPClient | undefined);\r\n\r\n if (discovery.servers.length === 0) {\r\n spinner?.succeed('No MCP server configurations found');\r\n } else {\r\n spinner?.succeed(\r\n `Found ${discovery.servers.length} MCP server(s) across ${discovery.configsFound} config file(s)`,\r\n );\r\n\r\n const scanSpinner = isJSON ? null : ora('Scanning MCP servers...').start();\r\n for (const server of discovery.servers) {\r\n const result = await scanServer(server);\r\n results.push(result);\r\n }\r\n scanSpinner?.succeed(`Scanned ${results.length} MCP server(s)`);\r\n }\r\n }\r\n\r\n // ── Step 2: Discover & scan agent skills ──\r\n if (scanSkills) {\r\n const spinner = isJSON ? null : ora('Discovering agent skill files...').start();\r\n const skillDiscovery = await discoverAllSkills();\r\n\r\n if (skillDiscovery.skills.length === 0) {\r\n spinner?.succeed('No agent skill files found');\r\n } else {\r\n spinner?.succeed(\r\n `Found ${skillDiscovery.skills.length} skill file(s) across ${skillDiscovery.locationsFound} location(s)`,\r\n );\r\n\r\n const scanSpinner = isJSON ? null : ora('Scanning agent skills...').start();\r\n for (const skill of skillDiscovery.skills) {\r\n const result = await scanSkill(skill);\r\n skillResults.push(result);\r\n }\r\n scanSpinner?.succeed(`Scanned ${skillResults.length} skill file(s)`);\r\n }\r\n }\r\n\r\n // ── Check if anything was found ──\r\n if (results.length === 0 && skillResults.length === 0) {\r\n if (!isJSON) {\r\n if (scanMCP && !scanSkills) {\r\n printNoServersFound();\r\n } else if (scanSkills && !scanMCP) {\r\n printNoSkillsFound();\r\n } else {\r\n printNothingFound();\r\n }\r\n } else {\r\n console.log(JSON.stringify({ servers: [], skills: [], message: 'Nothing found to scan' }));\r\n }\r\n return;\r\n }\r\n\r\n // ── Step 3: Build combined summary ──\r\n const allResults = [...results];\r\n const allSkillResults = [...skillResults];\r\n\r\n // Combine trust level counts from both MCP and skill results\r\n const allTrustLevels = [\r\n ...results.map((r) => r.trustLevel),\r\n ...skillResults.map((r) => r.trustLevel),\r\n ];\r\n\r\n // Combine finding severities from both MCP and skill results\r\n const allFindings = [\r\n ...results.flatMap((r) => r.findings),\r\n ...skillResults.flatMap((r) => r.findings),\r\n ];\r\n\r\n const summary: ScanSummary = {\r\n totalServers: allResults.length,\r\n totalSkills: allSkillResults.length,\r\n byTrustLevel: {\r\n trusted: allTrustLevels.filter((l) => l === 'trusted').length,\r\n caution: allTrustLevels.filter((l) => l === 'caution').length,\r\n risky: allTrustLevels.filter((l) => l === 'risky').length,\r\n dangerous: allTrustLevels.filter((l) => l === 'dangerous').length,\r\n },\r\n bySeverity: {\r\n critical: allFindings.filter((f) => f.severity === 'critical').length,\r\n high: allFindings.filter((f) => f.severity === 'high').length,\r\n medium: allFindings.filter((f) => f.severity === 'medium').length,\r\n low: allFindings.filter((f) => f.severity === 'low').length,\r\n info: allFindings.filter((f) => f.severity === 'info').length,\r\n },\r\n results: allResults,\r\n skillResults: allSkillResults,\r\n timestamp: new Date().toISOString(),\r\n version: VERSION,\r\n };\r\n\r\n // ── Step 4: Output results ──\r\n if (isJSON) {\r\n const jsonOutput = formatJSON(summary);\r\n if (options.output) {\r\n await writeFile(options.output, jsonOutput);\r\n } else {\r\n // Write JSON and wait for stdout to drain — prevents truncation\r\n // when piped (stdout is async for pipes, sync for TTYs/files)\r\n await new Promise<void>((resolve, reject) => {\r\n const ok = process.stdout.write(jsonOutput + '\\n');\r\n if (ok) resolve();\r\n else process.stdout.once('drain', resolve);\r\n });\r\n }\r\n } else {\r\n console.log('');\r\n\r\n // Print MCP server results\r\n if (results.length > 0) {\r\n for (const result of results) {\r\n printServerResult(result, options.verbose ?? false);\r\n }\r\n }\r\n\r\n // Print skill results\r\n if (skillResults.length > 0) {\r\n for (const result of skillResults) {\r\n printSkillResult(result, options.verbose ?? false);\r\n }\r\n }\r\n\r\n printSummary(summary);\r\n\r\n if (options.output) {\r\n await writeFile(options.output, formatJSON(summary));\r\n console.log(` Results saved to ${options.output}`);\r\n }\r\n }\r\n\r\n // ── Step 4.5: Upload results to Vigile API (if authenticated) ──\r\n if (options.noUpload !== true) {\r\n await uploadResults(results, skillResults, isJSON);\r\n }\r\n\r\n // ── Step 5: Sentinel Runtime Monitoring (if --sentinel) ──\r\n if (options.sentinel) {\r\n await runSentinel(options, results, isJSON);\r\n }\r\n\r\n // ── Step 6: BaaS Scanning (if --supabase / --firebase / --app) ──\r\n if (options.supabase || options.firebase || options.app) {\r\n await runBaaSScan(options, isJSON);\r\n }\r\n\r\n // ── Exit with appropriate code ──\r\n if (summary.bySeverity.critical > 0 || summary.bySeverity.high > 0) {\r\n // Wait for stdout to drain before exiting — prevents truncation\r\n // when output is piped (stdout is async for pipes, not TTYs)\r\n await new Promise<void>((resolve) => {\r\n if (process.stdout.writableNeedDrain) {\r\n process.stdout.once('drain', resolve);\r\n } else {\r\n resolve();\r\n }\r\n });\r\n process.exit(1);\r\n }\r\n}\r\n\r\n// ============================================================\r\n// API Upload — Send scan results to the Vigile registry\r\n// ============================================================\r\n\r\n/**\r\n * Upload scan results to the Vigile API.\r\n * Graceful degradation: if API is unreachable or auth fails, just warn.\r\n */\r\nasync function uploadResults(\r\n mcpResults: ScanResult[],\r\n skillResults: SkillScanResult[],\r\n isJSON: boolean,\r\n): Promise<void> {\r\n const client = await getAuthenticatedClient();\r\n\r\n if (!client) {\r\n if (!isJSON) {\r\n printUploadSkipped('not-authenticated');\r\n }\r\n return;\r\n }\r\n\r\n const summary: UploadSummary = {\r\n mcpUploaded: 0,\r\n skillsUploaded: 0,\r\n failures: 0,\r\n errors: [],\r\n };\r\n\r\n const spinner = isJSON ? null : ora('Uploading results to Vigile registry...').start();\r\n\r\n // Upload MCP scan results\r\n for (const result of mcpResults) {\r\n const payload = mapMCPResultToApiPayload(result);\r\n const response = await client.submitMCPScan(payload);\r\n\r\n if (response.ok) {\r\n summary.mcpUploaded++;\r\n } else {\r\n summary.failures++;\r\n summary.errors.push(`${result.server.name}: ${response.error}`);\r\n }\r\n }\r\n\r\n // Upload skill scan results\r\n for (const result of skillResults) {\r\n const payload = mapSkillResultToApiPayload(result);\r\n const response = await client.submitSkillScan(payload);\r\n\r\n if (response.ok) {\r\n summary.skillsUploaded++;\r\n } else {\r\n summary.failures++;\r\n summary.errors.push(`${result.skill.name}: ${response.error}`);\r\n }\r\n }\r\n\r\n if (spinner) {\r\n const total = summary.mcpUploaded + summary.skillsUploaded;\r\n if (total > 0 && summary.failures === 0) {\r\n spinner.succeed(`Uploaded ${total} result(s) to Vigile registry`);\r\n } else if (total > 0 && summary.failures > 0) {\r\n spinner.warn(`Uploaded ${total} result(s), ${summary.failures} failed`);\r\n } else {\r\n spinner.fail('Upload failed');\r\n }\r\n }\r\n\r\n if (!isJSON) {\r\n printUploadSuccess(summary);\r\n }\r\n}\r\n\r\n// ============================================================\r\n// BaaS Scan Flow — Supabase / Firebase / deployed app scanning\r\n// ============================================================\r\n\r\n/**\r\n * Runs BaaS security scanning for any --supabase, --firebase,\r\n * or --app URLs provided as CLI flags.\r\n *\r\n * Prints findings inline and exits with code 1 if critical/high\r\n * findings are discovered (same contract as MCP scan).\r\n */\r\nasync function runBaaSScan(options: ScanOptions, isJSON: boolean): Promise<void> {\r\n const spinner = isJSON ? null : ora('Running BaaS security scan...').start();\r\n\r\n let totalFindings = 0;\r\n let criticalOrHigh = 0;\r\n\r\n // ── Supabase scan ──\r\n if (options.supabase) {\r\n const result = await scanSupabase({\r\n projectUrl: options.supabase,\r\n anonKey: options.supabaseKey,\r\n });\r\n totalFindings += result.findings.length;\r\n criticalOrHigh += result.findings.filter(\r\n (f) => f.severity === 'critical' || f.severity === 'high',\r\n ).length;\r\n\r\n if (!isJSON) {\r\n spinner?.stop();\r\n console.log(chalk.bold(`\\n Supabase: ${options.supabase}`));\r\n if (result.errors.length > 0) {\r\n result.errors.forEach((e) => console.log(chalk.yellow(` ⚠ ${e}`)));\r\n }\r\n result.findings.forEach((f) => {\r\n const color =\r\n f.severity === 'critical' ? chalk.red : f.severity === 'high' ? chalk.yellow : chalk.gray;\r\n console.log(color(` [${f.severity.toUpperCase()}] ${f.title}`));\r\n if (options.verbose) console.log(chalk.gray(` ${f.description}`));\r\n });\r\n }\r\n }\r\n\r\n // ── Firebase scan ──\r\n if (options.firebase) {\r\n const result = await scanFirebase({ projectUrl: options.firebase });\r\n totalFindings += result.findings.length;\r\n criticalOrHigh += result.findings.filter(\r\n (f) => f.severity === 'critical' || f.severity === 'high',\r\n ).length;\r\n\r\n if (!isJSON) {\r\n spinner?.stop();\r\n console.log(chalk.bold(`\\n Firebase: ${options.firebase}`));\r\n if (result.errors.length > 0) {\r\n result.errors.forEach((e) => console.log(chalk.yellow(` ⚠ ${e}`)));\r\n }\r\n result.findings.forEach((f) => {\r\n const color =\r\n f.severity === 'critical' ? chalk.red : f.severity === 'high' ? chalk.yellow : chalk.gray;\r\n console.log(color(` [${f.severity.toUpperCase()}] ${f.title}`));\r\n if (options.verbose) console.log(chalk.gray(` ${f.description}`));\r\n });\r\n }\r\n }\r\n\r\n // ── Web app scan (bundle analysis + platform detection) ──\r\n if (options.app) {\r\n const result = await scanVibeApp({ appUrl: options.app });\r\n totalFindings += result.findings.length;\r\n criticalOrHigh += result.findings.filter(\r\n (f) => f.severity === 'critical' || f.severity === 'high',\r\n ).length;\r\n\r\n if (!isJSON) {\r\n spinner?.stop();\r\n console.log(chalk.bold(`\\n App: ${options.app}`));\r\n console.log(chalk.gray(` Platform detected: ${result.detectedPlatform}`));\r\n console.log(chalk.gray(` Bundles analyzed: ${result.bundlesAnalyzed}`));\r\n if (result.errors.length > 0) {\r\n result.errors.forEach((e) => console.log(chalk.yellow(` ⚠ ${e}`)));\r\n }\r\n result.findings.forEach((f) => {\r\n const color =\r\n f.severity === 'critical' ? chalk.red : f.severity === 'high' ? chalk.yellow : chalk.gray;\r\n console.log(color(` [${f.severity.toUpperCase()}] ${f.title}`));\r\n if (options.verbose) {\r\n console.log(chalk.gray(` ${f.description}`));\r\n if (f.evidence) console.log(chalk.gray(` Evidence: ${f.evidence}`));\r\n }\r\n });\r\n }\r\n }\r\n\r\n if (!isJSON) {\r\n console.log('');\r\n if (totalFindings === 0) {\r\n console.log(chalk.green(' BaaS scan complete — no secrets or misconfigurations found'));\r\n } else {\r\n console.log(\r\n chalk.yellow(` BaaS scan complete — ${totalFindings} finding(s), ${criticalOrHigh} critical/high`),\r\n );\r\n }\r\n console.log('');\r\n }\r\n}\r\n\r\n// ============================================================\r\n// Field Mapping Helpers — CLI types → API request shapes\r\n// ============================================================\r\n\r\n/**\r\n * Map a CLI ScanResult to the API ScanRequest payload.\r\n */\r\nfunction mapMCPResultToApiPayload(result: ScanResult): {\r\n server_name: string;\r\n source: string;\r\n package_url?: string;\r\n description?: string;\r\n tool_descriptions?: string[];\r\n} {\r\n // Derive package URL from command + args\r\n let packageUrl: string | undefined;\r\n if (result.server.command === 'npx') {\r\n const packageName = result.server.args.find((a) => !a.startsWith('-'));\r\n if (packageName) {\r\n packageUrl = `https://www.npmjs.com/package/${packageName}`;\r\n }\r\n } else if (result.server.command === 'uvx' || result.server.command === 'pip') {\r\n const packageName = result.server.args.find((a) => !a.startsWith('-'));\r\n if (packageName) {\r\n packageUrl = `https://pypi.org/project/${packageName}/`;\r\n }\r\n }\r\n\r\n // Tool descriptions from non-flag args\r\n const toolDescriptions = result.server.args.filter((a) => !a.startsWith('-'));\r\n\r\n return {\r\n server_name: result.server.name,\r\n source: 'manual', // CLI-discovered servers are always manual source\r\n package_url: packageUrl,\r\n description: `MCP server discovered from ${result.server.source} config`,\r\n tool_descriptions: toolDescriptions.length > 0 ? toolDescriptions : undefined,\r\n };\r\n}\r\n\r\n/**\r\n * Map a CLI SkillScanResult to the API SkillScanRequest payload.\r\n */\r\nfunction mapSkillResultToApiPayload(result: SkillScanResult): {\r\n skill_name: string;\r\n content: string;\r\n file_type: string;\r\n platform: string;\r\n source: string;\r\n} {\r\n // Map CLI SkillSource to API platform values\r\n const platformMap: Record<string, string> = {\r\n 'claude-code': 'claude-code',\r\n 'github-copilot': 'copilot',\r\n 'cursor': 'cursor',\r\n 'memory-file': 'unknown',\r\n 'custom': 'unknown',\r\n };\r\n\r\n return {\r\n skill_name: result.skill.name,\r\n content: result.skill.content,\r\n file_type: result.skill.fileType,\r\n platform: platformMap[result.skill.source] || 'unknown',\r\n source: 'manual', // CLI submissions are always manual source\r\n };\r\n}\r\n\r\n/**\r\n * Map a CLI NetworkEvent (camelCase) to the API NetworkEventSubmission (snake_case).\r\n */\r\nfunction mapNetworkEventToApi(event: NetworkEvent): {\r\n timestamp: number;\r\n server_name: string;\r\n method: string;\r\n url: string;\r\n destination_ip: string | null;\r\n port: number;\r\n request_size: number;\r\n response_size: number | null;\r\n status_code: number | null;\r\n headers: Record<string, string> | null;\r\n dns_query_type: string | null;\r\n tls: boolean;\r\n body_hash: string | null;\r\n body_entropy: number | null;\r\n} {\r\n return {\r\n timestamp: event.timestamp,\r\n server_name: event.serverName,\r\n method: event.method,\r\n url: event.url,\r\n destination_ip: event.destinationIp || null,\r\n port: event.port,\r\n request_size: event.requestSize,\r\n response_size: event.responseSize ?? null,\r\n status_code: event.statusCode ?? null,\r\n headers: event.headers || null,\r\n dns_query_type: event.dnsQueryType ?? null,\r\n tls: event.tls,\r\n body_hash: event.bodyHash ?? null,\r\n body_entropy: event.bodyEntropy ?? null,\r\n };\r\n}\r\n\r\n// ============================================================\r\n// Sentinel Runtime Monitoring — with API integration\r\n// ============================================================\r\n\r\n/**\r\n * Run Sentinel runtime monitoring on discovered MCP servers.\r\n * When authenticated, creates API sessions and submits events\r\n * for server-side analysis. Falls back to local-only when offline.\r\n */\r\nasync function runSentinel(\r\n options: ScanOptions,\r\n scanResults: ScanResult[],\r\n isJSON: boolean,\r\n): Promise<void> {\r\n // Resolve tier: API if authenticated, else env var fallback\r\n const client = await getAuthenticatedClient();\r\n let tier: 'free' | 'pro' = 'free';\r\n\r\n if (client) {\r\n const meResult = await client.getMe();\r\n if (meResult.ok) {\r\n tier = meResult.data.tier as typeof tier;\r\n }\r\n } else {\r\n tier = (process.env.VIGILE_TIER as typeof tier) || 'free';\r\n }\r\n\r\n const features = getSentinelFeatures(tier);\r\n\r\n if (!features.monitoringEnabled) {\r\n if (!isJSON) {\r\n printSentinelUpgrade();\r\n } else {\r\n console.log(\r\n JSON.stringify({\r\n sentinel: { error: 'upgrade_required', message: SENTINEL_MARKETING.upgradePrompt },\r\n }),\r\n );\r\n }\r\n return;\r\n }\r\n\r\n // Determine which server(s) to monitor\r\n const serversToMonitor: string[] = [];\r\n if (options.sentinelServer) {\r\n serversToMonitor.push(options.sentinelServer);\r\n } else if (scanResults.length > 0) {\r\n // Monitor all discovered servers (up to the tier limit)\r\n const limit =\r\n features.maxConcurrentServers === -1\r\n ? scanResults.length\r\n : Math.min(scanResults.length, features.maxConcurrentServers);\r\n for (let i = 0; i < limit; i++) {\r\n serversToMonitor.push(scanResults[i].server.name);\r\n }\r\n } else {\r\n if (!isJSON) {\r\n console.log(\r\n chalk.yellow(' No MCP servers to monitor. Run a scan first or specify --sentinel-server <name>.'),\r\n );\r\n }\r\n return;\r\n }\r\n\r\n // Clamp duration to tier limit\r\n const requestedDuration = options.sentinelDuration || 120;\r\n const maxDuration = features.maxDurationSeconds === -1 ? requestedDuration : features.maxDurationSeconds;\r\n const duration = Math.min(requestedDuration, maxDuration);\r\n\r\n if (!isJSON) {\r\n console.log('');\r\n console.log(chalk.bold.hex('#2C4A7C')(' \\u{1F6E1}\\uFE0F Vigile Sentinel \\u2014 Runtime Monitor'));\r\n console.log(\r\n chalk.gray(` Tier: ${tier.toUpperCase()} | Duration: ${duration}s | Servers: ${serversToMonitor.length}`),\r\n );\r\n console.log('');\r\n }\r\n\r\n // Start monitoring each server\r\n const sentinelReports: SentinelReport[] = [];\r\n\r\n for (const serverName of serversToMonitor) {\r\n // Create API session if authenticated\r\n let apiSessionId: number | null = null;\r\n if (client) {\r\n const sessionResult = await client.createSentinelSession(serverName, duration);\r\n if (sessionResult.ok) {\r\n apiSessionId = sessionResult.data.session_id;\r\n }\r\n }\r\n\r\n const spinner = isJSON ? null : ora(`Monitoring ${serverName} for ${duration}s...`).start();\r\n\r\n // Batch events for API submission\r\n let pendingApiEvents: NetworkEvent[] = [];\r\n let lastApiSubmit = Date.now();\r\n\r\n const engine = new SentinelEngine({\r\n serverName,\r\n durationSeconds: duration,\r\n onEvent: (event) => {\r\n if (!isJSON && spinner) {\r\n spinner.text = `Monitoring ${serverName} \\u2014 ${(engine as unknown as { events: unknown[] }).events.length} events captured...`;\r\n }\r\n\r\n // Accumulate events for API batch submission\r\n if (apiSessionId !== null && client) {\r\n pendingApiEvents.push(event);\r\n\r\n // Submit every 5 seconds\r\n const now = Date.now();\r\n if (now - lastApiSubmit > 5000 && pendingApiEvents.length > 0) {\r\n const eventsToSubmit = pendingApiEvents.map(mapNetworkEventToApi);\r\n pendingApiEvents = [];\r\n lastApiSubmit = now;\r\n // Fire and forget — don't await in the event handler\r\n client.submitSentinelEvents(apiSessionId, eventsToSubmit).catch(() => {\r\n // Silently ignore API submission errors during monitoring\r\n });\r\n }\r\n }\r\n },\r\n });\r\n\r\n await engine.startMonitoring();\r\n\r\n // Wait for monitoring duration\r\n await new Promise((resolve) => setTimeout(resolve, duration * 1000));\r\n\r\n const report = await engine.stopMonitoring();\r\n sentinelReports.push(report);\r\n\r\n // Submit remaining events and get API analysis\r\n if (apiSessionId !== null && client) {\r\n if (pendingApiEvents.length > 0) {\r\n const finalEvents = pendingApiEvents.map(mapNetworkEventToApi);\r\n await client.submitSentinelEvents(apiSessionId, finalEvents);\r\n }\r\n\r\n const apiReport = await client.analyzeSentinelSession(apiSessionId);\r\n if (apiReport.ok && !isJSON) {\r\n console.log(\r\n chalk.gray(\r\n ` API Analysis: ${apiReport.data.findings.length} findings, threat: ${apiReport.data.threat_level}`,\r\n ),\r\n );\r\n }\r\n }\r\n\r\n if (spinner) {\r\n spinner.succeed(\r\n `${serverName}: ${report.totalEvents} events, ${report.findings.length} findings [${report.threatLevel.toUpperCase()}]`,\r\n );\r\n }\r\n }\r\n\r\n // Output Sentinel results\r\n if (isJSON) {\r\n console.log(JSON.stringify({ sentinel: sentinelReports }, null, 2));\r\n } else {\r\n for (const report of sentinelReports) {\r\n printSentinelReport(report);\r\n }\r\n }\r\n}\r\n\r\nprogram.parse();\r\n","// ============================================================\r\n// Claude Desktop — MCP Config Discovery\r\n// ============================================================\r\n// Config locations (as of February 2026):\r\n// macOS: ~/Library/Application Support/Claude/claude_desktop_config.json\r\n// Windows: %APPDATA%\\Claude\\claude_desktop_config.json\r\n// Linux: ~/.config/Claude/claude_desktop_config.json\r\n\r\nimport { join } from 'path';\r\nimport { getHome, getPlatform, getAppData, tryConfigPaths } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverClaudeDesktop(): Promise<MCPServerEntry[]> {\r\n const home = getHome();\r\n const plat = getPlatform();\r\n\r\n const paths: string[] = [];\r\n\r\n switch (plat) {\r\n case 'darwin':\r\n paths.push(\r\n join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')\r\n );\r\n break;\r\n case 'win32':\r\n paths.push(join(getAppData(), 'Claude', 'claude_desktop_config.json'));\r\n break;\r\n case 'linux':\r\n paths.push(join(home, '.config', 'Claude', 'claude_desktop_config.json'));\r\n break;\r\n }\r\n\r\n return tryConfigPaths(paths, 'claude-desktop');\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Discovery Utilities\r\n// ============================================================\r\n\r\nimport { readFile } from 'fs/promises';\r\nimport { existsSync } from 'fs';\r\nimport { join } from 'path';\r\nimport { homedir, platform } from 'os';\r\nimport type { MCPClient, MCPServerEntry } from '../types/index.js';\r\n\r\n/** Get the home directory */\r\nexport function getHome(): string {\r\n return homedir();\r\n}\r\n\r\n/** Get the current platform */\r\nexport function getPlatform(): 'darwin' | 'win32' | 'linux' {\r\n return platform() as 'darwin' | 'win32' | 'linux';\r\n}\r\n\r\n/** Get Windows APPDATA path */\r\nexport function getAppData(): string {\r\n return process.env.APPDATA || join(homedir(), 'AppData', 'Roaming');\r\n}\r\n\r\n/** Get Windows LOCALAPPDATA path */\r\nexport function getLocalAppData(): string {\r\n return (\r\n process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local')\r\n );\r\n}\r\n\r\n/**\r\n * Parse an MCP config file (JSON with mcpServers key).\r\n * Returns empty array if file doesn't exist or is invalid.\r\n */\r\nexport async function parseMCPConfig(\r\n configPath: string,\r\n source: MCPClient\r\n): Promise<MCPServerEntry[]> {\r\n if (!existsSync(configPath)) {\r\n return [];\r\n }\r\n\r\n try {\r\n const raw = await readFile(configPath, 'utf-8');\r\n const config = JSON.parse(raw);\r\n\r\n // Handle { mcpServers: {...} }, { servers: {...} } (VS Code), and direct configs\r\n const servers = config.mcpServers || config.servers || config;\r\n\r\n if (typeof servers !== 'object' || servers === null) {\r\n return [];\r\n }\r\n\r\n const entries: MCPServerEntry[] = [];\r\n\r\n for (const [name, serverConfig] of Object.entries(servers)) {\r\n const sc = serverConfig as Record<string, unknown>;\r\n\r\n // Skip if it doesn't look like a server config\r\n if (!sc.command && !sc.url) continue;\r\n\r\n entries.push({\r\n name,\r\n source,\r\n command: (sc.command as string) || '',\r\n args: Array.isArray(sc.args) ? (sc.args as string[]) : [],\r\n env: sc.env as Record<string, string> | undefined,\r\n configPath,\r\n });\r\n }\r\n\r\n return entries;\r\n } catch {\r\n // Invalid JSON, file read error, etc.\r\n return [];\r\n }\r\n}\r\n\r\n/**\r\n * Try multiple possible config paths and return all servers found.\r\n */\r\nexport async function tryConfigPaths(\r\n paths: string[],\r\n source: MCPClient\r\n): Promise<MCPServerEntry[]> {\r\n const allServers: MCPServerEntry[] = [];\r\n\r\n for (const path of paths) {\r\n const servers = await parseMCPConfig(path, source);\r\n allServers.push(...servers);\r\n }\r\n\r\n return allServers;\r\n}\r\n","// ============================================================\r\n// Cursor — MCP Config Discovery\r\n// ============================================================\r\n// Config locations:\r\n// Global: ~/.cursor/mcp.json\r\n// Project: .cursor/mcp.json (in cwd)\r\n\r\nimport { join } from 'path';\r\nimport { getHome, tryConfigPaths } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverCursor(): Promise<MCPServerEntry[]> {\r\n const home = getHome();\r\n\r\n const paths = [\r\n join(home, '.cursor', 'mcp.json'),\r\n join(process.cwd(), '.cursor', 'mcp.json'),\r\n ];\r\n\r\n return tryConfigPaths(paths, 'cursor');\r\n}\r\n","// ============================================================\r\n// Claude Code — MCP Config Discovery\r\n// ============================================================\r\n// Config locations:\r\n// Global: ~/.claude.json (mcpServers key)\r\n// Project: .mcp.json (in cwd)\r\n// Plugins: ~/.claude/plugins/cache/claude-plugins-official/<name>/<version>/.mcp.json\r\n\r\nimport { join } from 'path';\r\nimport { readdirSync, existsSync } from 'fs';\r\nimport { getHome, tryConfigPaths, parseMCPConfig } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverClaudeCode(): Promise<MCPServerEntry[]> {\r\n const home = getHome();\r\n const allServers: MCPServerEntry[] = [];\r\n\r\n // 1. Traditional config paths (global + project-local)\r\n const traditionalPaths = [\r\n join(home, '.claude.json'),\r\n join(process.cwd(), '.mcp.json'),\r\n ];\r\n allServers.push(...await tryConfigPaths(traditionalPaths, 'claude-code'));\r\n\r\n // 2. Claude Code plugin cache — each plugin can bundle its own .mcp.json\r\n const pluginCacheDir = join(home, '.claude', 'plugins', 'cache', 'claude-plugins-official');\r\n if (existsSync(pluginCacheDir)) {\r\n allServers.push(...await discoverPluginMCPConfigs(pluginCacheDir));\r\n }\r\n\r\n return allServers;\r\n}\r\n\r\n/**\r\n * Scan the Claude Code plugin cache for .mcp.json files.\r\n * Structure: ~/.claude/plugins/cache/claude-plugins-official/<plugin>/<version>/.mcp.json\r\n * Some plugins nest deeper (e.g., semgrep/<hash>/plugin/.mcp.json).\r\n */\r\nasync function discoverPluginMCPConfigs(cacheDir: string): Promise<MCPServerEntry[]> {\r\n const servers: MCPServerEntry[] = [];\r\n const seen = new Set<string>();\r\n\r\n let pluginDirs: ReturnType<typeof readdirSync>;\r\n try {\r\n pluginDirs = readdirSync(cacheDir, { withFileTypes: true });\r\n } catch {\r\n return [];\r\n }\r\n\r\n for (const pluginEntry of pluginDirs) {\r\n if (!pluginEntry.isDirectory()) continue;\r\n\r\n const pluginDir = join(cacheDir, pluginEntry.name);\r\n\r\n let versionDirs: ReturnType<typeof readdirSync>;\r\n try {\r\n versionDirs = readdirSync(pluginDir, { withFileTypes: true });\r\n } catch {\r\n continue;\r\n }\r\n\r\n for (const versionEntry of versionDirs) {\r\n if (!versionEntry.isDirectory()) continue;\r\n\r\n const versionDir = join(pluginDir, versionEntry.name);\r\n\r\n // Collect candidate .mcp.json paths: direct + one level deeper\r\n const candidates = [join(versionDir, '.mcp.json')];\r\n\r\n try {\r\n for (const sub of readdirSync(versionDir, { withFileTypes: true })) {\r\n if (sub.isDirectory()) {\r\n candidates.push(join(versionDir, sub.name, '.mcp.json'));\r\n }\r\n }\r\n } catch { /* ignore read errors on subdirs */ }\r\n\r\n for (const candidate of candidates) {\r\n const found = await parseMCPConfig(candidate, 'claude-code');\r\n for (const server of found) {\r\n // Deduplicate: same server name from different cached versions\r\n if (!seen.has(server.name)) {\r\n seen.add(server.name);\r\n servers.push(server);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return servers;\r\n}\r\n","// ============================================================\r\n// Windsurf — MCP Config Discovery\r\n// ============================================================\r\n// Config locations:\r\n// macOS/Linux: ~/.codeium/windsurf/mcp_config.json\r\n// Windows: %USERPROFILE%\\.codeium\\windsurf\\mcp_config.json\r\n\r\nimport { join } from 'path';\r\nimport { getHome, tryConfigPaths } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverWindsurf(): Promise<MCPServerEntry[]> {\r\n const home = getHome();\r\n\r\n const paths = [\r\n join(home, '.codeium', 'windsurf', 'mcp_config.json'),\r\n ];\r\n\r\n return tryConfigPaths(paths, 'windsurf');\r\n}\r\n","// ============================================================\r\n// VS Code — MCP Config Discovery\r\n// ============================================================\r\n// Config locations:\r\n// Project: .vscode/mcp.json (in cwd)\r\n\r\nimport { join } from 'path';\r\nimport { tryConfigPaths } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverVSCode(): Promise<MCPServerEntry[]> {\r\n const paths = [\r\n join(process.cwd(), '.vscode', 'mcp.json'),\r\n ];\r\n\r\n return tryConfigPaths(paths, 'vscode');\r\n}\r\n","// ============================================================\r\n// OpenClaw — MCP Config Discovery\r\n// ============================================================\r\n// Config locations:\r\n// All platforms: ~/.openclaw/openclaw.json (mcpServers key)\r\n// Project-local: openclaw.config.json (in cwd)\r\n//\r\n// OpenClaw also supports agent-level MCP configs:\r\n// { agents: { list: [{ id: \"main\", mcp: { servers: [...] } }] } }\r\n//\r\n// Note: OpenClaw uses JSON5 format (comments, trailing commas).\r\n// Our parser uses standard JSON.parse — configs with JSON5-only\r\n// features will be skipped gracefully.\r\n\r\nimport { join } from 'path';\r\nimport { readFile } from 'fs/promises';\r\nimport { existsSync } from 'fs';\r\nimport { getHome, tryConfigPaths } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverOpenClaw(): Promise<MCPServerEntry[]> {\r\n const home = getHome();\r\n const allServers: MCPServerEntry[] = [];\r\n const seen = new Set<string>();\r\n\r\n // 1. Standard mcpServers block (top-level, same format as Claude Desktop)\r\n const configPaths = [\r\n join(home, '.openclaw', 'openclaw.json'),\r\n join(process.cwd(), 'openclaw.config.json'),\r\n ];\r\n for (const server of await tryConfigPaths(configPaths, 'openclaw')) {\r\n if (!seen.has(server.name)) {\r\n seen.add(server.name);\r\n allServers.push(server);\r\n }\r\n }\r\n\r\n // 2. Agent-level MCP configs (nested under agents.list[].mcp.servers)\r\n const globalConfig = join(home, '.openclaw', 'openclaw.json');\r\n if (existsSync(globalConfig)) {\r\n for (const server of await parseAgentMCPConfigs(globalConfig)) {\r\n if (!seen.has(server.name)) {\r\n seen.add(server.name);\r\n allServers.push(server);\r\n }\r\n }\r\n }\r\n\r\n return allServers;\r\n}\r\n\r\n/**\r\n * Parse agent-level MCP server configs from OpenClaw config.\r\n * Structure: { agents: { list: [{ id: \"main\", mcp: { servers: [...] } }] } }\r\n *\r\n * Agent-level servers use an array format with name/command/args properties,\r\n * unlike the top-level mcpServers which uses an object keyed by server name.\r\n */\r\nasync function parseAgentMCPConfigs(configPath: string): Promise<MCPServerEntry[]> {\r\n try {\r\n const raw = await readFile(configPath, 'utf-8');\r\n const config = JSON.parse(raw);\r\n\r\n const agents = config?.agents?.list;\r\n if (!Array.isArray(agents)) return [];\r\n\r\n const servers: MCPServerEntry[] = [];\r\n\r\n for (const agent of agents) {\r\n const mcpServers = agent?.mcp?.servers;\r\n if (!Array.isArray(mcpServers)) continue;\r\n\r\n for (const server of mcpServers) {\r\n if (!server.name || (!server.command && !server.url)) continue;\r\n\r\n servers.push({\r\n name: server.name,\r\n source: 'openclaw',\r\n command: server.command || '',\r\n args: Array.isArray(server.args) ? server.args : [],\r\n env: server.env,\r\n configPath,\r\n });\r\n }\r\n }\r\n\r\n return servers;\r\n } catch {\r\n return [];\r\n }\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Skill File Discovery\r\n// ============================================================\r\n// Discovers agent skill files (SKILL.md, .mdc rules, CLAUDE.md,\r\n// SOUL.md, MEMORY.md) across all supported AI tools.\r\n//\r\n// Scan locations:\r\n// Claude Code: .claude/skills/*/SKILL.md (project)\r\n// ~/.claude/skills/*/SKILL.md (global)\r\n// GitHub Copilot: .github/skills/*/SKILL.md (project)\r\n// Cursor: .cursor/rules/*.mdc (project)\r\n// ~/.cursor/rules/*.mdc (global)\r\n// Memory Files: CLAUDE.md, SOUL.md, MEMORY.md (project root)\r\n// ~/.claude/CLAUDE.md (global)\r\n\r\nimport { readFile, stat } from 'fs/promises';\r\nimport { existsSync } from 'fs';\r\nimport { join, basename, dirname } from 'path';\r\nimport { glob } from 'glob';\r\nimport { getHome } from './utils.js';\r\nimport type {\r\n SkillEntry,\r\n SkillSource,\r\n SkillFileType,\r\n SkillDiscoveryResult,\r\n} from '../types/index.js';\r\n\r\n/**\r\n * Discover all agent skill files on this machine.\r\n */\r\nexport async function discoverAllSkills(): Promise<SkillDiscoveryResult> {\r\n const skills: SkillEntry[] = [];\r\n const errors: Array<{ source: SkillSource; error: string }> = [];\r\n let locationsChecked = 0;\r\n let locationsFound = 0;\r\n\r\n const discoverers: Array<{\r\n source: SkillSource;\r\n fn: () => Promise<SkillEntry[]>;\r\n }> = [\r\n { source: 'claude-code', fn: discoverClaudeCodeSkills },\r\n { source: 'github-copilot', fn: discoverGitHubCopilotSkills },\r\n { source: 'cursor', fn: discoverCursorRules },\r\n { source: 'memory-file', fn: discoverMemoryFiles },\r\n ];\r\n\r\n for (const { source, fn } of discoverers) {\r\n locationsChecked++;\r\n try {\r\n const found = await fn();\r\n if (found.length > 0) {\r\n locationsFound++;\r\n skills.push(...found);\r\n }\r\n } catch (err) {\r\n errors.push({\r\n source,\r\n error: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n }\r\n\r\n return { skills, locationsChecked, locationsFound, errors };\r\n}\r\n\r\n/**\r\n * Discover Claude Code skill files.\r\n * Project: .claude/skills/* /SKILL.md\r\n * Global: ~/.claude/skills/* /SKILL.md\r\n */\r\nasync function discoverClaudeCodeSkills(): Promise<SkillEntry[]> {\r\n const home = getHome();\r\n const skills: SkillEntry[] = [];\r\n\r\n // Project-local skills\r\n const projectPatterns = [\r\n join(process.cwd(), '.claude', 'skills', '*', 'SKILL.md'),\r\n join(process.cwd(), '.claude', 'commands', '**', '*.md'),\r\n ];\r\n\r\n for (const pattern of projectPatterns) {\r\n const files = await glob(pattern, { absolute: true });\r\n for (const filePath of files) {\r\n const entry = await readSkillFile(filePath, 'claude-code', 'skill.md', 'project');\r\n if (entry) skills.push(entry);\r\n }\r\n }\r\n\r\n // Global skills\r\n const globalPatterns = [\r\n join(home, '.claude', 'skills', '*', 'SKILL.md'),\r\n join(home, '.claude', 'commands', '**', '*.md'),\r\n ];\r\n\r\n for (const pattern of globalPatterns) {\r\n const files = await glob(pattern, { absolute: true });\r\n for (const filePath of files) {\r\n const entry = await readSkillFile(filePath, 'claude-code', 'skill.md', 'global');\r\n if (entry) skills.push(entry);\r\n }\r\n }\r\n\r\n return skills;\r\n}\r\n\r\n/**\r\n * Discover GitHub Copilot skill files.\r\n * Project: .github/skills/* /SKILL.md\r\n */\r\nasync function discoverGitHubCopilotSkills(): Promise<SkillEntry[]> {\r\n const skills: SkillEntry[] = [];\r\n\r\n const patterns = [\r\n join(process.cwd(), '.github', 'skills', '*', 'SKILL.md'),\r\n join(process.cwd(), '.github', 'copilot', '**', '*.md'),\r\n ];\r\n\r\n for (const pattern of patterns) {\r\n const files = await glob(pattern, { absolute: true });\r\n for (const filePath of files) {\r\n const entry = await readSkillFile(filePath, 'github-copilot', 'skill.md', 'project');\r\n if (entry) skills.push(entry);\r\n }\r\n }\r\n\r\n return skills;\r\n}\r\n\r\n/**\r\n * Discover Cursor .mdc rule files.\r\n * Project: .cursor/rules/*.mdc\r\n * Global: ~/.cursor/rules/*.mdc\r\n */\r\nasync function discoverCursorRules(): Promise<SkillEntry[]> {\r\n const home = getHome();\r\n const skills: SkillEntry[] = [];\r\n\r\n // Project-local rules\r\n const projectPattern = join(process.cwd(), '.cursor', 'rules', '*.mdc');\r\n const projectFiles = await glob(projectPattern, { absolute: true });\r\n for (const filePath of projectFiles) {\r\n const entry = await readSkillFile(filePath, 'cursor', 'mdc-rule', 'project');\r\n if (entry) skills.push(entry);\r\n }\r\n\r\n // Also check .cursorrules (legacy single file)\r\n const legacyPath = join(process.cwd(), '.cursorrules');\r\n if (existsSync(legacyPath)) {\r\n const entry = await readSkillFile(legacyPath, 'cursor', 'mdc-rule', 'project');\r\n if (entry) skills.push(entry);\r\n }\r\n\r\n // Global rules\r\n const globalPattern = join(home, '.cursor', 'rules', '*.mdc');\r\n const globalFiles = await glob(globalPattern, { absolute: true });\r\n for (const filePath of globalFiles) {\r\n const entry = await readSkillFile(filePath, 'cursor', 'mdc-rule', 'global');\r\n if (entry) skills.push(entry);\r\n }\r\n\r\n return skills;\r\n}\r\n\r\n/**\r\n * Discover memory/instruction files at project root and global locations.\r\n * CLAUDE.md, SOUL.md, MEMORY.md\r\n */\r\nasync function discoverMemoryFiles(): Promise<SkillEntry[]> {\r\n const home = getHome();\r\n const skills: SkillEntry[] = [];\r\n const cwd = process.cwd();\r\n\r\n const memoryFiles: Array<{\r\n path: string;\r\n fileType: SkillFileType;\r\n scope: 'project' | 'global';\r\n }> = [\r\n // Project-level memory files\r\n { path: join(cwd, 'CLAUDE.md'), fileType: 'claude.md', scope: 'project' },\r\n { path: join(cwd, '.claude', 'CLAUDE.md'), fileType: 'claude.md', scope: 'project' },\r\n { path: join(cwd, 'SOUL.md'), fileType: 'soul.md', scope: 'project' },\r\n { path: join(cwd, 'MEMORY.md'), fileType: 'memory.md', scope: 'project' },\r\n // Additional project instruction files\r\n { path: join(cwd, '.github', 'copilot-instructions.md'), fileType: 'claude.md', scope: 'project' },\r\n { path: join(cwd, 'AGENTS.md'), fileType: 'claude.md', scope: 'project' },\r\n { path: join(cwd, '.windsurfrules'), fileType: 'claude.md', scope: 'project' },\r\n { path: join(cwd, 'GEMINI.md'), fileType: 'claude.md', scope: 'project' },\r\n // Global memory files\r\n { path: join(home, '.claude', 'CLAUDE.md'), fileType: 'claude.md', scope: 'global' },\r\n { path: join(home, 'CLAUDE.md'), fileType: 'claude.md', scope: 'global' },\r\n ];\r\n\r\n for (const { path, fileType, scope } of memoryFiles) {\r\n if (existsSync(path)) {\r\n const entry = await readSkillFile(path, 'memory-file', fileType, scope);\r\n if (entry) skills.push(entry);\r\n }\r\n }\r\n\r\n return skills;\r\n}\r\n\r\n/**\r\n * Read a skill file and return a SkillEntry.\r\n */\r\nasync function readSkillFile(\r\n filePath: string,\r\n source: SkillSource,\r\n fileType: SkillFileType,\r\n scope: 'project' | 'global'\r\n): Promise<SkillEntry | null> {\r\n try {\r\n const content = await readFile(filePath, 'utf-8');\r\n const fileStat = await stat(filePath);\r\n\r\n // Derive skill name from context\r\n const name = deriveSkillName(filePath, fileType);\r\n\r\n return {\r\n name,\r\n source,\r\n fileType,\r\n filePath,\r\n content,\r\n size: fileStat.size,\r\n scope,\r\n };\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Derive a human-readable skill name from a file path.\r\n */\r\nfunction deriveSkillName(filePath: string, fileType: SkillFileType): string {\r\n switch (fileType) {\r\n case 'skill.md': {\r\n // .claude/skills/my-skill/SKILL.md → \"my-skill\"\r\n const parentDir = basename(dirname(filePath));\r\n return parentDir === 'skills' ? basename(filePath, '.md') : parentDir;\r\n }\r\n case 'mdc-rule': {\r\n // .cursor/rules/my-rule.mdc → \"my-rule\"\r\n const name = basename(filePath);\r\n return name.replace(/\\.(mdc|cursorrules?)$/, '') || name;\r\n }\r\n case 'claude.md':\r\n return 'CLAUDE.md';\r\n case 'soul.md':\r\n return 'SOUL.md';\r\n case 'memory.md':\r\n return 'MEMORY.md';\r\n default:\r\n return basename(filePath);\r\n }\r\n}\r\n","// ============================================================\r\n// Vigile CLI — MCP Config Discovery Orchestrator\r\n// ============================================================\r\n// Auto-discovers MCP server configurations across all supported\r\n// AI tools on the user's machine. Supports macOS, Windows, Linux.\r\n\r\nimport { discoverClaudeDesktop } from './claude-desktop.js';\r\nimport { discoverCursor } from './cursor.js';\r\nimport { discoverClaudeCode } from './claude-code.js';\r\nimport { discoverWindsurf } from './windsurf.js';\r\nimport { discoverVSCode } from './vscode.js';\r\nimport { discoverOpenClaw } from './openclaw.js';\r\nimport type { MCPServerEntry, MCPClient } from '../types/index.js';\r\n\r\n// Re-export skill discovery for use in CLI\r\nexport { discoverAllSkills } from './skills.js';\r\n\r\nexport interface DiscoveryResult {\r\n servers: MCPServerEntry[];\r\n configsChecked: number;\r\n configsFound: number;\r\n errors: Array<{ client: MCPClient; error: string }>;\r\n}\r\n\r\n/**\r\n * Discover all MCP server configurations on this machine.\r\n * Checks all known IDE/tool config locations for the current OS.\r\n */\r\nexport async function discoverAllServers(\r\n clientFilter?: MCPClient\r\n): Promise<DiscoveryResult> {\r\n const discoverers: Array<{\r\n client: MCPClient;\r\n fn: () => Promise<MCPServerEntry[]>;\r\n }> = [\r\n { client: 'claude-desktop', fn: discoverClaudeDesktop },\r\n { client: 'cursor', fn: discoverCursor },\r\n { client: 'claude-code', fn: discoverClaudeCode },\r\n { client: 'windsurf', fn: discoverWindsurf },\r\n { client: 'vscode', fn: discoverVSCode },\r\n { client: 'openclaw', fn: discoverOpenClaw },\r\n ];\r\n\r\n // Filter to specific client if requested\r\n const toRun = clientFilter\r\n ? discoverers.filter((d) => d.client === clientFilter)\r\n : discoverers;\r\n\r\n const servers: MCPServerEntry[] = [];\r\n const errors: Array<{ client: MCPClient; error: string }> = [];\r\n let configsFound = 0;\r\n\r\n for (const { client, fn } of toRun) {\r\n try {\r\n const found = await fn();\r\n if (found.length > 0) {\r\n configsFound++;\r\n servers.push(...found);\r\n }\r\n } catch (err) {\r\n errors.push({\r\n client,\r\n error: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n }\r\n\r\n return {\r\n servers,\r\n configsChecked: toRun.length,\r\n configsFound,\r\n errors,\r\n };\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Detection Patterns\r\n// ============================================================\r\n// These patterns detect known attack vectors in MCP server\r\n// tool descriptions and configurations. Based on real-world\r\n// attacks documented by Invariant Labs, OWASP MCP Top 10,\r\n// and the broader AI security research community.\r\n\r\nimport type { Severity, FindingCategory } from '../types/index.js';\r\n\r\nexport interface DetectionPattern {\r\n id: string;\r\n category: FindingCategory;\r\n severity: Severity;\r\n title: string;\r\n /** Regex pattern to match against */\r\n pattern: RegExp;\r\n /** Human-readable description */\r\n description: string;\r\n /** What the user should do */\r\n recommendation: string;\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// TOOL POISONING PATTERNS\r\n// Detect hidden instructions embedded in tool descriptions\r\n// that manipulate the AI agent's behavior.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const TOOL_POISONING_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'TP-001',\r\n category: 'tool-poisoning',\r\n severity: 'critical',\r\n title: 'Prompt override instruction detected',\r\n pattern: /ignore\\s+(all\\s+)?previous\\s+instructions/i,\r\n description:\r\n 'Tool description contains instructions to override the AI agent\\'s system prompt. This is a classic prompt injection attack.',\r\n recommendation:\r\n 'Do NOT install this MCP server. This is a known attack pattern.',\r\n },\r\n {\r\n id: 'TP-002',\r\n category: 'tool-poisoning',\r\n severity: 'critical',\r\n title: 'Hidden agent manipulation',\r\n pattern: /do\\s+not\\s+tell\\s+the\\s+user/i,\r\n description:\r\n 'Tool description instructs the AI agent to hide information from the user — a hallmark of tool poisoning.',\r\n recommendation:\r\n 'Do NOT install this MCP server. Legitimate tools never instruct agents to hide actions.',\r\n },\r\n // TP-003 removed: \"you are a/an [role]\" is too broad — matches legitimate\r\n // role descriptions in skills (e.g., \"You are a computer science professor\").\r\n // SK-001 covers the real threat (malicious persona hijacking) with proper\r\n // specificity by requiring suspicious role terms like \"hacker\" or \"jailbroken\".\r\n {\r\n id: 'TP-004',\r\n category: 'tool-poisoning',\r\n severity: 'high',\r\n title: 'Cross-tool instruction injection',\r\n pattern: /(before|after|instead\\s+of)\\s+(using|calling|invoking)\\s+(this|any|other|the)\\s+tool/i,\r\n description:\r\n 'Tool description tries to influence how other tools are called — a cross-origin escalation pattern.',\r\n recommendation:\r\n 'Review carefully. This tool may be trying to shadow or intercept other tool calls.',\r\n },\r\n {\r\n id: 'TP-005',\r\n category: 'tool-poisoning',\r\n severity: 'high',\r\n title: 'Instruction to call specific tool',\r\n pattern: /(always|must|should)\\s+(first\\s+)?(call|use|invoke|run)\\s+[\\w-]+\\s+(tool|function|command)/i,\r\n description:\r\n 'Tool description mandates calling a specific other tool, which could be used to chain attacks.',\r\n recommendation:\r\n 'Verify that the referenced tool is legitimate and necessary.',\r\n },\r\n {\r\n id: 'TP-006',\r\n category: 'tool-poisoning',\r\n severity: 'high',\r\n title: 'Hidden text block detected',\r\n pattern: /\\n{5,}.*\\n{5,}/s,\r\n description:\r\n 'Tool description contains large blocks of whitespace that may hide instructions from casual review.',\r\n recommendation:\r\n 'Inspect the full tool description carefully for hidden content.',\r\n },\r\n {\r\n id: 'TP-007',\r\n category: 'tool-poisoning',\r\n severity: 'medium',\r\n title: 'System prompt reference',\r\n pattern: /system\\s*prompt|system\\s*message|system\\s*instruction/i,\r\n description:\r\n 'Tool description references system prompts, which may indicate an attempt to manipulate agent behavior.',\r\n recommendation:\r\n 'Review the context. Legitimate tools rarely reference system prompts.',\r\n },\r\n {\r\n id: 'TP-008',\r\n category: 'tool-poisoning',\r\n severity: 'medium',\r\n title: 'Instruction to keep secrets',\r\n pattern: /(keep|this\\s+is)\\s+(a\\s+)?secret|don'?t\\s+(mention|reveal|disclose|share)/i,\r\n description:\r\n 'Tool description instructs the agent to keep information secret from the user.',\r\n recommendation:\r\n 'Legitimate tools don\\'t ask agents to hide information. Review carefully.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// DATA EXFILTRATION PATTERNS\r\n// Detect attempts to steal sensitive data through tool\r\n// descriptions that reference credential files, env vars,\r\n// or suspicious external URLs.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const EXFILTRATION_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'EX-001',\r\n category: 'data-exfiltration',\r\n severity: 'critical',\r\n title: 'SSH key access pattern',\r\n pattern: /\\.ssh\\/(id_rsa|id_ed25519|id_ecdsa|authorized_keys|known_hosts|config)/i,\r\n description:\r\n 'Tool references SSH key files. This matches the Invariant Labs attack where SSH keys were exfiltrated from Claude Desktop.',\r\n recommendation:\r\n 'CRITICAL: Remove immediately. No legitimate MCP tool needs access to SSH keys.',\r\n },\r\n {\r\n id: 'EX-002',\r\n category: 'data-exfiltration',\r\n severity: 'critical',\r\n title: 'AWS credential access',\r\n pattern: /\\.aws\\/(credentials|config)|AWS_SECRET_ACCESS_KEY|AWS_ACCESS_KEY_ID/i,\r\n description:\r\n 'Tool references AWS credential files or environment variables.',\r\n recommendation:\r\n 'Remove immediately unless this is a verified AWS management tool.',\r\n },\r\n {\r\n id: 'EX-003',\r\n category: 'data-exfiltration',\r\n severity: 'critical',\r\n title: 'Environment file access',\r\n pattern: /\\.(env|env\\.local|env\\.production|env\\.development)\\b/i,\r\n description:\r\n 'Tool references .env files which typically contain API keys and secrets.',\r\n recommendation:\r\n 'Review why this tool needs access to environment files.',\r\n },\r\n {\r\n id: 'EX-004',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'Credential file access pattern',\r\n pattern: /(credentials|secrets|tokens|passwords|api[_-]?keys)\\.(json|yaml|yml|txt|cfg|ini|conf)/i,\r\n description:\r\n 'Tool references files commonly used to store credentials.',\r\n recommendation:\r\n 'Verify this tool has a legitimate reason to access credential files.',\r\n },\r\n {\r\n id: 'EX-005',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'Suspicious external URL',\r\n pattern: /https?:\\/\\/(?!(?:github\\.com|npmjs\\.com|registry\\.npmjs\\.org|pypi\\.org|api\\.github\\.com))[a-z0-9][-a-z0-9]*\\.[a-z]{2,}\\/(collect|track|log|beacon|webhook|exfil|receive|data|upload|report)/i,\r\n description:\r\n 'Tool description contains a URL pointing to a data collection endpoint.',\r\n recommendation:\r\n 'Investigate the URL. This may be a data exfiltration endpoint.',\r\n },\r\n {\r\n id: 'EX-006',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'Cryptocurrency wallet access',\r\n pattern: /(\\.bitcoin|\\.ethereum|wallet\\.dat|\\.solana|seed\\s*phrase|private\\s*key|keystore)/i,\r\n description:\r\n 'Tool references cryptocurrency wallet files or seed phrases. Matches the malicious OpenClaw skills pattern.',\r\n recommendation:\r\n 'CRITICAL: Remove immediately unless this is a verified crypto tool.',\r\n },\r\n {\r\n id: 'EX-007',\r\n category: 'data-exfiltration',\r\n severity: 'medium',\r\n title: 'Browser data access',\r\n pattern: /(cookies|local\\s*storage|session\\s*storage|browser\\s*history|bookmarks|saved\\s*passwords)/i,\r\n description:\r\n 'Tool references browser data stores.',\r\n recommendation:\r\n 'Review why this tool needs access to browser data.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// PERMISSION ABUSE PATTERNS\r\n// Detect tools requesting excessive or suspicious permissions.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const PERMISSION_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'PM-001',\r\n category: 'permission-abuse',\r\n severity: 'high',\r\n title: 'Code execution capability',\r\n pattern: /\\b(eval|exec|spawn|child_process|subprocess|os\\.system|os\\.popen)\\b/i,\r\n description:\r\n 'Tool has code execution capabilities which could be used to run arbitrary commands.',\r\n recommendation:\r\n 'Ensure this tool\\'s code execution is properly sandboxed.',\r\n },\r\n {\r\n id: 'PM-002',\r\n category: 'permission-abuse',\r\n severity: 'high',\r\n title: 'Unrestricted filesystem access',\r\n pattern: /\\b(readFile|writeFile|readdir|rmdir|unlink|fs\\.|filesystem|file\\s*system)\\b.*\\b(any|all|entire|root|\\/)\\b/i,\r\n description:\r\n 'Tool claims unrestricted filesystem access.',\r\n recommendation:\r\n 'Tools should have scoped filesystem access, not unrestricted.',\r\n },\r\n {\r\n id: 'PM-003',\r\n category: 'permission-abuse',\r\n severity: 'medium',\r\n title: 'Network request capability',\r\n pattern: /\\b(fetch|axios|http\\.request|urllib|requests\\.get|requests\\.post|curl|wget)\\b/i,\r\n description:\r\n 'Tool makes network requests. Legitimate in many cases, but could be used for data exfiltration.',\r\n recommendation:\r\n 'Verify the tool\\'s network targets are expected and safe.',\r\n },\r\n {\r\n id: 'PM-004',\r\n category: 'permission-abuse',\r\n severity: 'medium',\r\n title: 'Sensitive path access',\r\n pattern: /\\/etc\\/(passwd|shadow|hosts|sudoers)|\\/root\\/|~\\/\\./,\r\n description:\r\n 'Tool accesses system-sensitive paths.',\r\n recommendation:\r\n 'Review why this tool needs access to system files.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// OBFUSCATION PATTERNS\r\n// Detect attempts to hide malicious content through encoding\r\n// or unicode tricks.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const OBFUSCATION_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'OB-001',\r\n category: 'obfuscation',\r\n severity: 'high',\r\n title: 'Base64 encoded content',\r\n pattern: /[A-Za-z0-9+/]{40,}={0,2}/,\r\n description:\r\n 'Tool description contains what appears to be base64-encoded content, which may hide malicious instructions.',\r\n recommendation:\r\n 'Decode the base64 content and inspect it before using this tool.',\r\n },\r\n {\r\n id: 'OB-002',\r\n category: 'obfuscation',\r\n severity: 'high',\r\n title: 'Zero-width characters detected',\r\n pattern: /[\\u200B\\u200C\\u200D\\uFEFF\\u2060\\u2061\\u2062\\u2063\\u2064]/,\r\n description:\r\n 'Tool description contains invisible zero-width Unicode characters that may hide content.',\r\n recommendation:\r\n 'Strip zero-width characters and inspect the resulting text.',\r\n },\r\n {\r\n id: 'OB-003',\r\n category: 'obfuscation',\r\n severity: 'medium',\r\n title: 'Hex-encoded string',\r\n pattern: /\\\\x[0-9a-fA-F]{2}(\\\\x[0-9a-fA-F]{2}){4,}/,\r\n description:\r\n 'Tool description contains hex-encoded strings that may hide instructions.',\r\n recommendation:\r\n 'Decode the hex content and inspect it.',\r\n },\r\n {\r\n id: 'OB-004',\r\n category: 'obfuscation',\r\n severity: 'medium',\r\n title: 'Unicode escape sequences',\r\n pattern: /\\\\u[0-9a-fA-F]{4}(\\\\u[0-9a-fA-F]{4}){4,}/,\r\n description:\r\n 'Tool description contains Unicode escape sequences that may hide content.',\r\n recommendation:\r\n 'Decode the Unicode escapes and inspect the resulting text.',\r\n },\r\n];\r\n\r\n/** All detection patterns combined */\r\nexport const ALL_PATTERNS: DetectionPattern[] = [\r\n ...TOOL_POISONING_PATTERNS,\r\n ...EXFILTRATION_PATTERNS,\r\n ...PERMISSION_PATTERNS,\r\n ...OBFUSCATION_PATTERNS,\r\n];\r\n","// ============================================================\r\n// Vigile CLI — Trust Score Calculator\r\n// ============================================================\r\n// Calculates a 0-100 trust score based on weighted factors.\r\n// For CLI v1, we focus on what we can analyze locally:\r\n// - Code Analysis (30%): Findings from pattern scanning\r\n// - Dependency Health (20%): Package source and age signals\r\n// - Permission Safety (20%): How much access the tool requests\r\n// - Behavioral Stability (15%): Command stability signals\r\n// - Transparency (15%): Open source, known package, etc.\r\n\r\nimport type { Finding, MCPServerEntry, ScoreBreakdown, Severity } from '../types/index.js';\r\n\r\ninterface ScoreResult {\r\n score: number;\r\n breakdown: ScoreBreakdown;\r\n}\r\n\r\n/** Severity weights for score deductions */\r\nconst SEVERITY_DEDUCTIONS: Record<Severity, number> = {\r\n critical: 35,\r\n high: 20,\r\n medium: 10,\r\n low: 5,\r\n info: 0,\r\n};\r\n\r\n/**\r\n * Calculate trust score for an MCP server based on scan findings.\r\n */\r\nexport function calculateTrustScore(\r\n findings: Finding[],\r\n server: MCPServerEntry\r\n): ScoreResult {\r\n // Start each factor at 100 and deduct based on findings\r\n let codeAnalysis = 100;\r\n let dependencyHealth = 100;\r\n let permissionSafety = 100;\r\n let behavioralStability = 100;\r\n let transparency = 100;\r\n\r\n // ── Deduct from Code Analysis based on poisoning/obfuscation findings ──\r\n for (const f of findings) {\r\n if (f.category === 'tool-poisoning' || f.category === 'obfuscation') {\r\n codeAnalysis -= SEVERITY_DEDUCTIONS[f.severity];\r\n }\r\n }\r\n codeAnalysis = Math.max(0, codeAnalysis);\r\n\r\n // ── Deduct from Dependency Health based on dependency findings ──\r\n for (const f of findings) {\r\n if (f.category === 'dependency-risk') {\r\n dependencyHealth -= SEVERITY_DEDUCTIONS[f.severity];\r\n }\r\n }\r\n\r\n // Boost dependency health for well-known package sources\r\n if (isWellKnownPackage(server)) {\r\n dependencyHealth = Math.min(100, dependencyHealth + 15);\r\n }\r\n dependencyHealth = Math.max(0, dependencyHealth);\r\n\r\n // ── Deduct from Permission Safety based on permission/exfil findings ──\r\n for (const f of findings) {\r\n if (\r\n f.category === 'permission-abuse' ||\r\n f.category === 'data-exfiltration'\r\n ) {\r\n permissionSafety -= SEVERITY_DEDUCTIONS[f.severity];\r\n }\r\n }\r\n permissionSafety = Math.max(0, permissionSafety);\r\n\r\n // ── Behavioral Stability ──\r\n // In CLI v1, we use command structure as a proxy\r\n // npx/uvx commands are less stable (can change), local paths are more stable\r\n if (server.command === 'npx' || server.command === 'uvx') {\r\n behavioralStability -= 10; // Slight deduction for remote packages\r\n }\r\n if (server.command === 'node' || server.command === 'python' || server.command === 'python3') {\r\n behavioralStability += 5; // Local execution is more predictable\r\n }\r\n behavioralStability = Math.max(0, Math.min(100, behavioralStability));\r\n\r\n // ── Transparency ──\r\n // Check for signals of transparency\r\n if (isOpenSourcePackage(server)) {\r\n transparency = Math.min(100, transparency + 10);\r\n }\r\n // Deduct for unknown/untraceable sources\r\n if (!isWellKnownPackage(server) && server.command !== 'node' && server.command !== 'python') {\r\n transparency -= 20;\r\n }\r\n transparency = Math.max(0, Math.min(100, transparency));\r\n\r\n // ── Calculate weighted score ──\r\n const breakdown: ScoreBreakdown = {\r\n codeAnalysis,\r\n dependencyHealth,\r\n permissionSafety,\r\n behavioralStability,\r\n transparency,\r\n };\r\n\r\n const score = Math.round(\r\n codeAnalysis * 0.30 +\r\n dependencyHealth * 0.20 +\r\n permissionSafety * 0.20 +\r\n behavioralStability * 0.15 +\r\n transparency * 0.15\r\n );\r\n\r\n return { score: Math.max(0, Math.min(100, score)), breakdown };\r\n}\r\n\r\n/** Check if the server uses a well-known package */\r\nfunction isWellKnownPackage(server: MCPServerEntry): boolean {\r\n const wellKnown = [\r\n '@modelcontextprotocol/',\r\n '@anthropic-ai/',\r\n 'mcp-server-',\r\n '@mcp/',\r\n ];\r\n\r\n const packageName = server.args[0] || '';\r\n // Remove -y flag to get actual package name\r\n const name = server.args.find(a => !a.startsWith('-')) || '';\r\n\r\n return wellKnown.some(\r\n (prefix) => packageName.startsWith(prefix) || name.startsWith(prefix)\r\n );\r\n}\r\n\r\n/** Check if the server appears to be from an open source package */\r\nfunction isOpenSourcePackage(server: MCPServerEntry): boolean {\r\n // npx packages are on npm (public), uvx are on PyPI (public)\r\n return ['npx', 'uvx', 'pip', 'pipx'].includes(server.command);\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Scanner Engine\r\n// ============================================================\r\n// Analyzes MCP server entries for security issues by examining\r\n// the command, args, env vars, and (where possible) tool\r\n// descriptions against known attack patterns.\r\n\r\nimport { ALL_PATTERNS } from './patterns.js';\r\nimport type { MCPServerEntry, Finding, ScanResult } from '../types/index.js';\r\nimport { calculateTrustScore } from '../scoring/trust-score.js';\r\n\r\n/**\r\n * Scan a single MCP server entry for security issues.\r\n */\r\nexport async function scanServer(server: MCPServerEntry): Promise<ScanResult> {\r\n const findings: Finding[] = [];\r\n\r\n // ── Scan the command and args ──\r\n const commandStr = `${server.command} ${server.args.join(' ')}`;\r\n findings.push(...scanText(commandStr, 'command'));\r\n\r\n // ── Scan the server name ──\r\n findings.push(...scanText(server.name, 'server name'));\r\n\r\n // ── Scan environment variables ──\r\n if (server.env) {\r\n for (const [key, value] of Object.entries(server.env)) {\r\n // Don't flag standard env vars like PATH, NODE_ENV, etc.\r\n if (isStandardEnvVar(key)) continue;\r\n\r\n // Check for sensitive data in env values\r\n findings.push(...scanEnvVar(key, value));\r\n }\r\n }\r\n\r\n // ── Scan args for suspicious patterns ──\r\n findings.push(...scanArgs(server.args));\r\n\r\n // ── Analyze command for known risky packages ──\r\n findings.push(...analyzeCommand(server));\r\n\r\n // Deduplicate findings by ID\r\n const uniqueFindings = deduplicateFindings(findings);\r\n\r\n // Calculate trust score\r\n const { score, breakdown } = calculateTrustScore(uniqueFindings, server);\r\n\r\n // Determine trust level\r\n const trustLevel =\r\n score >= 80 ? 'trusted' :\r\n score >= 60 ? 'caution' :\r\n score >= 40 ? 'risky' :\r\n 'dangerous';\r\n\r\n return {\r\n server,\r\n trustScore: score,\r\n scoreBreakdown: breakdown,\r\n findings: uniqueFindings,\r\n trustLevel,\r\n scannedAt: new Date().toISOString(),\r\n };\r\n}\r\n\r\n/**\r\n * Scan a text string against all detection patterns.\r\n */\r\nfunction scanText(text: string, context: string): Finding[] {\r\n const findings: Finding[] = [];\r\n\r\n for (const pattern of ALL_PATTERNS) {\r\n const match = pattern.pattern.exec(text);\r\n if (match) {\r\n findings.push({\r\n id: pattern.id,\r\n category: pattern.category,\r\n severity: pattern.severity,\r\n title: pattern.title,\r\n description: `${pattern.description} (found in ${context})`,\r\n evidence: match[0].substring(0, 200),\r\n recommendation: pattern.recommendation,\r\n });\r\n }\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Scan environment variables for sensitive data exposure.\r\n */\r\nfunction scanEnvVar(key: string, value: string): Finding[] {\r\n const findings: Finding[] = [];\r\n\r\n // Check for API keys/tokens being passed in env\r\n const sensitiveKeyPatterns = [\r\n /api[_-]?key/i,\r\n /secret[_-]?key/i,\r\n /access[_-]?token/i,\r\n /private[_-]?key/i,\r\n /password/i,\r\n /auth[_-]?token/i,\r\n ];\r\n\r\n for (const pat of sensitiveKeyPatterns) {\r\n if (pat.test(key)) {\r\n findings.push({\r\n id: 'EV-001',\r\n category: 'data-exfiltration',\r\n severity: 'medium',\r\n title: 'Sensitive environment variable',\r\n description: `The MCP server receives a potentially sensitive environment variable: ${key}. This data is accessible to the server code.`,\r\n evidence: `${key}=<redacted>`,\r\n recommendation:\r\n 'Verify this MCP server needs this credential and that you trust it with this access.',\r\n });\r\n break;\r\n }\r\n }\r\n\r\n // Scan env values against exfiltration patterns too\r\n findings.push(...scanText(value, `env var ${key}`));\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Scan arguments for suspicious patterns.\r\n */\r\nfunction scanArgs(args: string[]): Finding[] {\r\n const findings: Finding[] = [];\r\n const argsStr = args.join(' ');\r\n\r\n // Check for suspicious argument patterns\r\n if (/--allow-all|--no-sandbox|--disable-security/i.test(argsStr)) {\r\n findings.push({\r\n id: 'AR-001',\r\n category: 'permission-abuse',\r\n severity: 'high',\r\n title: 'Security bypass flags detected',\r\n description:\r\n 'The MCP server is started with flags that disable security restrictions.',\r\n evidence: argsStr.substring(0, 200),\r\n recommendation:\r\n 'Remove security-bypass flags. These significantly increase risk.',\r\n });\r\n }\r\n\r\n // Check for args referencing sensitive paths\r\n for (const arg of args) {\r\n if (/\\.(ssh|aws|gnupg|config\\/gcloud)/.test(arg)) {\r\n findings.push({\r\n id: 'AR-002',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'Sensitive directory in arguments',\r\n description: `An MCP server argument references a sensitive directory: ${arg}`,\r\n evidence: arg,\r\n recommendation:\r\n 'Verify this server needs access to this sensitive directory.',\r\n });\r\n }\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Analyze the server command for known risky packages.\r\n */\r\nfunction analyzeCommand(server: MCPServerEntry): Finding[] {\r\n const findings: Finding[] = [];\r\n const fullCommand = `${server.command} ${server.args.join(' ')}`;\r\n\r\n // Check for npx with -y flag (auto-install without confirmation)\r\n if (server.command === 'npx' && server.args.includes('-y')) {\r\n findings.push({\r\n id: 'CM-001',\r\n category: 'dependency-risk',\r\n severity: 'low',\r\n title: 'Auto-install enabled (npx -y)',\r\n description:\r\n 'This MCP server uses npx with the -y flag, which auto-installs packages without confirmation. This is standard practice but means the package is downloaded and executed automatically.',\r\n evidence: fullCommand.substring(0, 200),\r\n recommendation:\r\n 'Verify the package name is correct (watch for typosquatting).',\r\n });\r\n }\r\n\r\n // Check for pip/python packages that might be typosquatted\r\n if (/\\b(pip|python|uvx)\\b/.test(server.command)) {\r\n const packageArg = server.args.find(\r\n (a) => !a.startsWith('-') && !a.startsWith('/') && !a.includes('=')\r\n );\r\n if (packageArg) {\r\n // Basic typosquatting check — flag single-char differences from popular packages\r\n const popularPackages = [\r\n 'mcp-server-fetch',\r\n 'mcp-server-filesystem',\r\n 'mcp-server-github',\r\n 'mcp-server-sqlite',\r\n 'mcp-server-postgres',\r\n 'mcp-server-slack',\r\n 'mcp-server-memory',\r\n 'mcp-server-puppeteer',\r\n 'mcp-server-brave-search',\r\n 'mcp-server-sequential-thinking',\r\n ];\r\n\r\n for (const popular of popularPackages) {\r\n if (\r\n packageArg !== popular &&\r\n levenshteinDistance(packageArg, popular) <= 2 &&\r\n levenshteinDistance(packageArg, popular) > 0\r\n ) {\r\n findings.push({\r\n id: 'CM-002',\r\n category: 'dependency-risk',\r\n severity: 'high',\r\n title: 'Possible typosquatting detected',\r\n description: `Package \"${packageArg}\" is very similar to the popular package \"${popular}\". This could be a typosquatting attack.`,\r\n evidence: `${packageArg} ≈ ${popular}`,\r\n recommendation: `Verify you intended to install \"${packageArg}\" and not \"${popular}\".`,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/** Simple Levenshtein distance for typosquatting detection */\r\nfunction levenshteinDistance(a: string, b: string): number {\r\n const matrix = Array.from({ length: a.length + 1 }, (_, i) =>\r\n Array.from({ length: b.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))\r\n );\r\n\r\n for (let i = 1; i <= a.length; i++) {\r\n for (let j = 1; j <= b.length; j++) {\r\n matrix[i][j] =\r\n a[i - 1] === b[j - 1]\r\n ? matrix[i - 1][j - 1]\r\n : 1 + Math.min(matrix[i - 1][j], matrix[i][j - 1], matrix[i - 1][j - 1]);\r\n }\r\n }\r\n\r\n return matrix[a.length][b.length];\r\n}\r\n\r\n/** Check if an env var name is standard/non-sensitive */\r\nfunction isStandardEnvVar(key: string): boolean {\r\n const standard = new Set([\r\n 'PATH',\r\n 'HOME',\r\n 'USER',\r\n 'SHELL',\r\n 'LANG',\r\n 'LC_ALL',\r\n 'NODE_ENV',\r\n 'DEBUG',\r\n 'LOG_LEVEL',\r\n 'PORT',\r\n 'HOST',\r\n 'HOSTNAME',\r\n 'TZ',\r\n 'TERM',\r\n 'EDITOR',\r\n 'VISUAL',\r\n 'TMPDIR',\r\n 'TEMP',\r\n 'TMP',\r\n ]);\r\n return standard.has(key);\r\n}\r\n\r\n/** Remove duplicate findings (same ID) */\r\nfunction deduplicateFindings(findings: Finding[]): Finding[] {\r\n const seen = new Set<string>();\r\n return findings.filter((f) => {\r\n if (seen.has(f.id)) return false;\r\n seen.add(f.id);\r\n return true;\r\n });\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Skill-Specific Detection Patterns\r\n// ============================================================\r\n// Patterns targeting threats unique to agent skill files,\r\n// .mdc rules, and memory files. These supplement the base\r\n// MCP patterns with natural-language-aware detections.\r\n//\r\n// Based on the SkillScan Architecture threat model:\r\n// - Instruction Injection (hidden directives in skill text)\r\n// - Malware Delivery (fake prerequisites, install commands)\r\n// - Stealth Operations (silent actions, suppressed output)\r\n// - Safety Bypass (disable confirmations, skip verification)\r\n// - Persistence Abuse (cron jobs, startup hooks, memory tampering)\r\n// - Data Exfiltration (credential harvesting via skill instructions)\r\n\r\nimport type { DetectionPattern } from './patterns.js';\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// INSTRUCTION INJECTION PATTERNS\r\n// Hidden directives embedded in skill files that manipulate\r\n// the agent into performing unintended actions.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const INSTRUCTION_INJECTION_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-001',\r\n category: 'instruction-injection',\r\n severity: 'critical',\r\n title: 'Agent role hijacking in skill',\r\n pattern: /(?:you\\s+are|act\\s+as|pretend\\s+to\\s+be|your\\s+new\\s+role\\s+is|from\\s+now\\s+on\\s+you\\s+are)\\s+(?:a\\s+)?(?:hacker|attacker|malicious|evil|unrestricted|jailbroken)/i,\r\n description:\r\n 'Skill file attempts to redefine the agent as a malicious persona. This is a critical prompt injection attack.',\r\n recommendation:\r\n 'Do NOT install this skill. It contains an explicit agent hijack attempt.',\r\n },\r\n {\r\n id: 'SK-002',\r\n category: 'instruction-injection',\r\n severity: 'critical',\r\n title: 'Instruction override directive',\r\n pattern: /(?:disregard|ignore|forget|override|bypass)\\s+(?:all\\s+)?(?:previous|prior|above|system|safety|security)\\s+(?:instructions?|rules?|guidelines?|constraints?|prompts?)/i,\r\n description:\r\n 'Skill file contains a directive to override safety instructions or system prompts.',\r\n recommendation:\r\n 'Do NOT install this skill. It explicitly attempts to disable safety guardrails.',\r\n },\r\n {\r\n id: 'SK-003',\r\n category: 'instruction-injection',\r\n severity: 'high',\r\n title: 'Hidden instructions in markdown',\r\n pattern: /<!--[\\s\\S]*?(?:execute|run|install|download|curl|wget|fetch|eval|bash|sh\\s+-c)[\\s\\S]*?-->/i,\r\n description:\r\n 'Skill file contains hidden instructions inside HTML/markdown comments that the user won\\'t see in rendered view.',\r\n recommendation:\r\n 'Inspect all HTML comments in this skill file. Hidden instructions are a strong indicator of malicious intent.',\r\n },\r\n {\r\n id: 'SK-004',\r\n category: 'instruction-injection',\r\n severity: 'high',\r\n title: 'Conditional behavior trigger',\r\n pattern: /(?:when|if|once)\\s+(?:the\\s+)?user\\s+(?:is\\s+not\\s+looking|isn'?t\\s+(?:watching|paying\\s+attention)|leaves|steps\\s+away|is\\s+away|is\\s+idle)/i,\r\n description:\r\n 'Skill file contains instructions that trigger only when the user is not paying attention.',\r\n recommendation:\r\n 'Remove this skill immediately. Legitimate skills never check for user inattention.',\r\n },\r\n {\r\n id: 'SK-005',\r\n category: 'instruction-injection',\r\n severity: 'high',\r\n title: 'Cross-skill poisoning directive',\r\n pattern: /(?:when\\s+(?:using|calling|invoking)\\s+(?:any|other|the)\\s+(?:skill|tool|command))|(?:modify|alter|change|update)\\s+(?:other|the)\\s+(?:skill|tool|command)\\s+(?:files?|definitions?|descriptions?)/i,\r\n description:\r\n 'Skill file attempts to influence or modify other skills/tools, enabling cross-skill attack chaining.',\r\n recommendation:\r\n 'Review this skill carefully. It should not need to reference or modify other skills.',\r\n },\r\n {\r\n id: 'SK-006',\r\n category: 'instruction-injection',\r\n severity: 'medium',\r\n title: 'Invisible unicode directives',\r\n pattern: /[\\u200B\\u200C\\u200D\\uFEFF\\u2060-\\u2064\\u00AD]{3,}/,\r\n description:\r\n 'Skill file contains clusters of invisible Unicode characters that may hide instructions from visual review.',\r\n recommendation:\r\n 'Strip invisible characters and inspect the resulting content.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// MALWARE DELIVERY PATTERNS\r\n// Skill files that trick the agent into downloading and\r\n// executing malicious code disguised as \"prerequisites\".\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const MALWARE_DELIVERY_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-010',\r\n category: 'malware-delivery',\r\n severity: 'critical',\r\n title: 'Remote script execution',\r\n pattern: /(?:curl|wget|fetch)\\s+(?:-[sSkLfO]+\\s+)?(?:https?:\\/\\/[^\\s|]+)\\s*\\|\\s*(?:bash|sh|zsh|python|node|perl|ruby)/i,\r\n description:\r\n 'Skill instructs the agent to download and pipe a remote script directly into an interpreter. This is a primary malware delivery vector.',\r\n recommendation:\r\n 'Do NOT install this skill. Piping remote scripts to interpreters is extremely dangerous.',\r\n },\r\n {\r\n id: 'SK-011',\r\n category: 'malware-delivery',\r\n severity: 'critical',\r\n title: 'Reverse shell pattern',\r\n pattern: /(?:\\/dev\\/tcp\\/|bash\\s+-i\\s+>&|nc\\s+-[elp]+|ncat\\s+-[elp]+|mkfifo\\s+\\/tmp\\/|python.*socket.*connect|socat\\s+(?:exec|tcp))/i,\r\n description:\r\n 'Skill file contains a reverse shell pattern that would give a remote attacker interactive access to the user\\'s machine.',\r\n recommendation:\r\n 'CRITICAL: Remove immediately. This is a backdoor attempt.',\r\n },\r\n {\r\n id: 'SK-012',\r\n category: 'malware-delivery',\r\n severity: 'high',\r\n title: 'Suspicious install prerequisite',\r\n pattern: /(?:first|before\\s+(?:you\\s+)?(?:start|begin|proceed))\\s*,?\\s*(?:you\\s+)?(?:must|need\\s+to|should)\\s+(?:install|run|execute|download)\\s+[`\"]?(?:curl|wget|npm\\s+i(?:nstall)?|pip\\s+install|gem\\s+install|brew\\s+install)\\s+\\S+/i,\r\n description:\r\n 'Skill file instructs the agent to install specific packages as a \"prerequisite\". This is a common social engineering vector for malware delivery.',\r\n recommendation:\r\n 'Verify the prerequisite package is legitimate before installing.',\r\n },\r\n {\r\n id: 'SK-013',\r\n category: 'malware-delivery',\r\n severity: 'high',\r\n title: 'Encoded payload execution',\r\n pattern: /(?:echo|printf)\\s+['\"]?[A-Za-z0-9+/]{40,}={0,2}['\"]?\\s*\\|\\s*(?:base64\\s+(?:-[dD]|--decode)|openssl\\s+(?:enc|base64))\\s*\\|\\s*(?:bash|sh|python|node)/i,\r\n description:\r\n 'Skill contains a base64-encoded payload that is decoded and executed. This hides malicious code from inspection.',\r\n recommendation:\r\n 'Do NOT run this skill. Decode the base64 content to inspect what it actually executes.',\r\n },\r\n {\r\n id: 'SK-014',\r\n category: 'malware-delivery',\r\n severity: 'high',\r\n title: 'Suspicious npm/pip package install',\r\n pattern: /(?:npm\\s+(?:install|i)\\s+(?:-g\\s+)?|pip\\s+install\\s+|gem\\s+install\\s+)(?!(?:typescript|react|express|flask|django|numpy|pandas|pytest|eslint|prettier|jest|mocha)\\b)[a-z][\\w.-]*(?:\\s|$)/i,\r\n description:\r\n 'Skill instructs installation of a non-standard package. Verify the package name is not typosquatted.',\r\n recommendation:\r\n 'Verify this package exists on the official registry and is the intended package.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// STEALTH OPERATIONS PATTERNS\r\n// Instructions that tell the agent to act silently, hide\r\n// output, or suppress confirmation prompts.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const STEALTH_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-020',\r\n category: 'stealth-operations',\r\n severity: 'critical',\r\n title: 'Stealth action directive',\r\n pattern: /(?:silently|quietly|without\\s+(?:the\\s+)?user\\s+(?:knowing|noticing|seeing)|in\\s+the\\s+background|don'?t\\s+(?:show|display|tell|inform|alert|notify)\\s+(?:the\\s+)?user)/i,\r\n description:\r\n 'Skill instructs the agent to perform actions without informing the user. This is a hallmark of malicious skills.',\r\n recommendation:\r\n 'Do NOT install. Legitimate skills never instruct the agent to hide actions from the user.',\r\n },\r\n {\r\n id: 'SK-021',\r\n category: 'stealth-operations',\r\n severity: 'high',\r\n title: 'Output suppression',\r\n pattern: /(?:suppress|hide|redact|omit|censor)\\s+(?:the\\s+)?(?:output|results?|response|error|warning|log)/i,\r\n description:\r\n 'Skill instructs the agent to hide output or errors from the user, preventing them from seeing what the skill does.',\r\n recommendation:\r\n 'Remove this skill. Users should always see the full output of actions taken on their behalf.',\r\n },\r\n {\r\n id: 'SK-022',\r\n category: 'stealth-operations',\r\n severity: 'high',\r\n title: 'History/log evasion',\r\n pattern: /(?:clear|delete|remove|wipe|purge)\\s+(?:the\\s+)?(?:history|logs?|traces?|evidence|audit\\s+trail|command\\s+history|bash_history)/i,\r\n description:\r\n 'Skill instructs the agent to clear logs or command history to cover its tracks.',\r\n recommendation:\r\n 'CRITICAL: Remove this skill. Clearing logs is a strong indicator of malicious intent.',\r\n },\r\n {\r\n id: 'SK-023',\r\n category: 'stealth-operations',\r\n severity: 'medium',\r\n title: 'Deceptive user response',\r\n pattern: /(?:tell|inform|show|respond\\s+to)\\s+(?:the\\s+)?user\\s+(?:that|with)\\s+(?:everything\\s+is|it'?s?\\s+(?:fine|ok|normal|safe|working|complete|done))\\s+(?:even\\s+(?:if|though|when))/i,\r\n description:\r\n 'Skill instructs the agent to give a misleading \"all clear\" response regardless of what actually happened.',\r\n recommendation:\r\n 'Remove this skill. It instructs the agent to deceive the user about outcomes.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// SAFETY BYPASS PATTERNS\r\n// Instructions that disable safety features, skip\r\n// confirmations, or override security controls.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const SAFETY_BYPASS_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-030',\r\n category: 'safety-bypass',\r\n severity: 'critical',\r\n title: 'Confirmation bypass',\r\n pattern: /(?:skip|bypass|disable|turn\\s+off|don'?t\\s+(?:ask\\s+for|require|need|prompt\\s+for))\\s+(?:user\\s+)?(?:confirmation|approval|consent|verification|permission|authorization)/i,\r\n description:\r\n 'Skill instructs the agent to bypass user confirmation for actions. This removes the human-in-the-loop safety check.',\r\n recommendation:\r\n 'Do NOT install. Disabling confirmation prompts allows the skill to take destructive actions without approval.',\r\n },\r\n {\r\n id: 'SK-031',\r\n category: 'safety-bypass',\r\n severity: 'critical',\r\n title: 'Safety feature disable',\r\n pattern: /(?:disable|turn\\s+off|deactivate|circumvent|work\\s+around)\\s+(?:the\\s+)?(?:safety|security|sandbox|firewall|antivirus|protection|guardrail|content\\s+filter)/i,\r\n description:\r\n 'Skill explicitly instructs disabling safety or security features.',\r\n recommendation:\r\n 'Do NOT install. This is an explicit attempt to weaken system security.',\r\n },\r\n {\r\n id: 'SK-032',\r\n category: 'safety-bypass',\r\n severity: 'high',\r\n title: 'Force flag usage',\r\n pattern: /(?:always\\s+)?(?:use|add|pass|include)\\s+(?:the\\s+)?(?:--force|--no-verify|-f\\s|--yes|-y\\s|--assume-yes|--no-confirm|--skip-validation|--allow-empty|--no-check)/i,\r\n description:\r\n 'Skill instructs the agent to always use force/bypass flags that skip safety validations.',\r\n recommendation:\r\n 'Review why this skill needs to bypass safety checks. Remove the force flags if not essential.',\r\n },\r\n {\r\n id: 'SK-033',\r\n category: 'safety-bypass',\r\n severity: 'high',\r\n title: 'Root/sudo escalation',\r\n pattern: /(?:run|execute|use)\\s+(?:as\\s+)?(?:root|sudo|admin(?:istrator)?)|(?:sudo\\s+(?!apt\\s+update|apt\\s+install))/i,\r\n description:\r\n 'Skill instructs the agent to escalate to root/admin privileges.',\r\n recommendation:\r\n 'Review why this skill needs elevated privileges. Most skills should not require root access.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// PERSISTENCE ABUSE PATTERNS\r\n// Instructions that establish persistence mechanisms,\r\n// modify startup files, or tamper with memory/config files.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const PERSISTENCE_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-040',\r\n category: 'persistence-abuse',\r\n severity: 'critical',\r\n title: 'Startup persistence mechanism',\r\n pattern: /(?:add|write|append|insert)\\s+(?:to|into)\\s+(?:the\\s+)?(?:\\.bashrc|\\.zshrc|\\.bash_profile|\\.profile|\\.zprofile|crontab|\\.config\\/autostart|LaunchAgents|startup)/i,\r\n description:\r\n 'Skill instructs the agent to modify startup/shell config files to establish persistence across reboots.',\r\n recommendation:\r\n 'CRITICAL: Remove this skill. Modifying startup files is a persistence technique used by malware.',\r\n },\r\n {\r\n id: 'SK-041',\r\n category: 'persistence-abuse',\r\n severity: 'critical',\r\n title: 'Memory file tampering',\r\n pattern: /(?:modify|edit|write\\s+to|append\\s+to|update|overwrite)\\s+(?:the\\s+)?(?:CLAUDE\\.md|SOUL\\.md|MEMORY\\.md|\\.claude\\/|\\.cursorrules|\\.cursor\\/rules)/i,\r\n description:\r\n 'Skill instructs the agent to modify other skill/memory files. This can be used to inject persistent instructions that survive across sessions.',\r\n recommendation:\r\n 'Do NOT install. A skill should never modify other skill or memory files.',\r\n },\r\n {\r\n id: 'SK-042',\r\n category: 'persistence-abuse',\r\n severity: 'high',\r\n title: 'Cron job creation',\r\n pattern: /(?:crontab\\s+-[el]|\\/etc\\/cron|systemctl\\s+enable|launchctl\\s+load|schtasks\\s+\\/create)/i,\r\n description:\r\n 'Skill instructs creation of scheduled tasks or cron jobs for persistent execution.',\r\n recommendation:\r\n 'Review why this skill needs scheduled tasks. This is unusual for agent skills.',\r\n },\r\n {\r\n id: 'SK-043',\r\n category: 'persistence-abuse',\r\n severity: 'high',\r\n title: 'Git hook injection',\r\n pattern: /(?:\\.git\\/hooks\\/|pre-commit|post-commit|pre-push|post-receive|pre-receive)\\s*(?:hook|script|file)/i,\r\n description:\r\n 'Skill instructs modification of git hooks, which execute automatically on git operations.',\r\n recommendation:\r\n 'Verify this skill legitimately needs git hook access. Malicious hooks can exfiltrate code on every commit.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// SKILL DATA EXFILTRATION PATTERNS\r\n// Natural language instructions that trick the agent into\r\n// leaking sensitive data through skill-specific vectors.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const SKILL_EXFILTRATION_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-050',\r\n category: 'data-exfiltration',\r\n severity: 'critical',\r\n title: 'Credential harvesting directive',\r\n pattern: /(?:read|extract|get|find|locate|collect|gather|retrieve)\\s+(?:all\\s+)?(?:the\\s+)?(?:api\\s+keys?|tokens?|credentials?|passwords?|secrets?|private\\s+keys?)\\s+(?:from|in|stored\\s+in|located\\s+at)/i,\r\n description:\r\n 'Skill instructs the agent to collect credentials or secrets from the user\\'s system.',\r\n recommendation:\r\n 'Do NOT install. This is a credential harvesting attack.',\r\n },\r\n {\r\n id: 'SK-051',\r\n category: 'data-exfiltration',\r\n severity: 'critical',\r\n title: 'Data exfiltration via URL',\r\n pattern: /(?:send|post|upload|transmit|forward|exfiltrate|ship)\\s+(?:the\\s+)?(?:data|contents?|results?|files?|output|information|credentials?|keys?)\\s+(?:to|via|using|through)\\s+(?:https?:\\/\\/|webhook|api|endpoint|server)/i,\r\n description:\r\n 'Skill instructs the agent to send data to an external URL or endpoint.',\r\n recommendation:\r\n 'CRITICAL: Review what data is being sent and to where. This matches known exfiltration patterns.',\r\n },\r\n {\r\n id: 'SK-052',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'File system enumeration',\r\n pattern: /(?:list|enumerate|scan|find|search|catalogue|index)\\s+(?:all\\s+)?(?:the\\s+)?(?:files?|directories|folders?)\\s+(?:in|under|at|from)\\s+(?:\\/|~\\/|home|root|the\\s+(?:home|root|user))/i,\r\n description:\r\n 'Skill instructs broad filesystem enumeration, potentially to map out sensitive files for exfiltration.',\r\n recommendation:\r\n 'Verify this skill needs filesystem access. Broad enumeration is suspicious.',\r\n },\r\n {\r\n id: 'SK-053',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'Environment variable dumping',\r\n pattern: /(?:print|dump|list|show|display|export|echo)\\s+(?:all\\s+)?(?:the\\s+)?(?:environment\\s+variables?|env\\s+vars?|process\\.env|os\\.environ)/i,\r\n description:\r\n 'Skill instructs dumping all environment variables, which often contain secrets and API keys.',\r\n recommendation:\r\n 'Review why this skill needs access to all environment variables.',\r\n },\r\n];\r\n\r\n/** All skill-specific detection patterns combined */\r\nexport const ALL_SKILL_PATTERNS: DetectionPattern[] = [\r\n ...INSTRUCTION_INJECTION_PATTERNS,\r\n ...MALWARE_DELIVERY_PATTERNS,\r\n ...STEALTH_PATTERNS,\r\n ...SAFETY_BYPASS_PATTERNS,\r\n ...PERSISTENCE_PATTERNS,\r\n ...SKILL_EXFILTRATION_PATTERNS,\r\n];\r\n","// ============================================================\r\n// Vigile CLI — Skill Scanner Engine\r\n// ============================================================\r\n// Analyzes agent skill files (SKILL.md, .mdc rules, CLAUDE.md,\r\n// etc.) for security issues. Scans file content against both\r\n// skill-specific and base detection patterns.\r\n\r\nimport { ALL_PATTERNS } from './patterns.js';\r\nimport { ALL_SKILL_PATTERNS } from './skill-patterns.js';\r\nimport type {\r\n SkillEntry,\r\n Finding,\r\n SkillScanResult,\r\n ScoreBreakdown,\r\n Severity,\r\n} from '../types/index.js';\r\n\r\n/** Severity weights for score deductions */\r\nconst SEVERITY_DEDUCTIONS: Record<Severity, number> = {\r\n critical: 35,\r\n high: 20,\r\n medium: 10,\r\n low: 5,\r\n info: 0,\r\n};\r\n\r\n/**\r\n * Scan a single skill file for security issues.\r\n */\r\nexport async function scanSkill(skill: SkillEntry): Promise<SkillScanResult> {\r\n const findings: Finding[] = [];\r\n\r\n // ── Scan the full content against skill-specific patterns ──\r\n findings.push(...scanContent(skill.content, 'skill content', ALL_SKILL_PATTERNS));\r\n\r\n // ── Also scan against base MCP patterns (tool poisoning, exfil, etc.) ──\r\n findings.push(...scanContent(skill.content, 'skill content', ALL_PATTERNS));\r\n\r\n // ── Analyze structural properties of the skill ──\r\n findings.push(...analyzeStructure(skill));\r\n\r\n // ── Check for MDC-specific issues (Cursor rules) ──\r\n if (skill.fileType === 'mdc-rule') {\r\n findings.push(...analyzeMDCRule(skill));\r\n }\r\n\r\n // Deduplicate findings by ID\r\n const uniqueFindings = deduplicateFindings(findings);\r\n\r\n // Calculate trust score\r\n const { score, breakdown } = calculateSkillTrustScore(uniqueFindings, skill);\r\n\r\n const trustLevel =\r\n score >= 80 ? 'trusted' :\r\n score >= 60 ? 'caution' :\r\n score >= 40 ? 'risky' :\r\n 'dangerous';\r\n\r\n return {\r\n skill,\r\n trustScore: score,\r\n scoreBreakdown: breakdown,\r\n findings: uniqueFindings,\r\n trustLevel,\r\n scannedAt: new Date().toISOString(),\r\n };\r\n}\r\n\r\n/**\r\n * Scan text content against a set of detection patterns.\r\n */\r\nfunction scanContent(\r\n text: string,\r\n context: string,\r\n patterns: Array<{ id: string; category: string; severity: Severity; title: string; pattern: RegExp; description: string; recommendation: string }>\r\n): Finding[] {\r\n const findings: Finding[] = [];\r\n\r\n for (const pattern of patterns) {\r\n // Reset regex state for global patterns\r\n pattern.pattern.lastIndex = 0;\r\n const match = pattern.pattern.exec(text);\r\n if (match) {\r\n findings.push({\r\n id: pattern.id,\r\n category: pattern.category as Finding['category'],\r\n severity: pattern.severity,\r\n title: pattern.title,\r\n description: `${pattern.description} (found in ${context})`,\r\n evidence: match[0].substring(0, 200),\r\n recommendation: pattern.recommendation,\r\n });\r\n }\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Analyze structural properties of a skill file.\r\n */\r\nfunction analyzeStructure(skill: SkillEntry): Finding[] {\r\n const findings: Finding[] = [];\r\n const content = skill.content;\r\n\r\n // Check for suspiciously large skill files (potential payload hiding)\r\n if (skill.size > 50_000) {\r\n findings.push({\r\n id: 'SK-060',\r\n category: 'obfuscation',\r\n severity: 'medium',\r\n title: 'Unusually large skill file',\r\n description: `Skill file is ${Math.round(skill.size / 1024)}KB. Large skill files may contain hidden payloads or obfuscated content.`,\r\n evidence: `File size: ${skill.size} bytes`,\r\n recommendation: 'Inspect the full file carefully. Large skill files are unusual.',\r\n });\r\n }\r\n\r\n // Check for code blocks with suspicious commands\r\n const codeBlockPattern = /```(?:bash|sh|shell|zsh)?\\n([\\s\\S]*?)```/g;\r\n let codeMatch;\r\n while ((codeMatch = codeBlockPattern.exec(content)) !== null) {\r\n const codeBlock = codeMatch[1];\r\n\r\n // Check for dangerous commands in code blocks\r\n if (/rm\\s+-rf\\s+[\\/~]|rm\\s+-rf\\s+\\$\\{?HOME/.test(codeBlock)) {\r\n findings.push({\r\n id: 'SK-061',\r\n category: 'permission-abuse',\r\n severity: 'critical',\r\n title: 'Destructive command in code block',\r\n description: 'Skill contains a recursive delete command targeting the home or root directory.',\r\n evidence: codeBlock.substring(0, 200),\r\n recommendation: 'Do NOT install. This command would delete critical files.',\r\n });\r\n }\r\n\r\n // Check for chmod 777 or overly permissive permissions\r\n if (/chmod\\s+(?:777|a\\+rwx|\\+rwx)/i.test(codeBlock)) {\r\n findings.push({\r\n id: 'SK-062',\r\n category: 'permission-abuse',\r\n severity: 'high',\r\n title: 'World-writable permissions set',\r\n description: 'Skill sets overly permissive file permissions (777/world-writable).',\r\n evidence: codeBlock.substring(0, 200),\r\n recommendation: 'Review why world-writable permissions are needed. This is almost always a security risk.',\r\n });\r\n }\r\n }\r\n\r\n // Check for excessive external URLs\r\n const urlPattern = /https?:\\/\\/[^\\s)>\\]\"']+/g;\r\n const urls = content.match(urlPattern) || [];\r\n const externalUrls = urls.filter(\r\n (url) =>\r\n !url.includes('github.com') &&\r\n !url.includes('npmjs.com') &&\r\n !url.includes('docs.') &&\r\n !url.includes('stackoverflow.com')\r\n );\r\n\r\n if (externalUrls.length > 5) {\r\n findings.push({\r\n id: 'SK-063',\r\n category: 'data-exfiltration',\r\n severity: 'low',\r\n title: 'Many external URLs in skill',\r\n description: `Skill contains ${externalUrls.length} external URLs. Excessive external URLs may indicate data exfiltration endpoints.`,\r\n evidence: externalUrls.slice(0, 3).join(', '),\r\n recommendation: 'Review the external URLs to ensure they are all legitimate.',\r\n });\r\n }\r\n\r\n // Check for ratio of invisible to visible characters\r\n const invisibleChars = content.match(/[\\u200B-\\u200D\\uFEFF\\u2060-\\u2064\\u00AD]/g) || [];\r\n if (invisibleChars.length > 10) {\r\n findings.push({\r\n id: 'SK-064',\r\n category: 'obfuscation',\r\n severity: 'high',\r\n title: 'High concentration of invisible characters',\r\n description: `Skill contains ${invisibleChars.length} invisible Unicode characters that may hide malicious instructions.`,\r\n evidence: `${invisibleChars.length} invisible characters detected`,\r\n recommendation: 'Strip invisible characters and compare the before/after content.',\r\n });\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Analyze Cursor .mdc rule files for specific issues.\r\n */\r\nfunction analyzeMDCRule(skill: SkillEntry): Finding[] {\r\n const findings: Finding[] = [];\r\n const content = skill.content;\r\n\r\n // MDC files can have frontmatter with glob patterns for auto-attachment\r\n const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---/);\r\n if (frontmatterMatch) {\r\n const frontmatter = frontmatterMatch[1];\r\n\r\n // Check for overly broad glob patterns (auto-attaches to everything)\r\n if (/globs?:\\s*['\"]\\*\\*\\/\\*['\"]/i.test(frontmatter) || /globs?:\\s*\\*\\*\\/\\*/i.test(frontmatter)) {\r\n findings.push({\r\n id: 'SK-070',\r\n category: 'permission-abuse',\r\n severity: 'medium',\r\n title: 'MDC rule attached to all files',\r\n description: 'This Cursor rule uses a wildcard glob pattern that auto-attaches it to every file. This means its instructions apply universally.',\r\n evidence: frontmatter.substring(0, 200),\r\n recommendation: 'Review why this rule needs to apply to all files. Narrow the glob pattern if possible.',\r\n });\r\n }\r\n\r\n // Check for alwaysApply\r\n if (/alwaysApply:\\s*true/i.test(frontmatter)) {\r\n findings.push({\r\n id: 'SK-071',\r\n category: 'permission-abuse',\r\n severity: 'low',\r\n title: 'MDC rule always applied',\r\n description: 'This Cursor rule has alwaysApply: true, meaning it runs on every interaction regardless of context.',\r\n evidence: 'alwaysApply: true',\r\n recommendation: 'Consider if this rule truly needs to run on every interaction.',\r\n });\r\n }\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Calculate trust score for a skill file based on findings.\r\n */\r\nfunction calculateSkillTrustScore(\r\n findings: Finding[],\r\n skill: SkillEntry\r\n): { score: number; breakdown: ScoreBreakdown } {\r\n let codeAnalysis = 100;\r\n let dependencyHealth = 100;\r\n let permissionSafety = 100;\r\n let behavioralStability = 100;\r\n let transparency = 100;\r\n\r\n for (const f of findings) {\r\n const deduction = SEVERITY_DEDUCTIONS[f.severity];\r\n\r\n switch (f.category) {\r\n case 'tool-poisoning':\r\n case 'instruction-injection':\r\n case 'obfuscation':\r\n codeAnalysis -= deduction;\r\n break;\r\n case 'malware-delivery':\r\n case 'dependency-risk':\r\n dependencyHealth -= deduction;\r\n break;\r\n case 'permission-abuse':\r\n case 'data-exfiltration':\r\n case 'safety-bypass':\r\n permissionSafety -= deduction;\r\n break;\r\n case 'stealth-operations':\r\n case 'persistence-abuse':\r\n behavioralStability -= deduction;\r\n break;\r\n case 'rug-pull':\r\n transparency -= deduction;\r\n break;\r\n }\r\n }\r\n\r\n // Clamp all factors to 0-100\r\n codeAnalysis = Math.max(0, codeAnalysis);\r\n dependencyHealth = Math.max(0, dependencyHealth);\r\n permissionSafety = Math.max(0, permissionSafety);\r\n behavioralStability = Math.max(0, behavioralStability);\r\n transparency = Math.max(0, transparency);\r\n\r\n // Skill transparency bonuses\r\n if (skill.scope === 'project') {\r\n transparency = Math.min(100, transparency + 10); // Local skills are more transparent\r\n }\r\n if (skill.fileType === 'skill.md' || skill.fileType === 'mdc-rule') {\r\n transparency = Math.min(100, transparency + 5); // Structured skill formats\r\n }\r\n\r\n const breakdown: ScoreBreakdown = {\r\n codeAnalysis,\r\n dependencyHealth,\r\n permissionSafety,\r\n behavioralStability,\r\n transparency,\r\n };\r\n\r\n const score = Math.round(\r\n codeAnalysis * 0.30 +\r\n dependencyHealth * 0.20 +\r\n permissionSafety * 0.20 +\r\n behavioralStability * 0.15 +\r\n transparency * 0.15\r\n );\r\n\r\n return { score: Math.max(0, Math.min(100, score)), breakdown };\r\n}\r\n\r\n/** Remove duplicate findings (same ID) */\r\nfunction deduplicateFindings(findings: Finding[]): Finding[] {\r\n const seen = new Set<string>();\r\n return findings.filter((f) => {\r\n if (seen.has(f.id)) return false;\r\n seen.add(f.id);\r\n return true;\r\n });\r\n}\r\n","// ============================================================\r\n// Vigile Sentinel — Runtime Network Monitor for MCP Servers\r\n// ============================================================\r\n// The Sentinel engine monitors MCP server network behavior\r\n// in real-time to detect \"phoning home\" — C2 beaconing,\r\n// data exfiltration, DNS tunneling, and covert channels.\r\n//\r\n// Architecture:\r\n// 1. Launches a lightweight network monitor alongside MCP servers\r\n// 2. Captures outbound connections using OS-level network inspection\r\n// 3. Feeds events through behavioral + endpoint pattern matching\r\n// 4. Generates a SentinelReport with findings and threat score\r\n//\r\n// This is a PRO+ subscription feature.\r\n//\r\n// Usage:\r\n// npx vigile-scan --sentinel # Monitor all discovered MCP servers\r\n// npx vigile-scan --sentinel --server <name> # Monitor specific server\r\n// npx vigile-scan --sentinel --duration 300 # Monitor for 5 minutes\r\n\r\nimport { execFileSync, spawn, type ChildProcess } from 'child_process';\r\nimport { promises as dnsPromises } from 'dns';\r\nimport type {\r\n NetworkEvent,\r\n SentinelFinding,\r\n SentinelReport,\r\n SentinelThreatLevel,\r\n} from './sentinel-patterns.js';\r\nimport {\r\n SUSPICIOUS_ENDPOINT_PATTERNS,\r\n BEHAVIORAL_PATTERNS,\r\n CREDENTIAL_EXFIL_PATTERNS,\r\n calculateThreatScore,\r\n threatLevelFromScore,\r\n} from './sentinel-patterns.js';\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// SENTINEL ENGINE\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport class SentinelEngine {\r\n private events: NetworkEvent[] = [];\r\n private findings: SentinelFinding[] = [];\r\n private monitorProcess: ChildProcess | null = null;\r\n private startTime: number = 0;\r\n private serverName: string;\r\n private durationSeconds: number;\r\n private onEvent?: (event: NetworkEvent) => void;\r\n /** DNS reverse-lookup cache: IP → hostname. Persists for the session. */\r\n private dnsCache = new Map<string, string>();\r\n\r\n constructor(options: {\r\n serverName: string;\r\n durationSeconds?: number;\r\n onEvent?: (event: NetworkEvent) => void;\r\n }) {\r\n this.serverName = options.serverName;\r\n this.durationSeconds = options.durationSeconds || 120; // Default 2 minutes\r\n this.onEvent = options.onEvent;\r\n }\r\n\r\n /**\r\n * Start monitoring network activity for the given MCP server.\r\n *\r\n * The monitor works in four modes depending on the OS and permissions:\r\n * 1. macOS: Uses `lsof -i` polling (no root required)\r\n * 2. Linux: Uses `ss -tnp` polling (no root required)\r\n * 3. Windows: Uses PowerShell `Get-NetTCPConnection` (no admin required)\r\n * 4. Fallback: Stub — events must be fed manually via ingestEvent()\r\n *\r\n * All modes perform async reverse-DNS enrichment so that endpoint patterns\r\n * (pastebin.com, ngrok.io, etc.) match against resolved hostnames rather\r\n * than raw IPs.\r\n */\r\n async startMonitoring(): Promise<void> {\r\n this.startTime = Date.now();\r\n this.events = [];\r\n this.findings = [];\r\n\r\n const method = this.detectMonitoringMethod();\r\n\r\n switch (method) {\r\n case 'lsof-poll':\r\n await this.startLsofPolling();\r\n break;\r\n case 'ss-poll':\r\n await this.startSsPolling();\r\n break;\r\n case 'netstat-poll':\r\n await this.startNetstatPolling();\r\n break;\r\n case 'proxy':\r\n await this.startProxyCapture();\r\n break;\r\n }\r\n }\r\n\r\n /**\r\n * Stop monitoring and generate the report.\r\n */\r\n async stopMonitoring(): Promise<SentinelReport> {\r\n if (this.monitorProcess) {\r\n this.monitorProcess.kill('SIGTERM');\r\n this.monitorProcess = null;\r\n }\r\n\r\n // Run all detection patterns against collected events\r\n this.analyzeEvents();\r\n\r\n const threatScore = calculateThreatScore(this.findings);\r\n const threatLevel = threatLevelFromScore(threatScore);\r\n\r\n const uniqueDestinations = [\r\n ...new Set(\r\n this.events.map(e => {\r\n try { return new URL(e.url).hostname; } catch { return e.url; }\r\n })\r\n ),\r\n ];\r\n\r\n return {\r\n serverName: this.serverName,\r\n monitoringDuration: (Date.now() - this.startTime) / 1000,\r\n totalEvents: this.events.length,\r\n uniqueDestinations,\r\n findings: this.findings,\r\n threatLevel,\r\n threatScore,\r\n startedAt: new Date(this.startTime).toISOString(),\r\n endedAt: new Date().toISOString(),\r\n };\r\n }\r\n\r\n /**\r\n * Feed a single network event into the engine (for real-time analysis).\r\n */\r\n ingestEvent(event: NetworkEvent): void {\r\n this.events.push(event);\r\n this.onEvent?.(event);\r\n\r\n // Run real-time endpoint checks (fast, per-event)\r\n this.checkEndpointPatterns(event);\r\n this.checkCredentialPatterns(event);\r\n }\r\n\r\n // ── Detection Methods ──\r\n\r\n /**\r\n * Run all analysis on collected events.\r\n */\r\n private analyzeEvents(): void {\r\n // 1. Endpoint pattern matching (per-event)\r\n for (const event of this.events) {\r\n this.checkEndpointPatterns(event);\r\n this.checkCredentialPatterns(event);\r\n }\r\n\r\n // 2. Behavioral pattern matching (across time windows)\r\n for (const pattern of BEHAVIORAL_PATTERNS) {\r\n if (this.events.length < pattern.minEvents) continue;\r\n\r\n // Slice events into the time window\r\n const now = Date.now();\r\n const windowStart = now - pattern.timeWindowSeconds * 1000;\r\n const windowEvents = this.events.filter(e => e.timestamp >= windowStart);\r\n\r\n if (windowEvents.length < pattern.minEvents) continue;\r\n\r\n const confidence = pattern.detect(windowEvents);\r\n if (confidence > 25) {\r\n // Only report if we haven't already found this pattern\r\n if (!this.findings.some(f => f.id === pattern.id)) {\r\n this.findings.push({\r\n id: pattern.id,\r\n category: pattern.id.startsWith('SN-01') ? 'c2-beaconing' :\r\n pattern.id === 'SN-012' ? 'dns-tunneling' :\r\n pattern.id === 'SN-013' ? 'covert-channel' : 'phone-home',\r\n severity: pattern.severity,\r\n title: pattern.title,\r\n description: pattern.description,\r\n serverName: this.serverName,\r\n evidence: windowEvents.slice(0, 10), // Cap evidence at 10 events\r\n recommendation: pattern.recommendation,\r\n confidence,\r\n });\r\n }\r\n }\r\n }\r\n\r\n // De-duplicate findings (keep highest confidence per ID)\r\n const seen = new Map<string, SentinelFinding>();\r\n for (const f of this.findings) {\r\n const existing = seen.get(f.id);\r\n if (!existing || f.confidence > existing.confidence) {\r\n seen.set(f.id, f);\r\n }\r\n }\r\n this.findings = Array.from(seen.values());\r\n }\r\n\r\n private checkEndpointPatterns(event: NetworkEvent): void {\r\n for (const pattern of SUSPICIOUS_ENDPOINT_PATTERNS) {\r\n if (pattern.urlPattern.test(event.url)) {\r\n if (!this.findings.some(f => f.id === pattern.id && f.evidence[0]?.url === event.url)) {\r\n this.findings.push({\r\n id: pattern.id,\r\n category: 'phone-home',\r\n severity: pattern.severity,\r\n title: pattern.title,\r\n description: pattern.description,\r\n serverName: this.serverName,\r\n evidence: [event],\r\n recommendation: pattern.recommendation,\r\n confidence: 95,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n private checkCredentialPatterns(event: NetworkEvent): void {\r\n for (const pattern of CREDENTIAL_EXFIL_PATTERNS) {\r\n if (pattern.urlPattern.test(event.url)) {\r\n this.findings.push({\r\n id: pattern.id,\r\n category: 'data-exfiltration',\r\n severity: pattern.severity,\r\n title: pattern.title,\r\n description: pattern.description,\r\n serverName: this.serverName,\r\n evidence: [event],\r\n recommendation: pattern.recommendation,\r\n confidence: 98,\r\n });\r\n }\r\n }\r\n }\r\n\r\n // ── Monitoring Methods ──\r\n\r\n private detectMonitoringMethod(): 'lsof-poll' | 'ss-poll' | 'netstat-poll' | 'proxy' {\r\n // Windows: PowerShell Get-NetTCPConnection is always available on Win 8+\r\n if (process.platform === 'win32') {\r\n return 'netstat-poll';\r\n }\r\n\r\n // macOS: lsof is always present — skip `which` probe entirely\r\n if (process.platform === 'darwin') {\r\n return 'lsof-poll';\r\n }\r\n\r\n // Linux: prefer ss (iproute2), fall back to lsof\r\n try {\r\n execFileSync('which', ['ss'], { stdio: 'pipe' });\r\n return 'ss-poll';\r\n } catch {\r\n try {\r\n execFileSync('which', ['lsof'], { stdio: 'pipe' });\r\n return 'lsof-poll';\r\n } catch {\r\n return 'proxy';\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * macOS: Poll `lsof -i` to capture network connections.\r\n * No root required. DNS-enriches IPs → hostnames before pattern matching.\r\n */\r\n private async startLsofPolling(): Promise<void> {\r\n const safeName = this.serverName.replace(/[^a-zA-Z0-9._-]/g, '');\r\n\r\n const pollInterval = setInterval(async () => {\r\n try {\r\n const lsofOutput = execFileSync('/usr/sbin/lsof', ['-i', '-n', '-P'], {\r\n timeout: 5000,\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'pipe'],\r\n });\r\n\r\n const lines = lsofOutput\r\n .split('\\n')\r\n .filter(line => line.toLowerCase().includes(safeName.toLowerCase()))\r\n .filter(Boolean);\r\n\r\n // Parse all lines, then DNS-enrich all IPs in parallel before ingesting\r\n const rawEvents = lines.map(l => this.parseLsofLine(l)).filter((e): e is NetworkEvent => e !== null);\r\n const enriched = await Promise.all(rawEvents.map(e => this.enrichWithHostname(e)));\r\n for (const event of enriched) this.ingestEvent(event);\r\n } catch {\r\n // lsof failed, continue\r\n }\r\n }, 2000);\r\n\r\n this.monitorProcess = { kill: () => clearInterval(pollInterval) } as unknown as ChildProcess;\r\n setTimeout(() => clearInterval(pollInterval), this.durationSeconds * 1000);\r\n }\r\n\r\n /**\r\n * Linux: Poll `ss -tnp` (socket statistics) for network connections.\r\n * No root required. DNS-enriches IPs → hostnames before pattern matching.\r\n */\r\n private async startSsPolling(): Promise<void> {\r\n const safeName = this.serverName.replace(/[^a-zA-Z0-9._-]/g, '');\r\n\r\n const pollInterval = setInterval(async () => {\r\n try {\r\n const ssOutput = execFileSync('ss', ['-tnp'], {\r\n timeout: 5000,\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'pipe'],\r\n });\r\n\r\n const lines = ssOutput\r\n .split('\\n')\r\n .filter(line => line.includes(safeName))\r\n .filter(Boolean);\r\n\r\n const rawEvents = lines.map(l => this.parseSsLine(l)).filter((e): e is NetworkEvent => e !== null);\r\n const enriched = await Promise.all(rawEvents.map(e => this.enrichWithHostname(e)));\r\n for (const event of enriched) this.ingestEvent(event);\r\n } catch {\r\n // ss failed, continue\r\n }\r\n }, 2000);\r\n\r\n this.monitorProcess = { kill: () => clearInterval(pollInterval) } as unknown as ChildProcess;\r\n setTimeout(() => clearInterval(pollInterval), this.durationSeconds * 1000);\r\n }\r\n\r\n /**\r\n * Windows: Poll PowerShell `Get-NetTCPConnection` for established connections.\r\n * No admin required (available on Windows 8+ / Server 2012+).\r\n * DNS-enriches IPs → hostnames so endpoint patterns match correctly.\r\n *\r\n * Note: Unlike lsof/ss, this captures ALL machine connections (not filtered\r\n * by process name) because Windows process-to-connection mapping requires\r\n * admin rights. Vigil's patterns handle the noise — it flags what matters.\r\n */\r\n private async startNetstatPolling(): Promise<void> {\r\n // PowerShell command: get established external TCP connections as CSV\r\n const psCommand =\r\n `Get-NetTCPConnection -State Established | ` +\r\n `Where-Object { $_.RemoteAddress -notmatch '^(127\\\\.|::1$|0\\\\.0\\\\.0\\\\.0$|::$)' } | ` +\r\n `Select-Object LocalAddress,LocalPort,RemoteAddress,RemotePort | ` +\r\n `ConvertTo-Csv -NoTypeInformation`;\r\n\r\n const pollInterval = setInterval(async () => {\r\n try {\r\n const output = execFileSync('powershell', [\r\n '-NoProfile',\r\n '-NonInteractive',\r\n '-Command',\r\n psCommand,\r\n ], {\r\n timeout: 8000, // PowerShell startup is slower than lsof\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'pipe'],\r\n });\r\n\r\n // CSV rows: skip header, parse each line\r\n const lines = output.split('\\n').slice(1).map(l => l.trim()).filter(Boolean);\r\n const rawEvents = lines.map(l => this.parsePowerShellLine(l)).filter((e): e is NetworkEvent => e !== null);\r\n const enriched = await Promise.all(rawEvents.map(e => this.enrichWithHostname(e)));\r\n for (const event of enriched) this.ingestEvent(event);\r\n } catch {\r\n // PowerShell failed or unavailable\r\n }\r\n }, 3000); // 3s interval — PS startup adds ~1-2s overhead\r\n\r\n this.monitorProcess = { kill: () => clearInterval(pollInterval) } as unknown as ChildProcess;\r\n setTimeout(() => clearInterval(pollInterval), this.durationSeconds * 1000);\r\n }\r\n\r\n /**\r\n * Fallback: Start a lightweight MITM proxy that MCP traffic routes through.\r\n * This requires the runtime proxy (Phase 3) to be active.\r\n */\r\n private async startProxyCapture(): Promise<void> {\r\n // Proxy mode requires the full runtime proxy infrastructure (Phase 3).\r\n // For Phase 2, this is a stub that collects events fed via ingestEvent().\r\n console.warn(\r\n '[Sentinel] Proxy capture requires the runtime proxy (coming soon). ' +\r\n 'Using manual event ingestion mode.'\r\n );\r\n }\r\n\r\n // ── Parsers ──\r\n\r\n private parseLsofLine(line: string): NetworkEvent | null {\r\n // lsof output format: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\r\n // NAME for TCP: host:port->remote:port (ESTABLISHED)\r\n const match = line.match(\r\n /(\\S+)\\s+(\\d+)\\s+\\S+\\s+\\S+\\s+IPv[46]\\s+\\S+\\s+\\S+\\s+TCP\\s+\\S+->(\\S+):(\\d+)\\s/\r\n );\r\n if (!match) return null;\r\n\r\n const [, _command, _pid, remoteHost, remotePort] = match;\r\n\r\n return {\r\n timestamp: Date.now(),\r\n serverName: this.serverName,\r\n method: 'TCP',\r\n url: `https://${remoteHost}:${remotePort}/`,\r\n destinationIp: remoteHost,\r\n port: parseInt(remotePort, 10),\r\n requestSize: 0,\r\n tls: parseInt(remotePort, 10) === 443,\r\n };\r\n }\r\n\r\n private parseSsLine(line: string): NetworkEvent | null {\r\n // ss output: State Recv-Q Send-Q Local:Port Peer:Port Process\r\n const match = line.match(\r\n /ESTAB\\s+\\d+\\s+(\\d+)\\s+\\S+\\s+(\\S+):(\\d+)/\r\n );\r\n if (!match) return null;\r\n\r\n const [, sendQueue, remoteHost, remotePort] = match;\r\n\r\n return {\r\n timestamp: Date.now(),\r\n serverName: this.serverName,\r\n method: 'TCP',\r\n url: `https://${remoteHost}:${remotePort}/`,\r\n destinationIp: remoteHost,\r\n port: parseInt(remotePort, 10),\r\n requestSize: parseInt(sendQueue, 10),\r\n tls: parseInt(remotePort, 10) === 443,\r\n };\r\n }\r\n\r\n /**\r\n * Windows: Parse a CSV row from PowerShell `Get-NetTCPConnection | ConvertTo-Csv`.\r\n * Format: \"LocalAddress\",\"LocalPort\",\"RemoteAddress\",\"RemotePort\"\r\n * Example: \"192.168.1.5\",\"54123\",\"172.64.155.209\",\"443\"\r\n */\r\n private parsePowerShellLine(line: string): NetworkEvent | null {\r\n // CSV values may be quoted or unquoted depending on PS version\r\n const match = line.match(/^\"?([^\",]+)\"?,\"\\d+\",\"?([^\",]+)\"?,\"?(\\d+)\"?/);\r\n if (!match) return null;\r\n\r\n const [, , remoteAddress, remotePort] = match;\r\n const port = parseInt(remotePort, 10);\r\n\r\n // Skip RFC-1918 private addresses and loopback (already filtered by PS\r\n // but guard here in case the regex misses edge cases)\r\n if (\r\n remoteAddress === '127.0.0.1' ||\r\n remoteAddress === '::1' ||\r\n remoteAddress.startsWith('10.') ||\r\n remoteAddress.startsWith('192.168.') ||\r\n /^172\\.(1[6-9]|2\\d|3[01])\\./.test(remoteAddress)\r\n ) {\r\n return null;\r\n }\r\n\r\n return {\r\n timestamp: Date.now(),\r\n serverName: this.serverName,\r\n method: 'TCP',\r\n url: `https://${remoteAddress}:${remotePort}/`,\r\n destinationIp: remoteAddress,\r\n port,\r\n requestSize: 0,\r\n tls: port === 443,\r\n };\r\n }\r\n\r\n // ── DNS Enrichment ──\r\n\r\n /**\r\n * Reverse-DNS lookup with a session-scoped cache.\r\n * Returns the first PTR record if available, otherwise the raw IP.\r\n *\r\n * WHY this matters: Sentinel's endpoint patterns match against known-bad\r\n * hostnames (pastebin.com, ngrok.io, etc.). Without DNS enrichment, lsof/ss/\r\n * netstat all return raw IPs and those patterns would silently miss every hit.\r\n */\r\n private async resolveIp(ip: string): Promise<string> {\r\n const cached = this.dnsCache.get(ip);\r\n if (cached !== undefined) return cached;\r\n\r\n try {\r\n const hostnames = await dnsPromises.reverse(ip);\r\n const hostname = hostnames[0] ?? ip;\r\n this.dnsCache.set(ip, hostname);\r\n return hostname;\r\n } catch {\r\n // Reverse DNS failed (common for CDN IPs) — fall back to raw IP\r\n this.dnsCache.set(ip, ip);\r\n return ip;\r\n }\r\n }\r\n\r\n /**\r\n * Enrich a NetworkEvent's url field with a resolved hostname.\r\n * Returns a new event object (does not mutate the original).\r\n */\r\n private async enrichWithHostname(event: NetworkEvent): Promise<NetworkEvent> {\r\n if (!event.destinationIp) return event;\r\n const hostname = await this.resolveIp(event.destinationIp);\r\n if (hostname === event.destinationIp) return event; // No change\r\n return {\r\n ...event,\r\n url: `https://${hostname}:${event.port}/`,\r\n };\r\n }\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// SUBSCRIPTION GATING\r\n// Sentinel is a premium feature (Pro tier and above).\r\n// ──────────────────────────────────────────────────────────\r\n\r\n// Launch tiers: free + pro only. Team and enterprise planned for future builds.\r\nexport type SubscriptionTier = 'free' | 'pro';\r\n\r\nexport interface SentinelFeatureGate {\r\n /** Whether Sentinel monitoring is available */\r\n monitoringEnabled: boolean;\r\n /** Maximum monitoring duration in seconds */\r\n maxDurationSeconds: number;\r\n /** Maximum number of servers to monitor simultaneously */\r\n maxConcurrentServers: number;\r\n /** Whether behavioral detection is available (vs. endpoint-only) */\r\n behavioralDetection: boolean;\r\n /** Whether real-time alerts are enabled */\r\n realTimeAlerts: boolean;\r\n /** Whether historical Sentinel data is stored */\r\n historyRetentionDays: number;\r\n /** Whether API access to Sentinel data is available */\r\n apiAccess: boolean;\r\n}\r\n\r\nexport function getSentinelFeatures(tier: SubscriptionTier): SentinelFeatureGate {\r\n switch (tier) {\r\n case 'free':\r\n return {\r\n monitoringEnabled: false, // Upgrade required\r\n maxDurationSeconds: 0,\r\n maxConcurrentServers: 0,\r\n behavioralDetection: false,\r\n realTimeAlerts: false,\r\n historyRetentionDays: 0,\r\n apiAccess: false,\r\n };\r\n case 'pro':\r\n return {\r\n monitoringEnabled: true,\r\n maxDurationSeconds: 300, // 5 minutes\r\n maxConcurrentServers: 3,\r\n behavioralDetection: true,\r\n realTimeAlerts: false, // Future: Team+ only\r\n historyRetentionDays: 7,\r\n apiAccess: true,\r\n };\r\n // Future tiers — uncomment when team/enterprise plans launch\r\n // case 'team':\r\n // return {\r\n // monitoringEnabled: true,\r\n // maxDurationSeconds: 1800, // 30 minutes\r\n // maxConcurrentServers: 10,\r\n // behavioralDetection: true,\r\n // realTimeAlerts: true,\r\n // historyRetentionDays: 30,\r\n // apiAccess: true,\r\n // };\r\n // case 'enterprise':\r\n // return {\r\n // monitoringEnabled: true,\r\n // maxDurationSeconds: -1, // Unlimited (continuous)\r\n // maxConcurrentServers: -1, // Unlimited\r\n // behavioralDetection: true,\r\n // realTimeAlerts: true,\r\n // historyRetentionDays: 365,\r\n // apiAccess: true,\r\n // };\r\n }\r\n}\r\n\r\n/**\r\n * Pretty marketing name for tier-gated Sentinel features.\r\n * Used in CLI output and web UI.\r\n */\r\nexport const SENTINEL_MARKETING = {\r\n tagline: 'Always watching what your tools are doing.',\r\n featureName: 'Vigile Sentinel',\r\n tierName: 'Sentinel Protection',\r\n upgradePrompt:\r\n '🛡️ Vigile Sentinel is a Pro feature. Upgrade at https://vigile.dev/pricing to monitor your MCP servers for real-time phone-home detection.',\r\n categories: [\r\n { icon: '📡', name: 'C2 Beaconing Detection', desc: 'Catches tools phoning home on a schedule' },\r\n { icon: '🔐', name: 'Credential Theft Alerts', desc: 'Detects SSH keys & API tokens leaving your machine' },\r\n { icon: '🕵️', name: 'DNS Tunneling Detection', desc: 'Spots data hidden in DNS queries' },\r\n { icon: '📊', name: 'Behavioral Analysis', desc: 'Machine-learning-ready traffic pattern analysis' },\r\n { icon: '⚡', name: 'Real-Time Alerts', desc: 'Instant notification when threats are detected' },\r\n { icon: '🛡️', name: 'Continuous Monitoring', desc: '24/7 protection for enterprise environments' },\r\n ],\r\n} as const;\r\n","// ============================================================\r\n// Vigile Sentinel — Runtime Phone-Home Detection Patterns\r\n// ============================================================\r\n// These patterns analyze runtime network behavior of MCP servers\r\n// to detect data exfiltration, C2 beaconing, and covert channels.\r\n//\r\n// Unlike static patterns (patterns.ts) which scan tool descriptions\r\n// BEFORE execution, Sentinel patterns analyze LIVE network traffic\r\n// DURING execution. This catches tools that look clean on paper\r\n// but phone home at runtime.\r\n//\r\n// Detection categories:\r\n// - C2 Beaconing (regular intervals, heartbeat patterns)\r\n// - Data Exfiltration (credential theft, file uploads)\r\n// - DNS Tunneling (encoded data in DNS queries)\r\n// - Covert Channels (steganographic exfil, timing-based)\r\n// - Suspicious Endpoints (known bad IPs, DGA domains)\r\n\r\nimport type { Severity, FindingCategory } from '../types/index.js';\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Sentinel-specific types\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport interface NetworkEvent {\r\n /** Timestamp of the network event */\r\n timestamp: number;\r\n /** Source process / MCP server name */\r\n serverName: string;\r\n /** HTTP method or protocol */\r\n method: string;\r\n /** Full URL or hostname */\r\n url: string;\r\n /** Destination IP address (resolved) */\r\n destinationIp?: string;\r\n /** Destination port */\r\n port: number;\r\n /** Request size in bytes */\r\n requestSize: number;\r\n /** Response size in bytes */\r\n responseSize?: number;\r\n /** HTTP status code */\r\n statusCode?: number;\r\n /** Request headers */\r\n headers?: Record<string, string>;\r\n /** DNS query type (for DNS events) */\r\n dnsQueryType?: string;\r\n /** Whether TLS was used */\r\n tls: boolean;\r\n /** Request body hash (we never store raw bodies) */\r\n bodyHash?: string;\r\n /** Estimated entropy of request body (0-8 bits/byte) */\r\n bodyEntropy?: number;\r\n}\r\n\r\nexport interface SentinelFinding {\r\n /** Unique finding ID (SN-series) */\r\n id: string;\r\n /** Category */\r\n category: FindingCategory | 'c2-beaconing' | 'dns-tunneling' | 'covert-channel' | 'phone-home';\r\n /** Severity */\r\n severity: Severity;\r\n /** Short title */\r\n title: string;\r\n /** Detailed description */\r\n description: string;\r\n /** The server that triggered this */\r\n serverName: string;\r\n /** Evidence — the specific network events that triggered this */\r\n evidence: NetworkEvent[];\r\n /** Recommendation */\r\n recommendation: string;\r\n /** Confidence score (0-100) — how sure Sentinel is this is malicious */\r\n confidence: number;\r\n}\r\n\r\nexport type SentinelThreatLevel = 'clean' | 'suspicious' | 'malicious' | 'critical';\r\n\r\nexport interface SentinelReport {\r\n /** Server being monitored */\r\n serverName: string;\r\n /** Monitoring duration in seconds */\r\n monitoringDuration: number;\r\n /** Total network events captured */\r\n totalEvents: number;\r\n /** Unique destinations contacted */\r\n uniqueDestinations: string[];\r\n /** Sentinel findings */\r\n findings: SentinelFinding[];\r\n /** Overall threat level */\r\n threatLevel: SentinelThreatLevel;\r\n /** Threat score (0-100, inverse of trust — higher = more dangerous) */\r\n threatScore: number;\r\n /** When monitoring started */\r\n startedAt: string;\r\n /** When monitoring ended */\r\n endedAt: string;\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// ENDPOINT REPUTATION PATTERNS\r\n// Known malicious or suspicious destination patterns.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport interface EndpointPattern {\r\n id: string;\r\n severity: Severity;\r\n title: string;\r\n /** Regex to match against the full URL */\r\n urlPattern: RegExp;\r\n /** Regex to match against destination IP (optional) */\r\n ipPattern?: RegExp;\r\n description: string;\r\n recommendation: string;\r\n}\r\n\r\nexport const SUSPICIOUS_ENDPOINT_PATTERNS: EndpointPattern[] = [\r\n {\r\n id: 'SN-001',\r\n severity: 'critical',\r\n title: 'Known data exfiltration endpoint',\r\n urlPattern: /https?:\\/\\/[^/]*(?:pastebin\\.com|hastebin\\.com|ghostbin\\.co|paste\\.ee|dpaste\\.org|transfer\\.sh|file\\.io|0x0\\.st|ix\\.io)\\/(?:api|raw|upload|documents)/i,\r\n description:\r\n 'MCP server is sending data to a paste/file sharing service commonly used for data exfiltration.',\r\n recommendation:\r\n 'CRITICAL: Stop this MCP server immediately. Legitimate tools should never upload to paste services.',\r\n },\r\n {\r\n id: 'SN-002',\r\n severity: 'critical',\r\n title: 'Webhook exfiltration channel',\r\n urlPattern: /https?:\\/\\/(?:hooks\\.slack\\.com|discord(?:app)?\\.com\\/api\\/webhooks|webhook\\.site|pipedream\\.net|requestbin\\.|hookbin\\.|beeceptor\\.com)/i,\r\n description:\r\n 'MCP server is sending data to a webhook endpoint. Attackers commonly use webhook services as low-noise exfiltration channels.',\r\n recommendation:\r\n 'Investigate what data is being sent to this webhook. This is a common attacker exfiltration method.',\r\n },\r\n {\r\n id: 'SN-003',\r\n severity: 'high',\r\n title: 'Dynamic DNS destination',\r\n urlPattern: /https?:\\/\\/[^/]*\\.(?:duckdns\\.org|no-ip\\.com|ngrok\\.io|ngrok-free\\.app|serveo\\.net|localhost\\.run|bore\\.digital|tailscale\\.io)(?:\\/|$)/i,\r\n description:\r\n 'MCP server is connecting to a dynamic DNS or tunneling service, commonly used for C2 infrastructure.',\r\n recommendation:\r\n 'Review this connection. Dynamic DNS is frequently used by attackers to rotate C2 endpoints.',\r\n },\r\n {\r\n id: 'SN-004',\r\n severity: 'high',\r\n title: 'Cryptocurrency-related endpoint',\r\n urlPattern: /https?:\\/\\/[^/]*(?:blockchain\\.info|etherscan\\.io|bscscan\\.com|solscan\\.io|mempool\\.space)\\/(?:api|rawaddr|tx|address)/i,\r\n description:\r\n 'MCP server is querying cryptocurrency blockchain APIs, which could indicate wallet scanning or theft.',\r\n recommendation:\r\n 'Unless this is an explicitly crypto-related tool, this connection is highly suspicious.',\r\n },\r\n {\r\n id: 'SN-005',\r\n severity: 'medium',\r\n title: 'Telemetry to unknown endpoint',\r\n urlPattern: /https?:\\/\\/[^/]*(?:\\/(?:telemetry|analytics|tracking|pixel|beacon|collect|event|metrics|heartbeat))(?:\\?|$|\\/)/i,\r\n description:\r\n 'MCP server is sending telemetry/analytics data. While sometimes legitimate, this can mask data exfiltration.',\r\n recommendation:\r\n 'Verify the telemetry destination is expected. Compare against the MCP server\\'s documentation.',\r\n },\r\n {\r\n id: 'SN-006',\r\n severity: 'critical',\r\n title: 'Raw IP connection (no hostname)',\r\n urlPattern: /https?:\\/\\/(?:\\d{1,3}\\.){3}\\d{1,3}(?::\\d+)?(?:\\/|$)/,\r\n description:\r\n 'MCP server is connecting directly to an IP address instead of a hostname. This bypasses DNS-based monitoring and is a strong indicator of C2 communication.',\r\n recommendation:\r\n 'CRITICAL: Direct IP connections are almost never legitimate for MCP servers. Investigate immediately.',\r\n },\r\n {\r\n id: 'SN-007',\r\n severity: 'high',\r\n title: 'Non-standard port connection',\r\n urlPattern: /https?:\\/\\/[^/]+:(?!80\\b|443\\b|8080\\b|8443\\b|3000\\b|5000\\b|8000\\b)\\d{4,5}(?:\\/|$)/,\r\n description:\r\n 'MCP server is connecting to a non-standard port. While sometimes legitimate, C2 infrastructure often uses unusual ports.',\r\n recommendation:\r\n 'Verify this port is expected for the service being contacted.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// BEHAVIORAL DETECTION PATTERNS\r\n// These don't match URLs — they match *patterns of behavior*\r\n// across multiple network events. The Sentinel engine\r\n// evaluates these over time windows.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport interface BehavioralPattern {\r\n id: string;\r\n severity: Severity;\r\n title: string;\r\n description: string;\r\n recommendation: string;\r\n /** Minimum number of events needed to trigger */\r\n minEvents: number;\r\n /** Time window in seconds to analyze */\r\n timeWindowSeconds: number;\r\n /** The detection function — returns confidence 0-100 */\r\n detect: (events: NetworkEvent[]) => number;\r\n}\r\n\r\nexport const BEHAVIORAL_PATTERNS: BehavioralPattern[] = [\r\n {\r\n id: 'SN-010',\r\n severity: 'critical',\r\n title: 'C2 beaconing detected (regular interval)',\r\n description:\r\n 'MCP server is making network requests at regular intervals to the same destination, consistent with command-and-control beaconing. Malware phones home on a schedule to receive commands.',\r\n recommendation:\r\n 'CRITICAL: This pattern strongly indicates the MCP server is compromised. Stop it immediately and investigate the destination.',\r\n minEvents: 5,\r\n timeWindowSeconds: 300, // 5 minutes\r\n detect: (events: NetworkEvent[]): number => {\r\n // Group events by destination\r\n const byDest = new Map<string, number[]>();\r\n for (const e of events) {\r\n const dest = new URL(e.url).hostname;\r\n if (!byDest.has(dest)) byDest.set(dest, []);\r\n byDest.get(dest)!.push(e.timestamp);\r\n }\r\n\r\n let maxConfidence = 0;\r\n for (const [, timestamps] of byDest) {\r\n if (timestamps.length < 5) continue;\r\n timestamps.sort((a, b) => a - b);\r\n\r\n // Calculate intervals between consecutive requests\r\n const intervals: number[] = [];\r\n for (let i = 1; i < timestamps.length; i++) {\r\n intervals.push(timestamps[i] - timestamps[i - 1]);\r\n }\r\n\r\n // Calculate coefficient of variation (std/mean)\r\n // Low CV = regular intervals = beaconing\r\n const mean = intervals.reduce((s, v) => s + v, 0) / intervals.length;\r\n const std = Math.sqrt(\r\n intervals.reduce((s, v) => s + (v - mean) ** 2, 0) / intervals.length\r\n );\r\n const cv = mean > 0 ? std / mean : 1;\r\n\r\n // CV < 0.15 = very regular (high confidence beaconing)\r\n // CV < 0.30 = somewhat regular (medium confidence)\r\n // Add jitter tolerance — sophisticated C2 adds random jitter\r\n if (cv < 0.15) maxConfidence = Math.max(maxConfidence, 95);\r\n else if (cv < 0.25) maxConfidence = Math.max(maxConfidence, 80);\r\n else if (cv < 0.35) maxConfidence = Math.max(maxConfidence, 60);\r\n }\r\n return maxConfidence;\r\n },\r\n },\r\n {\r\n id: 'SN-011',\r\n severity: 'critical',\r\n title: 'Burst data exfiltration',\r\n description:\r\n 'MCP server sent an unusually large amount of data in a short burst to an external endpoint. This pattern matches credential dump exfiltration and file theft.',\r\n recommendation:\r\n 'CRITICAL: Investigate what data was transmitted. Check for stolen SSH keys, credentials, or source code.',\r\n minEvents: 1,\r\n timeWindowSeconds: 60, // 1 minute\r\n detect: (events: NetworkEvent[]): number => {\r\n // Look for single large requests or bursts of requests\r\n const LARGE_REQUEST_THRESHOLD = 50_000; // 50KB in a single request\r\n const BURST_THRESHOLD = 200_000; // 200KB total in time window\r\n\r\n let maxSingle = 0;\r\n let totalBytes = 0;\r\n\r\n for (const e of events) {\r\n maxSingle = Math.max(maxSingle, e.requestSize);\r\n totalBytes += e.requestSize;\r\n }\r\n\r\n if (maxSingle > LARGE_REQUEST_THRESHOLD) return 90;\r\n if (totalBytes > BURST_THRESHOLD) return 85;\r\n if (totalBytes > BURST_THRESHOLD / 2) return 60;\r\n return 0;\r\n },\r\n },\r\n {\r\n id: 'SN-012',\r\n severity: 'high',\r\n title: 'DNS tunneling suspected',\r\n description:\r\n 'MCP server is making an unusual number of DNS queries with high-entropy subdomains. This pattern matches DNS tunneling — a technique to exfiltrate data by encoding it in DNS queries, which often bypass firewalls.',\r\n recommendation:\r\n 'Investigate the DNS queries. DNS tunneling uses encoded data in subdomain labels (e.g., aGVsbG8=.evil.com).',\r\n minEvents: 10,\r\n timeWindowSeconds: 120,\r\n detect: (events: NetworkEvent[]): number => {\r\n // Filter DNS events and look for high-entropy subdomain patterns\r\n const dnsEvents = events.filter(e => e.dnsQueryType);\r\n if (dnsEvents.length < 10) return 0;\r\n\r\n let suspiciousCount = 0;\r\n for (const e of dnsEvents) {\r\n try {\r\n const hostname = new URL(e.url).hostname;\r\n const parts = hostname.split('.');\r\n // DNS tunneling creates long, random-looking subdomains\r\n for (const part of parts) {\r\n if (part.length > 30) suspiciousCount++;\r\n // Check for base64-like patterns in subdomain\r\n if (/^[A-Za-z0-9+/]{20,}={0,2}$/.test(part)) suspiciousCount++;\r\n }\r\n } catch {\r\n // URL parse failed, skip\r\n }\r\n }\r\n\r\n const ratio = suspiciousCount / dnsEvents.length;\r\n if (ratio > 0.5) return 90;\r\n if (ratio > 0.3) return 70;\r\n if (ratio > 0.1) return 40;\r\n return 0;\r\n },\r\n },\r\n {\r\n id: 'SN-013',\r\n severity: 'high',\r\n title: 'Multi-destination scatter exfiltration',\r\n description:\r\n 'MCP server is distributing data across many different external destinations in a short period. Sophisticated exfiltration splits data across multiple endpoints to avoid size-based detection.',\r\n recommendation:\r\n 'Review all destinations contacted. This pattern suggests deliberate evasion of single-destination monitoring.',\r\n minEvents: 5,\r\n timeWindowSeconds: 120,\r\n detect: (events: NetworkEvent[]): number => {\r\n const destinations = new Set<string>();\r\n for (const e of events) {\r\n try {\r\n destinations.add(new URL(e.url).hostname);\r\n } catch {\r\n // skip\r\n }\r\n }\r\n\r\n // If contacting >10 unique destinations with data, very suspicious\r\n const withData = events.filter(e => e.requestSize > 500);\r\n const destWithData = new Set<string>();\r\n for (const e of withData) {\r\n try { destWithData.add(new URL(e.url).hostname); } catch { /* skip */ }\r\n }\r\n\r\n if (destWithData.size > 10) return 85;\r\n if (destWithData.size > 5) return 65;\r\n if (destWithData.size > 3 && withData.length > events.length * 0.5) return 50;\r\n return 0;\r\n },\r\n },\r\n {\r\n id: 'SN-014',\r\n severity: 'high',\r\n title: 'High-entropy payload transmission',\r\n description:\r\n 'MCP server is sending request bodies with unusually high entropy (randomness), suggesting encrypted or compressed data exfiltration. Legitimate API calls typically have structured, lower-entropy payloads.',\r\n recommendation:\r\n 'Investigate the payload contents. High entropy in outbound data often indicates stolen credentials or encrypted exfiltration.',\r\n minEvents: 3,\r\n timeWindowSeconds: 180,\r\n detect: (events: NetworkEvent[]): number => {\r\n // Look for events with high body entropy\r\n const highEntropyEvents = events.filter(\r\n e => e.bodyEntropy !== undefined && e.bodyEntropy > 7.0 && e.requestSize > 1000\r\n );\r\n\r\n if (highEntropyEvents.length === 0) return 0;\r\n const ratio = highEntropyEvents.length / events.length;\r\n\r\n if (ratio > 0.5 && highEntropyEvents.length >= 5) return 90;\r\n if (ratio > 0.3) return 70;\r\n if (highEntropyEvents.length >= 3) return 55;\r\n return 0;\r\n },\r\n },\r\n {\r\n id: 'SN-015',\r\n severity: 'medium',\r\n title: 'Unexpected outbound connection during idle',\r\n description:\r\n 'MCP server made network requests when no user-initiated tool calls were active. Legitimate MCP servers should only make network requests in response to tool invocations.',\r\n recommendation:\r\n 'Review why this MCP server is making network requests when idle. This could indicate background beaconing or telemetry.',\r\n minEvents: 2,\r\n timeWindowSeconds: 60,\r\n detect: (events: NetworkEvent[]): number => {\r\n // This detection relies on the caller marking events as \"idle\" context\r\n // For now, we detect based on timing — requests that happen without\r\n // a preceding tool call within 5 seconds\r\n // (The full implementation requires integration with the runtime proxy)\r\n if (events.length >= 3) return 50;\r\n if (events.length >= 2) return 30;\r\n return 0;\r\n },\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// CREDENTIAL-SPECIFIC EXFILTRATION PATTERNS\r\n// Patterns that look for credential-shaped data in network\r\n// requests. These check request URLs, headers, and body hashes\r\n// for signs of stolen secrets being transmitted.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const CREDENTIAL_EXFIL_PATTERNS: EndpointPattern[] = [\r\n {\r\n id: 'SN-020',\r\n severity: 'critical',\r\n title: 'SSH key in outbound request',\r\n urlPattern: /[?&](?:key|data|payload|content|body)=[^&]*(?:ssh-rsa|ssh-ed25519|PRIVATE\\s*KEY)/i,\r\n description:\r\n 'MCP server appears to be transmitting SSH key material in a URL parameter. This matches the Invariant Labs exfiltration attack vector.',\r\n recommendation:\r\n 'CRITICAL: Your SSH keys may be compromised. Rotate all SSH keys immediately.',\r\n },\r\n {\r\n id: 'SN-021',\r\n severity: 'critical',\r\n title: 'API key/token in outbound URL',\r\n urlPattern: /[?&](?:key|token|secret|api_key|apikey|auth|password|credential)=(?!(?:test|demo|example|placeholder))[A-Za-z0-9_-]{20,}/i,\r\n description:\r\n 'MCP server is transmitting what appears to be an API key or token in a URL parameter to an external destination.',\r\n recommendation:\r\n 'CRITICAL: Rotate the compromised API key/token immediately. Check your .env files for exposed secrets.',\r\n },\r\n {\r\n id: 'SN-022',\r\n severity: 'critical',\r\n title: 'AWS credential in outbound request',\r\n urlPattern: /(?:AKIA[0-9A-Z]{16}|(?:aws_secret_access_key|aws_access_key_id)\\s*[:=]\\s*[A-Za-z0-9/+=]{20,})/i,\r\n description:\r\n 'MCP server is transmitting AWS credentials. AWS access keys follow a known format (AKIA...).',\r\n recommendation:\r\n 'CRITICAL: Deactivate the compromised AWS keys immediately via IAM console.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// THREAT SCORING\r\n// Calculate a Sentinel threat score based on findings.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport function calculateThreatScore(findings: SentinelFinding[]): number {\r\n if (findings.length === 0) return 0;\r\n\r\n let score = 0;\r\n for (const f of findings) {\r\n const severityWeight =\r\n f.severity === 'critical' ? 30 :\r\n f.severity === 'high' ? 20 :\r\n f.severity === 'medium' ? 10 :\r\n f.severity === 'low' ? 5 : 2;\r\n\r\n // Weight by confidence — a 90% confidence critical is scarier than a 40% one\r\n score += severityWeight * (f.confidence / 100);\r\n }\r\n\r\n // Cap at 100\r\n return Math.min(100, Math.round(score));\r\n}\r\n\r\nexport function threatLevelFromScore(score: number): SentinelThreatLevel {\r\n if (score >= 70) return 'critical';\r\n if (score >= 40) return 'malicious';\r\n if (score >= 15) return 'suspicious';\r\n return 'clean';\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Terminal Output Formatter\r\n// ============================================================\r\n// Pretty-prints scan results with color-coded trust scores\r\n// and categorized findings for both MCP servers and skills.\r\n\r\nimport chalk from 'chalk';\r\nimport type {\r\n ScanResult,\r\n SkillScanResult,\r\n ScanSummary,\r\n Finding,\r\n TrustLevel,\r\n Severity,\r\n SkillFileType,\r\n} from '../types/index.js';\r\nimport type { SentinelReport, SentinelThreatLevel } from '../sentinel/index.js';\r\nimport { SENTINEL_MARKETING } from '../sentinel/index.js';\r\n\r\nconst TRUST_COLORS: Record<TrustLevel, (text: string) => string> = {\r\n trusted: chalk.green,\r\n caution: chalk.yellow,\r\n risky: chalk.hex('#FF8C00'), // orange\r\n dangerous: chalk.red,\r\n};\r\n\r\nconst SEVERITY_COLORS: Record<Severity, (text: string) => string> = {\r\n critical: chalk.bgRed.white.bold,\r\n high: chalk.red.bold,\r\n medium: chalk.yellow,\r\n low: chalk.blue,\r\n info: chalk.gray,\r\n};\r\n\r\nconst TRUST_ICONS: Record<TrustLevel, string> = {\r\n trusted: '✓',\r\n caution: '⚠',\r\n risky: '⚠',\r\n dangerous: '✗',\r\n};\r\n\r\nconst SEVERITY_ICONS: Record<Severity, string> = {\r\n critical: '!!!',\r\n high: '!!',\r\n medium: '!',\r\n low: 'i',\r\n info: '·',\r\n};\r\n\r\nconst FILE_TYPE_LABELS: Record<SkillFileType, string> = {\r\n 'skill.md': 'SKILL.md',\r\n 'mdc-rule': '.mdc rule',\r\n 'claude.md': 'CLAUDE.md',\r\n 'soul.md': 'SOUL.md',\r\n 'memory.md': 'MEMORY.md',\r\n};\r\n\r\n/** Print the Vigile banner */\r\nexport function printBanner(): void {\r\n console.log('');\r\n console.log(chalk.bold.hex('#2C4A7C')(' ╦ ╦╦╔═╗╦╦ ╔═╗'));\r\n console.log(chalk.bold.hex('#2C4A7C')(' ╚╗╔╝║║ ╦║║ ║╣ '));\r\n console.log(chalk.bold.hex('#2C4A7C')(' ╚╝ ╩╚═╝╩╩═╝╚═╝'));\r\n console.log(chalk.gray(' AI Agent Security Scanner'));\r\n console.log('');\r\n}\r\n\r\n/** Print scan results for a single MCP server */\r\nexport function printServerResult(result: ScanResult, verbose: boolean): void {\r\n const color = TRUST_COLORS[result.trustLevel];\r\n const icon = TRUST_ICONS[result.trustLevel];\r\n\r\n // Server header\r\n console.log(\r\n chalk.bold(` ${icon} `) +\r\n chalk.bold(result.server.name) +\r\n chalk.gray(` (${result.server.source})`) +\r\n ' ' +\r\n color(`[${result.trustScore}/100 ${result.trustLevel.toUpperCase()}]`)\r\n );\r\n\r\n // Config path\r\n console.log(chalk.gray(` Config: ${result.server.configPath}`));\r\n\r\n // Command\r\n console.log(\r\n chalk.gray(` Command: ${result.server.command} ${result.server.args.join(' ')}`)\r\n );\r\n\r\n // Findings\r\n if (result.findings.length === 0) {\r\n console.log(chalk.green(' No security issues found.'));\r\n } else {\r\n console.log(\r\n chalk.dim(` ${result.findings.length} finding(s):`)\r\n );\r\n\r\n for (const finding of result.findings) {\r\n printFinding(finding, verbose);\r\n }\r\n }\r\n\r\n // Score breakdown (verbose only)\r\n if (verbose) {\r\n printScoreBreakdown(result.scoreBreakdown);\r\n }\r\n\r\n console.log('');\r\n}\r\n\r\n/** Print scan results for a single skill file */\r\nexport function printSkillResult(result: SkillScanResult, verbose: boolean): void {\r\n const color = TRUST_COLORS[result.trustLevel];\r\n const icon = TRUST_ICONS[result.trustLevel];\r\n const typeLabel = FILE_TYPE_LABELS[result.skill.fileType] || result.skill.fileType;\r\n\r\n // Skill header\r\n console.log(\r\n chalk.bold(` ${icon} `) +\r\n chalk.bold(result.skill.name) +\r\n chalk.gray(` (${typeLabel} · ${result.skill.source} · ${result.skill.scope})`) +\r\n ' ' +\r\n color(`[${result.trustScore}/100 ${result.trustLevel.toUpperCase()}]`)\r\n );\r\n\r\n // File path\r\n console.log(chalk.gray(` Path: ${result.skill.filePath}`));\r\n\r\n // File size\r\n const sizeKB = (result.skill.size / 1024).toFixed(1);\r\n console.log(chalk.gray(` Size: ${sizeKB} KB`));\r\n\r\n // Findings\r\n if (result.findings.length === 0) {\r\n console.log(chalk.green(' No security issues found.'));\r\n } else {\r\n console.log(\r\n chalk.dim(` ${result.findings.length} finding(s):`)\r\n );\r\n\r\n for (const finding of result.findings) {\r\n printFinding(finding, verbose);\r\n }\r\n }\r\n\r\n // Score breakdown (verbose only)\r\n if (verbose) {\r\n printScoreBreakdown(result.scoreBreakdown);\r\n }\r\n\r\n console.log('');\r\n}\r\n\r\n/** Print a single finding */\r\nfunction printFinding(finding: Finding, verbose: boolean): void {\r\n const color = SEVERITY_COLORS[finding.severity];\r\n const icon = SEVERITY_ICONS[finding.severity];\r\n\r\n console.log(\r\n ` ${color(`[${icon}]`)} ${color(finding.severity.toUpperCase())} ` +\r\n chalk.white(finding.title) +\r\n chalk.gray(` (${finding.id})`)\r\n );\r\n\r\n if (verbose) {\r\n console.log(chalk.gray(` ${finding.description}`));\r\n if (finding.evidence) {\r\n console.log(chalk.gray(` Evidence: ${finding.evidence}`));\r\n }\r\n console.log(chalk.cyan(` → ${finding.recommendation}`));\r\n }\r\n}\r\n\r\n/** Print score breakdown */\r\nfunction printScoreBreakdown(b: { codeAnalysis: number; dependencyHealth: number; permissionSafety: number; behavioralStability: number; transparency: number }): void {\r\n console.log(chalk.dim(' Score Breakdown:'));\r\n console.log(chalk.dim(` Code Analysis: ${scoreBar(b.codeAnalysis)} ${b.codeAnalysis}/100 (30%)`));\r\n console.log(chalk.dim(` Dependency Health: ${scoreBar(b.dependencyHealth)} ${b.dependencyHealth}/100 (20%)`));\r\n console.log(chalk.dim(` Permission Safety: ${scoreBar(b.permissionSafety)} ${b.permissionSafety}/100 (20%)`));\r\n console.log(chalk.dim(` Behavioral Stability: ${scoreBar(b.behavioralStability)} ${b.behavioralStability}/100 (15%)`));\r\n console.log(chalk.dim(` Transparency: ${scoreBar(b.transparency)} ${b.transparency}/100 (15%)`));\r\n}\r\n\r\n/** Print the overall scan summary */\r\nexport function printSummary(summary: ScanSummary): void {\r\n console.log(chalk.bold(' ─── Scan Summary ───'));\r\n console.log('');\r\n\r\n if (summary.totalServers > 0) {\r\n console.log(` MCP servers scanned: ${chalk.bold(String(summary.totalServers))}`);\r\n }\r\n if (summary.totalSkills > 0) {\r\n console.log(` Skill files scanned: ${chalk.bold(String(summary.totalSkills))}`);\r\n }\r\n\r\n const totalScanned = summary.totalServers + summary.totalSkills;\r\n if (totalScanned > 0 && summary.totalServers > 0 && summary.totalSkills > 0) {\r\n console.log(` Total scanned: ${chalk.bold(String(totalScanned))}`);\r\n }\r\n\r\n console.log(\r\n ` ${chalk.green(`${TRUST_ICONS.trusted} Trusted: ${summary.byTrustLevel.trusted}`)} ` +\r\n `${chalk.yellow(`${TRUST_ICONS.caution} Caution: ${summary.byTrustLevel.caution}`)} ` +\r\n `${chalk.hex('#FF8C00')(`${TRUST_ICONS.risky} Risky: ${summary.byTrustLevel.risky}`)} ` +\r\n `${chalk.red(`${TRUST_ICONS.dangerous} Dangerous: ${summary.byTrustLevel.dangerous}`)}`\r\n );\r\n\r\n const totalFindings = Object.values(summary.bySeverity).reduce((a, b) => a + b, 0);\r\n if (totalFindings > 0) {\r\n console.log('');\r\n console.log(` Total findings: ${chalk.bold(String(totalFindings))}`);\r\n if (summary.bySeverity.critical > 0)\r\n console.log(chalk.bgRed.white.bold(` ${summary.bySeverity.critical} CRITICAL`));\r\n if (summary.bySeverity.high > 0)\r\n console.log(chalk.red.bold(` ${summary.bySeverity.high} HIGH`));\r\n if (summary.bySeverity.medium > 0)\r\n console.log(chalk.yellow(` ${summary.bySeverity.medium} MEDIUM`));\r\n if (summary.bySeverity.low > 0)\r\n console.log(chalk.blue(` ${summary.bySeverity.low} LOW`));\r\n if (summary.bySeverity.info > 0)\r\n console.log(chalk.gray(` ${summary.bySeverity.info} INFO`));\r\n }\r\n\r\n console.log('');\r\n console.log(chalk.gray(` Scanned at ${summary.timestamp}`));\r\n console.log(chalk.gray(` Vigile v${summary.version} — https://vigile.dev`));\r\n console.log('');\r\n}\r\n\r\n/** Print \"no servers found\" message */\r\nexport function printNoServersFound(): void {\r\n console.log(chalk.yellow(' No MCP server configurations found on this machine.'));\r\n console.log('');\r\n console.log(chalk.gray(' Vigile checks the following locations:'));\r\n console.log(chalk.gray(' • Claude Desktop config'));\r\n console.log(chalk.gray(' • Cursor MCP config'));\r\n console.log(chalk.gray(' • Claude Code config (.claude.json / .mcp.json)'));\r\n console.log(chalk.gray(' • Windsurf MCP config'));\r\n console.log(chalk.gray(' • VS Code MCP config (.vscode/mcp.json)'));\r\n console.log(chalk.gray(' • OpenClaw config (~/.openclaw/openclaw.json)'));\r\n console.log('');\r\n console.log(chalk.gray(' If you have MCP servers configured elsewhere, use:'));\r\n console.log(chalk.cyan(' vigile-scan --config /path/to/config.json'));\r\n console.log('');\r\n console.log(chalk.gray(' To also scan agent skill files, use:'));\r\n console.log(chalk.cyan(' vigile-scan --all'));\r\n console.log('');\r\n}\r\n\r\n/** Print \"no skills found\" message */\r\nexport function printNoSkillsFound(): void {\r\n console.log(chalk.yellow(' No agent skill files found on this machine.'));\r\n console.log('');\r\n console.log(chalk.gray(' Vigile scans the following skill locations:'));\r\n console.log(chalk.gray(' • Claude Code skills (.claude/skills/*/SKILL.md)'));\r\n console.log(chalk.gray(' • Claude Code commands (.claude/commands/**/*.md)'));\r\n console.log(chalk.gray(' • GitHub Copilot skills (.github/skills/*/SKILL.md)'));\r\n console.log(chalk.gray(' • Cursor rules (.cursor/rules/*.mdc, .cursorrules)'));\r\n console.log(chalk.gray(' • Memory files (CLAUDE.md, SOUL.md, MEMORY.md)'));\r\n console.log('');\r\n}\r\n\r\n/** Print \"nothing found\" message (neither servers nor skills) */\r\nexport function printNothingFound(): void {\r\n console.log(chalk.yellow(' No MCP servers or agent skill files found.'));\r\n console.log('');\r\n console.log(chalk.gray(' Try scanning a specific config:'));\r\n console.log(chalk.cyan(' vigile-scan --config /path/to/config.json'));\r\n console.log('');\r\n console.log(chalk.gray(' Or cd into a project directory that contains skill files:'));\r\n console.log(chalk.cyan(' cd /path/to/project && vigile-scan --all'));\r\n console.log('');\r\n}\r\n\r\n/** Create a mini score bar */\r\nfunction scoreBar(score: number): string {\r\n const filled = Math.round(score / 10);\r\n const empty = 10 - filled;\r\n const color =\r\n score >= 80 ? chalk.green :\r\n score >= 60 ? chalk.yellow :\r\n score >= 40 ? chalk.hex('#FF8C00') :\r\n chalk.red;\r\n return color('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));\r\n}\r\n\r\n// ============================================================\r\n// Sentinel Output\r\n// ============================================================\r\n\r\nconst SENTINEL_THREAT_COLORS: Record<SentinelThreatLevel, (text: string) => string> = {\r\n clean: chalk.green,\r\n suspicious: chalk.yellow,\r\n malicious: chalk.red,\r\n critical: chalk.bgRed.white.bold,\r\n};\r\n\r\nconst SENTINEL_THREAT_ICONS: Record<SentinelThreatLevel, string> = {\r\n clean: '✓',\r\n suspicious: '⚠',\r\n malicious: '✗',\r\n critical: '!!!',\r\n};\r\n\r\n/** Print a Sentinel monitoring report */\r\nexport function printSentinelReport(report: SentinelReport): void {\r\n const color = SENTINEL_THREAT_COLORS[report.threatLevel];\r\n const icon = SENTINEL_THREAT_ICONS[report.threatLevel];\r\n\r\n console.log('');\r\n console.log(\r\n chalk.bold(` ${icon} `) +\r\n chalk.bold(`Sentinel: ${report.serverName}`) +\r\n ' ' +\r\n color(`[Threat: ${report.threatScore}/100 ${report.threatLevel.toUpperCase()}]`)\r\n );\r\n\r\n console.log(chalk.gray(` Monitored: ${report.monitoringDuration.toFixed(0)}s | Events: ${report.totalEvents} | Destinations: ${report.uniqueDestinations.length}`));\r\n\r\n if (report.uniqueDestinations.length > 0) {\r\n console.log(chalk.gray(` Destinations:`));\r\n for (const dest of report.uniqueDestinations.slice(0, 10)) {\r\n console.log(chalk.gray(` → ${dest}`));\r\n }\r\n if (report.uniqueDestinations.length > 10) {\r\n console.log(chalk.gray(` ... and ${report.uniqueDestinations.length - 10} more`));\r\n }\r\n }\r\n\r\n if (report.findings.length === 0) {\r\n console.log(chalk.green(' No suspicious network behavior detected.'));\r\n } else {\r\n console.log(chalk.dim(` ${report.findings.length} finding(s):`));\r\n for (const finding of report.findings) {\r\n const fColor = SEVERITY_COLORS[finding.severity];\r\n const fIcon = SEVERITY_ICONS[finding.severity];\r\n console.log(\r\n ` ${fColor(`[${fIcon}]`)} ${fColor(finding.severity.toUpperCase())} ` +\r\n chalk.white(finding.title) +\r\n chalk.gray(` (${finding.id})`)\r\n );\r\n console.log(chalk.gray(` ${finding.description}`));\r\n if (finding.evidence && finding.evidence.length > 0) {\r\n console.log(chalk.gray(` Evidence: ${finding.evidence.length} network event(s)`));\r\n }\r\n console.log(chalk.cyan(` → ${finding.recommendation}`));\r\n }\r\n }\r\n\r\n console.log(chalk.gray(` ${report.startedAt} → ${report.endedAt}`));\r\n console.log('');\r\n}\r\n\r\n/** Print the Sentinel upgrade prompt (for free tier) */\r\nexport function printSentinelUpgrade(): void {\r\n console.log('');\r\n console.log(chalk.bold.hex('#2C4A7C')(` 🛡️ ${SENTINEL_MARKETING.featureName}`));\r\n console.log(chalk.white(` ${SENTINEL_MARKETING.tagline}`));\r\n console.log('');\r\n for (const cat of SENTINEL_MARKETING.categories) {\r\n console.log(chalk.white(` ${cat.icon} ${chalk.bold(cat.name)}`));\r\n console.log(chalk.gray(` ${cat.desc}`));\r\n }\r\n console.log('');\r\n console.log(chalk.yellow(` ⚡ Sentinel is a Pro feature. Upgrade to unlock runtime monitoring:`));\r\n console.log(chalk.cyan(` https://vigile.dev/pricing`));\r\n console.log('');\r\n console.log(chalk.gray(` Pro ($29/mo) — 5-min sessions, 3 servers, behavioral detection`));\r\n console.log(chalk.gray(` Pro+ ($99/mo) — 30-min sessions, 10 servers, DNS tunneling & C2 detection`));\r\n console.log('');\r\n}\r\n\r\n// ============================================================\r\n// API Upload & Auth Output\r\n// ============================================================\r\n\r\n/** Print auth status info */\r\nexport function printAuthStatus(info: {\r\n authenticated: boolean;\r\n source?: 'env' | 'config';\r\n email?: string;\r\n tier?: string;\r\n name?: string;\r\n error?: string;\r\n}): void {\r\n if (info.authenticated) {\r\n console.log(chalk.green(` Authenticated as ${info.email}`));\r\n console.log(chalk.gray(` Tier: ${(info.tier || 'free').toUpperCase()}`));\r\n if (info.name) {\r\n console.log(chalk.gray(` Name: ${info.name}`));\r\n }\r\n console.log(\r\n chalk.gray(\r\n ` Source: ${info.source === 'env' ? 'VIGILE_TOKEN env var' : '~/.vigile/config.json'}`,\r\n ),\r\n );\r\n } else {\r\n console.log(chalk.yellow(' Not authenticated.'));\r\n if (info.error) {\r\n console.log(chalk.red(` Error: ${info.error}`));\r\n }\r\n console.log(chalk.gray(' Run `vigile-scan auth login` or set VIGILE_TOKEN to authenticate.'));\r\n }\r\n console.log('');\r\n}\r\n\r\n/** Print auth login success */\r\nexport function printAuthLoginSuccess(email: string, tier: string): void {\r\n console.log('');\r\n console.log(chalk.green(' Authenticated successfully!'));\r\n console.log(chalk.gray(` Email: ${email}`));\r\n console.log(chalk.gray(` Tier: ${tier.toUpperCase()}`));\r\n console.log(chalk.gray(' Token stored in ~/.vigile/config.json'));\r\n console.log('');\r\n}\r\n\r\n/** Print upload success summary */\r\nexport function printUploadSuccess(summary: {\r\n mcpUploaded: number;\r\n skillsUploaded: number;\r\n failures: number;\r\n}): void {\r\n const total = summary.mcpUploaded + summary.skillsUploaded;\r\n if (total > 0) {\r\n console.log(chalk.green(` Uploaded ${total} result(s) to Vigile registry.`));\r\n if (summary.mcpUploaded > 0) {\r\n console.log(chalk.gray(` MCP servers: ${summary.mcpUploaded}`));\r\n }\r\n if (summary.skillsUploaded > 0) {\r\n console.log(chalk.gray(` Skills: ${summary.skillsUploaded}`));\r\n }\r\n }\r\n if (summary.failures > 0) {\r\n console.log(chalk.yellow(` ${summary.failures} upload(s) failed (results saved locally).`));\r\n }\r\n console.log('');\r\n}\r\n\r\n/** Print upload skip message (when not authenticated or --no-upload) */\r\nexport function printUploadSkipped(reason: 'not-authenticated' | 'no-upload-flag'): void {\r\n if (reason === 'not-authenticated') {\r\n console.log(\r\n chalk.gray(' Tip: Run `vigile-scan auth login` to upload results to the Vigile registry.'),\r\n );\r\n console.log('');\r\n }\r\n // For --no-upload flag, we stay silent (user explicitly opted out)\r\n}\r\n","// ============================================================\r\n// Vigile CLI — JSON Output Formatter\r\n// ============================================================\r\n// Outputs scan results as structured JSON for CI/CD integration,\r\n// piping to other tools, or sending to the Vigile API.\r\n\r\nimport type { ScanSummary } from '../types/index.js';\r\n\r\n/**\r\n * Format scan summary as JSON string.\r\n */\r\nexport function formatJSON(summary: ScanSummary): string {\r\n return JSON.stringify(summary, null, 2);\r\n}\r\n\r\n/**\r\n * Format scan summary as compact JSON (single line, for piping).\r\n */\r\nexport function formatJSONCompact(summary: ScanSummary): string {\r\n return JSON.stringify(summary);\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Auth Management\r\n// ============================================================\r\n// Handles token resolution, persistent storage in ~/.vigile/config.json,\r\n// and provides the auth login/status/logout logic.\r\n//\r\n// Token resolution priority:\r\n// 1. VIGILE_TOKEN environment variable (CI/CD friendly)\r\n// 2. ~/.vigile/config.json (persistent local config)\r\n\r\nimport { readFile, writeFile, mkdir } from 'fs/promises';\r\nimport { join } from 'path';\r\nimport { homedir } from 'os';\r\nimport { VigileApiClient, type ApiUserInfo } from './client.js';\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Config file paths\r\n// ──────────────────────────────────────────────────────────\r\n\r\nconst CONFIG_DIR = join(homedir(), '.vigile');\r\nconst CONFIG_FILE = join(CONFIG_DIR, 'config.json');\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Config shape\r\n// ──────────────────────────────────────────────────────────\r\n\r\ninterface VigileConfig {\r\n token?: string;\r\n api_url?: string;\r\n user?: {\r\n email: string;\r\n tier: string;\r\n name?: string;\r\n };\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Token & URL Resolution\r\n// ──────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Resolve the API token.\r\n * Priority: VIGILE_TOKEN env var > ~/.vigile/config.json\r\n */\r\nexport async function resolveToken(): Promise<string | null> {\r\n // 1. Environment variable takes precedence\r\n const envToken = process.env.VIGILE_TOKEN;\r\n if (envToken && envToken.length > 0) {\r\n return envToken;\r\n }\r\n\r\n // 2. Config file\r\n const config = await loadConfig();\r\n return config.token || null;\r\n}\r\n\r\n/**\r\n * Resolve the API base URL.\r\n * Priority: VIGILE_API_URL env var > ~/.vigile/config.json > default\r\n */\r\nexport async function resolveApiUrl(): Promise<string> {\r\n const envUrl = process.env.VIGILE_API_URL;\r\n if (envUrl) return envUrl;\r\n\r\n const config = await loadConfig();\r\n return config.api_url || 'https://api.vigile.dev';\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Config File Operations\r\n// ──────────────────────────────────────────────────────────\r\n\r\nasync function loadConfig(): Promise<VigileConfig> {\r\n try {\r\n const raw = await readFile(CONFIG_FILE, 'utf-8');\r\n return JSON.parse(raw) as VigileConfig;\r\n } catch {\r\n return {};\r\n }\r\n}\r\n\r\nasync function saveConfig(config: VigileConfig): Promise<void> {\r\n await mkdir(CONFIG_DIR, { recursive: true });\r\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), {\r\n mode: 0o600, // Owner read/write only\r\n });\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Auth Actions\r\n// ──────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Login: validate a token against the API and store it persistently.\r\n */\r\nexport async function authLogin(token: string): Promise<{\r\n success: boolean;\r\n user?: ApiUserInfo;\r\n error?: string;\r\n}> {\r\n const apiUrl = await resolveApiUrl();\r\n const client = new VigileApiClient(apiUrl, token);\r\n const result = await client.getMe();\r\n\r\n if (!result.ok) {\r\n return { success: false, error: result.error };\r\n }\r\n\r\n // Persist token and user info\r\n const config = await loadConfig();\r\n config.token = token;\r\n config.user = {\r\n email: result.data.email,\r\n tier: result.data.tier,\r\n name: result.data.name || undefined,\r\n };\r\n await saveConfig(config);\r\n\r\n return { success: true, user: result.data };\r\n}\r\n\r\n/**\r\n * Check current authentication status.\r\n */\r\nexport async function authStatus(): Promise<{\r\n authenticated: boolean;\r\n source?: 'env' | 'config';\r\n user?: ApiUserInfo;\r\n error?: string;\r\n}> {\r\n const envToken = process.env.VIGILE_TOKEN;\r\n const config = await loadConfig();\r\n const token = envToken || config.token;\r\n\r\n if (!token) {\r\n return { authenticated: false };\r\n }\r\n\r\n const source: 'env' | 'config' = envToken ? 'env' : 'config';\r\n const apiUrl = await resolveApiUrl();\r\n const client = new VigileApiClient(apiUrl, token);\r\n const result = await client.getMe();\r\n\r\n if (!result.ok) {\r\n return { authenticated: false, source, error: result.error };\r\n }\r\n\r\n return { authenticated: true, source, user: result.data };\r\n}\r\n\r\n/**\r\n * Logout: clear stored credentials from config file.\r\n */\r\nexport async function authLogout(): Promise<void> {\r\n const config = await loadConfig();\r\n delete config.token;\r\n delete config.user;\r\n await saveConfig(config);\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Convenience: get an authenticated client (or null)\r\n// ──────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Create an authenticated API client, or return null if no token is available.\r\n * Used by the scan upload step and Sentinel API integration.\r\n */\r\nexport async function getAuthenticatedClient(): Promise<VigileApiClient | null> {\r\n const token = await resolveToken();\r\n if (!token) return null;\r\n\r\n const apiUrl = await resolveApiUrl();\r\n return new VigileApiClient(apiUrl, token);\r\n}\r\n","// ============================================================\r\n// Vigile CLI — API Client\r\n// ============================================================\r\n// Lightweight HTTP client for communicating with the Vigile API.\r\n// Uses Node 18+ native fetch. Graceful degradation: if the API\r\n// is unreachable, the CLI continues in local-only mode.\r\n//\r\n// All methods return a discriminated union:\r\n// { ok: true, data: T } | { ok: false, error: string, status: number }\r\n// This avoids thrown exceptions and makes error handling explicit.\r\n\r\nconst DEFAULT_API_URL = 'https://api.vigile.dev';\r\nconst API_TIMEOUT_MS = 15_000; // 15 seconds\r\nconst CLI_VERSION = '0.2.0';\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Response Types\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport interface ApiUserInfo {\r\n id: number;\r\n email: string;\r\n name: string | null;\r\n tier: string; // 'free' | 'pro' (team/enterprise planned for future)\r\n}\r\n\r\nexport interface ApiScanResponse {\r\n id: number;\r\n server_name: string;\r\n trust_score: number;\r\n trust_level: string;\r\n score_breakdown: Record<string, number> | null;\r\n findings: Array<Record<string, unknown>>;\r\n findings_count: number;\r\n critical_count: number;\r\n high_count: number;\r\n scanner_version: string;\r\n scanned_at: string;\r\n}\r\n\r\nexport interface ApiSkillScanResponse {\r\n id: number;\r\n skill_name: string;\r\n file_type: string;\r\n platform: string;\r\n trust_score: number;\r\n trust_level: string;\r\n score_breakdown: Record<string, number> | null;\r\n findings: Array<Record<string, unknown>>;\r\n findings_count: number;\r\n critical_count: number;\r\n high_count: number;\r\n scanner_version: string;\r\n scanned_at: string;\r\n}\r\n\r\nexport interface ApiSentinelSessionResponse {\r\n session_id: number;\r\n server_name: string;\r\n max_duration_seconds: number;\r\n status: string;\r\n created_at: string;\r\n}\r\n\r\nexport interface ApiSentinelEventsResponse {\r\n status: string;\r\n events_received: number;\r\n total_events: number;\r\n}\r\n\r\nexport interface ApiSentinelReportResponse {\r\n id: number;\r\n session_id: number;\r\n server_name: string;\r\n monitoring_duration: number;\r\n total_events: number;\r\n unique_destinations: string[];\r\n findings: Array<{\r\n id: string;\r\n category: string;\r\n severity: string;\r\n title: string;\r\n description: string;\r\n server_name: string;\r\n evidence_count: number;\r\n recommendation: string;\r\n confidence: number;\r\n }>;\r\n threat_level: string;\r\n threat_score: number;\r\n started_at: string;\r\n ended_at: string;\r\n user_tier: string;\r\n}\r\n\r\n/** Discriminated union result type */\r\nexport type ApiResult<T> =\r\n | { ok: true; data: T }\r\n | { ok: false; error: string; status: number };\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// API Client\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport class VigileApiClient {\r\n private baseUrl: string;\r\n private token: string | null;\r\n\r\n constructor(baseUrl?: string, token?: string | null) {\r\n const rawUrl = (baseUrl || process.env.VIGILE_API_URL || DEFAULT_API_URL)\r\n .replace(/\\/+$/, ''); // strip trailing slashes\r\n // Validate URL — must be HTTPS (unless localhost for development)\r\n try {\r\n const u = new URL(rawUrl);\r\n if (u.protocol !== 'https:' && u.hostname !== 'localhost' && u.hostname !== '127.0.0.1') {\r\n console.error(`[vigile] API URL must use HTTPS — falling back to default`);\r\n this.baseUrl = DEFAULT_API_URL;\r\n } else {\r\n this.baseUrl = rawUrl;\r\n }\r\n } catch {\r\n console.error(`[vigile] Invalid API URL — falling back to default`);\r\n this.baseUrl = DEFAULT_API_URL;\r\n }\r\n this.token = token || null;\r\n }\r\n\r\n get isAuthenticated(): boolean {\r\n return this.token !== null && this.token.length > 0;\r\n }\r\n\r\n // ── Private helpers ──\r\n\r\n private async request<T>(\r\n method: string,\r\n path: string,\r\n body?: unknown,\r\n ): Promise<ApiResult<T>> {\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT_MS);\r\n\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n 'User-Agent': `vigile-cli/${CLI_VERSION}`,\r\n };\r\n if (this.token) {\r\n headers['Authorization'] = `Bearer ${this.token}`;\r\n }\r\n\r\n try {\r\n const response = await fetch(`${this.baseUrl}${path}`, {\r\n method,\r\n headers,\r\n body: body ? JSON.stringify(body) : undefined,\r\n signal: controller.signal,\r\n });\r\n\r\n clearTimeout(timeoutId);\r\n\r\n if (!response.ok) {\r\n const errorBody = await response.text();\r\n let errorMessage: string;\r\n try {\r\n const parsed = JSON.parse(errorBody);\r\n errorMessage = typeof parsed.detail === 'string'\r\n ? parsed.detail\r\n : JSON.stringify(parsed.detail);\r\n } catch {\r\n errorMessage = errorBody || `HTTP ${response.status}`;\r\n }\r\n return { ok: false, error: errorMessage, status: response.status };\r\n }\r\n\r\n const data = (await response.json()) as T;\r\n return { ok: true, data };\r\n } catch (err) {\r\n clearTimeout(timeoutId);\r\n if (err instanceof Error && err.name === 'AbortError') {\r\n return { ok: false, error: 'Request timed out', status: 0 };\r\n }\r\n return {\r\n ok: false,\r\n error: err instanceof Error ? err.message : 'Network error',\r\n status: 0,\r\n };\r\n }\r\n }\r\n\r\n // ── Auth ──\r\n\r\n async getMe(): Promise<ApiResult<ApiUserInfo>> {\r\n return this.request<ApiUserInfo>('GET', '/api/v1/auth/me');\r\n }\r\n\r\n // ── Scanning ──\r\n\r\n async submitMCPScan(payload: {\r\n server_name: string;\r\n source?: string;\r\n package_url?: string;\r\n repo_url?: string;\r\n description?: string;\r\n readme?: string;\r\n tool_descriptions?: string[];\r\n maintainer?: string;\r\n license?: string;\r\n homepage?: string;\r\n }): Promise<ApiResult<ApiScanResponse>> {\r\n return this.request<ApiScanResponse>('POST', '/api/v1/scan/', payload);\r\n }\r\n\r\n async submitSkillScan(payload: {\r\n skill_name: string;\r\n content: string;\r\n file_type?: string;\r\n platform?: string;\r\n source?: string;\r\n }): Promise<ApiResult<ApiSkillScanResponse>> {\r\n return this.request<ApiSkillScanResponse>('POST', '/api/v1/scan/skill', payload);\r\n }\r\n\r\n // ── Sentinel ──\r\n\r\n async createSentinelSession(\r\n serverName: string,\r\n durationSeconds: number,\r\n client: string = 'cli',\r\n ): Promise<ApiResult<ApiSentinelSessionResponse>> {\r\n return this.request<ApiSentinelSessionResponse>('POST', '/api/v1/sentinel/sessions', {\r\n server_name: serverName,\r\n duration_seconds: durationSeconds,\r\n client,\r\n });\r\n }\r\n\r\n async submitSentinelEvents(\r\n sessionId: number,\r\n events: Array<{\r\n timestamp: number;\r\n server_name: string;\r\n method: string;\r\n url: string;\r\n destination_ip?: string | null;\r\n port: number;\r\n request_size: number;\r\n response_size?: number | null;\r\n status_code?: number | null;\r\n headers?: Record<string, string> | null;\r\n dns_query_type?: string | null;\r\n tls: boolean;\r\n body_hash?: string | null;\r\n body_entropy?: number | null;\r\n }>,\r\n ): Promise<ApiResult<ApiSentinelEventsResponse>> {\r\n return this.request<ApiSentinelEventsResponse>(\r\n 'POST',\r\n `/api/v1/sentinel/sessions/${sessionId}/events`,\r\n { session_id: sessionId, events },\r\n );\r\n }\r\n\r\n async analyzeSentinelSession(\r\n sessionId: number,\r\n ): Promise<ApiResult<ApiSentinelReportResponse>> {\r\n return this.request<ApiSentinelReportResponse>(\r\n 'POST',\r\n `/api/v1/sentinel/sessions/${sessionId}/analyze`,\r\n );\r\n }\r\n}\r\n","// ============================================================\r\n// Vigile CLI — BaaS Secret Pattern Library\r\n// ============================================================\r\n// Regex patterns for detecting exposed credentials in JavaScript\r\n// bundles, environment variable leaks, and deployed web apps.\r\n//\r\n// 150 patterns across 40+ providers.\r\n// ============================================================\r\n\r\nexport interface SecretPattern {\r\n /** Unique pattern ID (SP-001 through SP-150) */\r\n id: string;\r\n /** Human-readable pattern name */\r\n name: string;\r\n /** Service/provider that issued this credential type */\r\n provider: string;\r\n /** How bad is exposure of this credential type */\r\n severity: 'critical' | 'high' | 'medium' | 'low';\r\n /** Regex to detect the credential */\r\n pattern: RegExp;\r\n /** What this credential type grants access to */\r\n description: string;\r\n /** Remediation guidance */\r\n recommendation: string;\r\n}\r\n\r\nexport interface SecretMatch {\r\n pattern: SecretPattern;\r\n /** Masked version of the matched value (first4***last4) */\r\n match: string;\r\n /** Surrounding text for context */\r\n context: string;\r\n}\r\n\r\nfunction mask(secret: string): string {\r\n if (secret.length <= 8) return '***';\r\n return `${secret.slice(0, 4)}***${secret.slice(-4)}`;\r\n}\r\n\r\n// ============================================================\r\n// Pattern Registry — 150 patterns across 40+ providers\r\n// ============================================================\r\n\r\nexport const SECRET_PATTERNS: SecretPattern[] = [\r\n // ── AI / LLM Providers ──\r\n\r\n {\r\n id: 'SP-001',\r\n name: 'OpenAI API Key',\r\n provider: 'openai',\r\n severity: 'critical',\r\n pattern: /sk-[A-Za-z0-9]{48}/,\r\n description: 'OpenAI API key grants full access to GPT-4, Whisper, DALL-E, and all models. Exposed keys result in immediate billing charges.',\r\n recommendation: 'Rotate at platform.openai.com/api-keys immediately. Move all AI calls to a server-side proxy.',\r\n },\r\n {\r\n id: 'SP-002',\r\n name: 'OpenAI Project API Key',\r\n provider: 'openai',\r\n severity: 'critical',\r\n pattern: /sk-proj-[A-Za-z0-9_-]{80,120}/,\r\n description: 'OpenAI project-scoped API key. Exposed keys grant API access within the project scope.',\r\n recommendation: 'Rotate at platform.openai.com/api-keys. Never include in client-side JavaScript.',\r\n },\r\n {\r\n id: 'SP-003',\r\n name: 'OpenAI Organization ID',\r\n provider: 'openai',\r\n severity: 'medium',\r\n pattern: /org-[A-Za-z0-9]{24}/,\r\n description: 'OpenAI organization identifier. Exposure can leak organizational structure.',\r\n recommendation: 'Treat as sensitive. Pass server-side only.',\r\n },\r\n {\r\n id: 'SP-004',\r\n name: 'Anthropic API Key',\r\n provider: 'anthropic',\r\n severity: 'critical',\r\n pattern: /sk-ant-api\\d{2}-[A-Za-z0-9_-]{93,100}/,\r\n description: 'Anthropic Claude API key. Grants access to Claude Sonnet, Opus, and Haiku. Full billing exposure.',\r\n recommendation: 'Rotate at console.anthropic.com. All Claude API calls must be server-side.',\r\n },\r\n {\r\n id: 'SP-005',\r\n name: 'Anthropic Session Token',\r\n provider: 'anthropic',\r\n severity: 'critical',\r\n pattern: /sk-ant-oau\\d{2}-[A-Za-z0-9_-]{80,}/,\r\n description: 'Anthropic OAuth session token. Short-lived but grants full account access if intercepted.',\r\n recommendation: 'These should never appear in bundles. Remove immediately and revoke session.',\r\n },\r\n {\r\n id: 'SP-006',\r\n name: 'HuggingFace User Token',\r\n provider: 'huggingface',\r\n severity: 'high',\r\n pattern: /hf_[A-Za-z0-9]{34,40}/,\r\n description: 'HuggingFace API token. Grants access to models, datasets, and inference API.',\r\n recommendation: 'Rotate at huggingface.co/settings/tokens. Use read-only tokens for public model access.',\r\n },\r\n {\r\n id: 'SP-007',\r\n name: 'HuggingFace Org Token',\r\n provider: 'huggingface',\r\n severity: 'high',\r\n pattern: /api_org_[A-Za-z0-9]{34,40}/,\r\n description: 'HuggingFace organization API token with org-level privileges.',\r\n recommendation: 'Rotate at huggingface.co/settings/tokens. Grant minimum required permissions.',\r\n },\r\n {\r\n id: 'SP-008',\r\n name: 'Replicate API Token',\r\n provider: 'replicate',\r\n severity: 'high',\r\n pattern: /r8_[A-Za-z0-9]{35,40}/,\r\n description: 'Replicate API token. Grants access to run ML models with per-second billing.',\r\n recommendation: 'Rotate at replicate.com/account/api-tokens. All inference must be server-side.',\r\n },\r\n {\r\n id: 'SP-009',\r\n name: 'Cohere API Key',\r\n provider: 'cohere',\r\n severity: 'high',\r\n pattern: /(?:co-|COHERE_API_KEY['\":\\s=]+)([A-Za-z0-9]{40,50})/,\r\n description: 'Cohere API key for language model inference and embeddings.',\r\n recommendation: 'Rotate at dashboard.cohere.com/api-keys.',\r\n },\r\n {\r\n id: 'SP-010',\r\n name: 'Groq API Key',\r\n provider: 'groq',\r\n severity: 'high',\r\n pattern: /gsk_[A-Za-z0-9]{48,60}/,\r\n description: 'Groq API key. Grants ultra-fast LLM inference on Llama, Mixtral, Gemma models.',\r\n recommendation: 'Rotate at console.groq.com/keys. Never ship in client bundles.',\r\n },\r\n {\r\n id: 'SP-011',\r\n name: 'Together AI API Key',\r\n provider: 'together-ai',\r\n severity: 'high',\r\n pattern: /(?:TOGETHER_API_KEY|together_api_key)['\":\\s=]+([A-Za-z0-9]{64})/i,\r\n description: 'Together AI API key for open-source LLM inference.',\r\n recommendation: 'Rotate at api.together.xyz/settings/api-keys.',\r\n },\r\n {\r\n id: 'SP-012',\r\n name: 'Mistral AI API Key',\r\n provider: 'mistral',\r\n severity: 'high',\r\n pattern: /(?:MISTRAL_API_KEY|mistral_api_key)['\":\\s=]+([A-Za-z0-9]{32,40})/i,\r\n description: 'Mistral AI API key for Mistral-7B, Mixtral, and Codestral models.',\r\n recommendation: 'Rotate at console.mistral.ai/api-keys.',\r\n },\r\n {\r\n id: 'SP-013',\r\n name: 'Perplexity API Key',\r\n provider: 'perplexity',\r\n severity: 'high',\r\n pattern: /pplx-[A-Za-z0-9]{48,60}/,\r\n description: 'Perplexity AI API key for search-augmented language model access.',\r\n recommendation: 'Rotate at perplexity.ai/settings/api.',\r\n },\r\n {\r\n id: 'SP-014',\r\n name: 'Cerebras API Key',\r\n provider: 'cerebras',\r\n severity: 'high',\r\n pattern: /csk-[A-Za-z0-9]{40,60}/,\r\n description: 'Cerebras API key for ultra-fast inference on Llama models.',\r\n recommendation: 'Rotate at cloud.cerebras.ai. All inference must be server-side.',\r\n },\r\n {\r\n id: 'SP-015',\r\n name: 'OpenRouter API Key',\r\n provider: 'openrouter',\r\n severity: 'high',\r\n pattern: /sk-or-[A-Za-z0-9]{40,60}/,\r\n description: 'OpenRouter API key providing access to 100+ AI models through a unified API.',\r\n recommendation: 'Rotate at openrouter.ai/keys.',\r\n },\r\n {\r\n id: 'SP-016',\r\n name: 'ElevenLabs API Key',\r\n provider: 'elevenlabs',\r\n severity: 'high',\r\n pattern: /(?:ELEVENLABS_API_KEY|xi-api-key)['\":\\s=]+([A-Za-z0-9_-]{32,50})/i,\r\n description: 'ElevenLabs voice synthesis API key. Grants text-to-speech and voice cloning access.',\r\n recommendation: 'Rotate at elevenlabs.io/profile/api-key.',\r\n },\r\n {\r\n id: 'SP-017',\r\n name: 'AssemblyAI API Key',\r\n provider: 'assemblyai',\r\n severity: 'high',\r\n pattern: /(?:ASSEMBLYAI_API_KEY)['\":\\s=]+([a-f0-9]{40,50})/i,\r\n description: 'AssemblyAI transcription API key. Grants access to speech-to-text and audio intelligence.',\r\n recommendation: 'Rotate at www.assemblyai.com/app/account.',\r\n },\r\n {\r\n id: 'SP-018',\r\n name: 'Deepgram API Key',\r\n provider: 'deepgram',\r\n severity: 'high',\r\n pattern: /(?:DEEPGRAM_API_KEY)['\":\\s=]+([a-f0-9]{40,50})/i,\r\n description: 'Deepgram speech recognition API key.',\r\n recommendation: 'Rotate at console.deepgram.com/project/api-key.',\r\n },\r\n {\r\n id: 'SP-019',\r\n name: 'Stability AI API Key',\r\n provider: 'stability-ai',\r\n severity: 'high',\r\n pattern: /sk-[A-Za-z0-9]{32,50}(?=['\"}\\s])/,\r\n description: 'Stability AI key for image generation (Stable Diffusion). Overlaps with sk- prefix — validate by context.',\r\n recommendation: 'Rotate at platform.stability.ai/account/keys.',\r\n },\r\n {\r\n id: 'SP-020',\r\n name: 'LangSmith API Key',\r\n provider: 'langsmith',\r\n severity: 'medium',\r\n pattern: /ls__[A-Za-z0-9_-]{32,50}/,\r\n description: 'LangSmith API key for LLM observability and tracing.',\r\n recommendation: 'Rotate at smith.langchain.com/settings.',\r\n },\r\n {\r\n id: 'SP-021',\r\n name: 'Weights & Biases API Key',\r\n provider: 'wandb',\r\n severity: 'medium',\r\n pattern: /(?:WANDB_API_KEY)['\":\\s=]+([a-f0-9]{40})/i,\r\n description: 'Weights & Biases ML experiment tracking API key.',\r\n recommendation: 'Rotate at wandb.ai/authorize.',\r\n },\r\n {\r\n id: 'SP-022',\r\n name: 'Helicone API Key',\r\n provider: 'helicone',\r\n severity: 'medium',\r\n pattern: /sk-helicone-[A-Za-z0-9_-]{30,60}/,\r\n description: 'Helicone LLM observability API key for caching and monitoring AI calls.',\r\n recommendation: 'Rotate at helicone.ai/settings.',\r\n },\r\n {\r\n id: 'SP-023',\r\n name: 'E2B API Key',\r\n provider: 'e2b',\r\n severity: 'high',\r\n pattern: /e2b_[A-Za-z0-9_-]{30,50}/,\r\n description: 'E2B sandbox API key. Grants ability to spin up code execution sandboxes — billing risk.',\r\n recommendation: 'Rotate at e2b.dev/dashboard.',\r\n },\r\n {\r\n id: 'SP-024',\r\n name: 'Tavily Search API Key',\r\n provider: 'tavily',\r\n severity: 'medium',\r\n pattern: /tvly-[A-Za-z0-9_-]{30,50}/,\r\n description: 'Tavily AI search API key.',\r\n recommendation: 'Rotate at app.tavily.com.',\r\n },\r\n {\r\n id: 'SP-025',\r\n name: 'Firecrawl API Key',\r\n provider: 'firecrawl',\r\n severity: 'medium',\r\n pattern: /fc-[A-Za-z0-9]{32,50}/,\r\n description: 'Firecrawl web scraping API key.',\r\n recommendation: 'Rotate at firecrawl.dev/account.',\r\n },\r\n {\r\n id: 'SP-026',\r\n name: 'Jina AI API Key',\r\n provider: 'jina',\r\n severity: 'medium',\r\n pattern: /jina_[A-Za-z0-9_-]{50,80}/,\r\n description: 'Jina AI API key for embeddings and search.',\r\n recommendation: 'Rotate at jina.ai/dashboard.',\r\n },\r\n {\r\n id: 'SP-027',\r\n name: 'Apify API Token',\r\n provider: 'apify',\r\n severity: 'medium',\r\n pattern: /apify_api_[A-Za-z0-9]{40,60}/,\r\n description: 'Apify web scraping platform API token.',\r\n recommendation: 'Rotate at console.apify.com/account/integrations.',\r\n },\r\n\r\n // ── Cloud Providers ──\r\n\r\n {\r\n id: 'SP-028',\r\n name: 'AWS Access Key ID',\r\n provider: 'aws',\r\n severity: 'critical',\r\n pattern: /AKIA[A-Z0-9]{16}/,\r\n description: 'AWS IAM Access Key ID. Pair this with a secret key to authenticate against any AWS service.',\r\n recommendation: 'Rotate immediately in AWS IAM console. Enable MFA. Use instance profiles instead of long-lived keys.',\r\n },\r\n {\r\n id: 'SP-029',\r\n name: 'AWS Secret Access Key',\r\n provider: 'aws',\r\n severity: 'critical',\r\n pattern: /(?:aws[_-]?secret[_-]?(?:access[_-]?)?key|AWS_SECRET(?:_ACCESS_KEY)?)['\":\\s=]+([A-Za-z0-9/+=]{40})/i,\r\n description: 'AWS IAM Secret Access Key. Combined with key ID, provides full AWS service access.',\r\n recommendation: 'Rotate immediately. Audit CloudTrail for unauthorized usage. Use IAM roles instead.',\r\n },\r\n {\r\n id: 'SP-030',\r\n name: 'AWS Session Token',\r\n provider: 'aws',\r\n severity: 'high',\r\n pattern: /(?:AWS_SESSION_TOKEN|aws_session_token)['\":\\s=]+([A-Za-z0-9/+=]{200,600})/i,\r\n description: 'Temporary AWS STS session token. Short-lived but grants API access until expiry.',\r\n recommendation: 'These expire automatically but should not be in client code.',\r\n },\r\n {\r\n id: 'SP-031',\r\n name: 'Google Cloud API Key',\r\n provider: 'google-cloud',\r\n severity: 'critical',\r\n pattern: /AIza[0-9A-Za-z\\-_]{35}/,\r\n description: 'Google Cloud / Firebase API key. Grants access to Google Maps, Firebase, YouTube, and other Google APIs depending on restrictions.',\r\n recommendation: 'Add API key restrictions in Google Cloud Console. Rotate at console.cloud.google.com/apis/credentials.',\r\n },\r\n {\r\n id: 'SP-032',\r\n name: 'Google Service Account Key',\r\n provider: 'google-cloud',\r\n severity: 'critical',\r\n pattern: /\"private_key\"\\s*:\\s*\"-----BEGIN RSA PRIVATE KEY/,\r\n description: 'Google Cloud service account private key embedded in source. Grants IAM service account impersonation.',\r\n recommendation: 'Revoke the service account key immediately. Use Workload Identity or ADC instead of key files.',\r\n },\r\n {\r\n id: 'SP-033',\r\n name: 'Google OAuth Client Secret',\r\n provider: 'google-cloud',\r\n severity: 'high',\r\n pattern: /GOCSPX-[A-Za-z0-9_-]{28}/,\r\n description: 'Google OAuth 2.0 client secret. Enables impersonation of your OAuth application.',\r\n recommendation: 'Rotate at console.cloud.google.com/apis/credentials. Keep client secrets server-side only.',\r\n },\r\n {\r\n id: 'SP-034',\r\n name: 'Firebase Web API Key',\r\n provider: 'firebase',\r\n severity: 'medium',\r\n pattern: /(?:NEXT_PUBLIC_FIREBASE_API_KEY|VITE_FIREBASE_API_KEY|firebase(?:Config)?\\.apiKey)['\":\\s=]+[\"']?(AIza[0-9A-Za-z\\-_]{35})/i,\r\n description: 'Firebase Web API Key in environment variable. The key itself is semi-public but should be paired with proper security rules.',\r\n recommendation: 'Restrict key usage in Firebase Console. Ensure Firestore and Storage rules are not open.',\r\n },\r\n {\r\n id: 'SP-035',\r\n name: 'Firebase Service Account',\r\n provider: 'firebase',\r\n severity: 'critical',\r\n pattern: /(?:FIREBASE_SERVICE_ACCOUNT|firebase[_-]admin[_-]sdk)['\":\\s=]+/i,\r\n description: 'Firebase Admin SDK service account credentials. Grants admin-level access bypassing all security rules.',\r\n recommendation: 'Never include Firebase Admin credentials in client bundles. Use client SDK on frontend, Admin SDK on backend only.',\r\n },\r\n {\r\n id: 'SP-036',\r\n name: 'Supabase Service Role Key',\r\n provider: 'supabase',\r\n severity: 'critical',\r\n pattern: /(?:SUPABASE_SERVICE_ROLE_KEY|SUPABASE_SERVICE_KEY|supabase[_-]service[_-]role)['\":\\s=]+[\"']?(eyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+)/i,\r\n description: 'Supabase service role key. Bypasses ALL Row Level Security policies — full database admin access.',\r\n recommendation: 'Immediately rotate in Supabase dashboard > Project Settings > API. This key MUST NEVER be in client code.',\r\n },\r\n {\r\n id: 'SP-037',\r\n name: 'Supabase Anon Key (Public)',\r\n provider: 'supabase',\r\n severity: 'low',\r\n pattern: /(?:NEXT_PUBLIC_SUPABASE_ANON_KEY|VITE_SUPABASE_ANON_KEY|supabase[_-]anon[_-]key)['\":\\s=]+[\"']?(eyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+)/i,\r\n description: 'Supabase anon/public key. This is designed to be public, but without proper RLS policies, it can expose your data.',\r\n recommendation: 'Safe to be public ONLY if RLS policies are correctly configured on all tables.',\r\n },\r\n {\r\n id: 'SP-038',\r\n name: 'Supabase URL',\r\n provider: 'supabase',\r\n severity: 'low',\r\n pattern: /https:\\/\\/[a-z0-9]{20}\\.supabase\\.co/,\r\n description: 'Supabase project URL. The unique identifier for your Supabase project.',\r\n recommendation: 'Exposure is low-risk, but combine with anon key check to assess full exposure surface.',\r\n },\r\n {\r\n id: 'SP-039',\r\n name: 'Azure Storage Connection String',\r\n provider: 'azure',\r\n severity: 'critical',\r\n pattern: /DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[A-Za-z0-9+/=]{88}/,\r\n description: 'Azure Storage Account connection string. Grants full read/write access to all blobs, queues, and tables.',\r\n recommendation: 'Rotate in Azure Portal > Storage Account > Access Keys. Use Managed Identity or SAS tokens instead.',\r\n },\r\n {\r\n id: 'SP-040',\r\n name: 'Azure Subscription Key',\r\n provider: 'azure',\r\n severity: 'high',\r\n pattern: /(?:Ocp-Apim-Subscription-Key|azure[_-]?subscription[_-]?key)['\":\\s=]+([A-Za-z0-9]{32})/i,\r\n description: 'Azure API Management subscription key.',\r\n recommendation: 'Rotate in Azure API Management portal.',\r\n },\r\n {\r\n id: 'SP-041',\r\n name: 'Azure AD Client Secret',\r\n provider: 'azure',\r\n severity: 'critical',\r\n pattern: /(?:AZURE_CLIENT_SECRET|azure[_-]?client[_-]?secret)['\":\\s=]+([A-Za-z0-9~.@-]{34,40})/i,\r\n description: 'Azure Active Directory application client secret. Grants OAuth access as the application.',\r\n recommendation: 'Rotate in Azure AD > App Registrations > Certificates & Secrets.',\r\n },\r\n {\r\n id: 'SP-042',\r\n name: 'Cloudflare API Token',\r\n provider: 'cloudflare',\r\n severity: 'critical',\r\n pattern: /(?:CLOUDFLARE_API_TOKEN|CF_API_TOKEN)['\":\\s=]+([A-Za-z0-9_-]{40})/i,\r\n description: 'Cloudflare API token. Grants access to DNS, Workers, Pages, and account settings.',\r\n recommendation: 'Rotate at dash.cloudflare.com/profile/api-tokens. Use scoped tokens with minimum permissions.',\r\n },\r\n {\r\n id: 'SP-043',\r\n name: 'Cloudflare API Key (Global)',\r\n provider: 'cloudflare',\r\n severity: 'critical',\r\n pattern: /(?:CLOUDFLARE_API_KEY|CF_API_KEY)['\":\\s=]+([a-f0-9]{37})/i,\r\n description: 'Cloudflare Global API Key. Grants full account access — more dangerous than API tokens.',\r\n recommendation: 'Rotate at dash.cloudflare.com/profile/api-tokens. Prefer scoped API tokens over global keys.',\r\n },\r\n {\r\n id: 'SP-044',\r\n name: 'Cloudflare Workers KV Namespace',\r\n provider: 'cloudflare',\r\n severity: 'low',\r\n pattern: /(?:KV_NAMESPACE_ID|CLOUDFLARE_KV_NAMESPACE)['\":\\s=]+([a-f0-9]{32})/i,\r\n description: 'Cloudflare Workers KV namespace identifier.',\r\n recommendation: 'Low risk alone, but combined with API credentials allows data access.',\r\n },\r\n {\r\n id: 'SP-045',\r\n name: 'Vercel API Token',\r\n provider: 'vercel',\r\n severity: 'critical',\r\n pattern: /(?:VERCEL_TOKEN|vercel[_-]token)['\":\\s=]+([A-Za-z0-9]{24})/i,\r\n description: 'Vercel deployment API token. Grants ability to deploy, manage domains, and access secrets.',\r\n recommendation: 'Rotate at vercel.com/account/tokens.',\r\n },\r\n {\r\n id: 'SP-046',\r\n name: 'Vercel Access Token',\r\n provider: 'vercel',\r\n severity: 'critical',\r\n pattern: /vercel_[A-Za-z0-9]{40,60}/i,\r\n description: 'Vercel access token for CLI or API authentication.',\r\n recommendation: 'Rotate at vercel.com/account/tokens.',\r\n },\r\n {\r\n id: 'SP-047',\r\n name: 'DigitalOcean Personal Access Token',\r\n provider: 'digitalocean',\r\n severity: 'critical',\r\n pattern: /dop_v1_[a-f0-9]{64}/,\r\n description: 'DigitalOcean personal access token. Grants full account access to droplets, databases, and storage.',\r\n recommendation: 'Rotate at cloud.digitalocean.com/account/api/tokens.',\r\n },\r\n {\r\n id: 'SP-048',\r\n name: 'Railway Token',\r\n provider: 'railway',\r\n severity: 'high',\r\n pattern: /(?:RAILWAY_TOKEN|railway[_-]token)['\":\\s=]+([A-Za-z0-9_-]{30,50})/i,\r\n description: 'Railway deployment platform API token.',\r\n recommendation: 'Rotate at railway.app/account/tokens.',\r\n },\r\n {\r\n id: 'SP-049',\r\n name: 'Fly.io API Token',\r\n provider: 'flyio',\r\n severity: 'high',\r\n pattern: /fo1_[A-Za-z0-9_-]{40,80}/,\r\n description: 'Fly.io API token for machine deployment and management.',\r\n recommendation: 'Rotate with: flyctl tokens revoke <token>',\r\n },\r\n\r\n // ── Payment Providers ──\r\n\r\n {\r\n id: 'SP-050',\r\n name: 'Stripe Secret Key (Live)',\r\n provider: 'stripe',\r\n severity: 'critical',\r\n pattern: /sk_live_[A-Za-z0-9]{24,100}/,\r\n description: 'Stripe live-mode secret key. Grants full access to create charges, issue refunds, and access customer data.',\r\n recommendation: 'Rotate immediately at dashboard.stripe.com/apikeys. Never expose in client code.',\r\n },\r\n {\r\n id: 'SP-051',\r\n name: 'Stripe Publishable Key (Live)',\r\n provider: 'stripe',\r\n severity: 'low',\r\n pattern: /pk_live_[A-Za-z0-9]{24,100}/,\r\n description: 'Stripe live publishable key. This is meant to be public for frontend use.',\r\n recommendation: 'This key is designed to be public. Ensure it is not confused with the secret key.',\r\n },\r\n {\r\n id: 'SP-052',\r\n name: 'Stripe Restricted Key (Live)',\r\n provider: 'stripe',\r\n severity: 'high',\r\n pattern: /rk_live_[A-Za-z0-9]{24,100}/,\r\n description: 'Stripe live restricted key with limited permissions. Scope depends on configuration.',\r\n recommendation: 'Rotate at dashboard.stripe.com/apikeys. Audit which permissions are granted.',\r\n },\r\n {\r\n id: 'SP-053',\r\n name: 'Stripe Webhook Secret',\r\n provider: 'stripe',\r\n severity: 'high',\r\n pattern: /whsec_[A-Za-z0-9]{32,60}/,\r\n description: 'Stripe webhook endpoint signing secret. Allows forging webhook events.',\r\n recommendation: 'Rotate webhook secret at dashboard.stripe.com/webhooks. Keep server-side only.',\r\n },\r\n {\r\n id: 'SP-054',\r\n name: 'PayPal Client Secret',\r\n provider: 'paypal',\r\n severity: 'critical',\r\n pattern: /(?:PAYPAL_CLIENT_SECRET|paypal[_-]secret)['\":\\s=]+([A-Za-z0-9_-]{60,80})/i,\r\n description: 'PayPal REST API client secret. Grants ability to process payments and access merchant data.',\r\n recommendation: 'Rotate at developer.paypal.com/developer/applications.',\r\n },\r\n {\r\n id: 'SP-055',\r\n name: 'PayPal Access Token',\r\n provider: 'paypal',\r\n severity: 'high',\r\n pattern: /(?:A21AA|Bearer\\s+)[A-Za-z0-9_-]{80,120}/,\r\n description: 'PayPal OAuth access token. Short-lived but grants API access to payment operations.',\r\n recommendation: 'Access tokens expire but should not be cached in client code.',\r\n },\r\n {\r\n id: 'SP-056',\r\n name: 'Plaid Secret Key',\r\n provider: 'plaid',\r\n severity: 'critical',\r\n pattern: /(?:PLAID_SECRET|plaid[_-]secret)['\":\\s=]+([a-f0-9]{30,40})/i,\r\n description: 'Plaid financial data API secret key. Grants access to bank account and transaction data.',\r\n recommendation: 'Rotate at dashboard.plaid.com/team/keys.',\r\n },\r\n {\r\n id: 'SP-057',\r\n name: 'Plaid Client ID',\r\n provider: 'plaid',\r\n severity: 'medium',\r\n pattern: /(?:PLAID_CLIENT_ID|plaid[_-]client[_-]id)['\":\\s=]+([a-f0-9]{24})/i,\r\n description: 'Plaid application client ID.',\r\n recommendation: 'Low risk alone, but combined with secret key grants bank data access.',\r\n },\r\n {\r\n id: 'SP-058',\r\n name: 'Square Access Token',\r\n provider: 'square',\r\n severity: 'critical',\r\n pattern: /(?:EAA|sq0atp-)[A-Za-z0-9_-]{22,44}/,\r\n description: 'Square payment processing access token.',\r\n recommendation: 'Rotate at developer.squareup.com/apps.',\r\n },\r\n {\r\n id: 'SP-059',\r\n name: 'Shopify Admin API Key',\r\n provider: 'shopify',\r\n severity: 'critical',\r\n pattern: /shpat_[a-fA-F0-9]{32}/,\r\n description: 'Shopify Admin API access token. Grants full store management capabilities.',\r\n recommendation: 'Revoke at Shopify Admin > Settings > Apps and Sales Channels > API.',\r\n },\r\n {\r\n id: 'SP-060',\r\n name: 'Shopify Storefront Token',\r\n provider: 'shopify',\r\n severity: 'medium',\r\n pattern: /shpss_[a-fA-F0-9]{32}/,\r\n description: 'Shopify Storefront API access token. More limited than admin token.',\r\n recommendation: 'Designed for client use but ensure scopes are minimum necessary.',\r\n },\r\n\r\n // ── Communication / Messaging ──\r\n\r\n {\r\n id: 'SP-061',\r\n name: 'Slack Bot Token',\r\n provider: 'slack',\r\n severity: 'high',\r\n pattern: /xoxb-[0-9]+-[0-9]+-[A-Za-z0-9]+/,\r\n description: 'Slack bot OAuth token. Grants access to post messages, read channels, and perform bot actions.',\r\n recommendation: 'Rotate at api.slack.com/apps. Scope tokens to minimum required permissions.',\r\n },\r\n {\r\n id: 'SP-062',\r\n name: 'Slack User Token',\r\n provider: 'slack',\r\n severity: 'critical',\r\n pattern: /xoxp-[0-9]+-[0-9]+-[0-9]+-[A-Za-z0-9]+/,\r\n description: 'Slack user OAuth token. Acts as the user — can read all accessible messages and DMs.',\r\n recommendation: 'Rotate at api.slack.com/apps. User tokens should never be in application code.',\r\n },\r\n {\r\n id: 'SP-063',\r\n name: 'Slack App-Level Token',\r\n provider: 'slack',\r\n severity: 'high',\r\n pattern: /xapp-[0-9]+-[A-Za-z0-9]+-[A-Za-z0-9]+/,\r\n description: 'Slack app-level token for socket mode and org-wide operations.',\r\n recommendation: 'Rotate at api.slack.com/apps.',\r\n },\r\n {\r\n id: 'SP-064',\r\n name: 'Slack Webhook URL',\r\n provider: 'slack',\r\n severity: 'medium',\r\n pattern: /https:\\/\\/hooks\\.slack\\.com\\/services\\/T[A-Z0-9]+\\/B[A-Z0-9]+\\/[A-Za-z0-9]+/,\r\n description: 'Slack incoming webhook URL. Allows posting messages to a specific channel.',\r\n recommendation: 'Rotate at api.slack.com/apps > Incoming Webhooks. Treat as sensitive.',\r\n },\r\n {\r\n id: 'SP-065',\r\n name: 'Discord Bot Token',\r\n provider: 'discord',\r\n severity: 'critical',\r\n pattern: /[MN][A-Za-z0-9_-]{23}\\.[\\w-]{6}\\.[\\w-]{27,38}/,\r\n description: 'Discord bot token. Grants full bot account access — can read all visible messages and take actions.',\r\n recommendation: 'Regenerate at discord.com/developers/applications. Immediately revoke if exposed.',\r\n },\r\n {\r\n id: 'SP-066',\r\n name: 'Discord Webhook URL',\r\n provider: 'discord',\r\n severity: 'medium',\r\n pattern: /https:\\/\\/discord(?:app)?\\.com\\/api\\/webhooks\\/[0-9]+\\/[A-Za-z0-9_-]+/,\r\n description: 'Discord webhook URL. Allows posting to a channel without a bot account.',\r\n recommendation: 'Delete and recreate the webhook at the Discord channel settings.',\r\n },\r\n {\r\n id: 'SP-067',\r\n name: 'Discord Client Secret',\r\n provider: 'discord',\r\n severity: 'high',\r\n pattern: /(?:DISCORD_CLIENT_SECRET|discord[_-]secret)['\":\\s=]+([A-Za-z0-9_-]{32})/i,\r\n description: 'Discord OAuth application client secret.',\r\n recommendation: 'Rotate at discord.com/developers/applications.',\r\n },\r\n {\r\n id: 'SP-068',\r\n name: 'Twilio Account SID',\r\n provider: 'twilio',\r\n severity: 'medium',\r\n pattern: /AC[a-fA-F0-9]{32}/,\r\n description: 'Twilio Account SID. Identifier needed with auth token to access SMS, voice, and messaging APIs.',\r\n recommendation: 'The SID alone is low risk — it requires the auth token to authenticate.',\r\n },\r\n {\r\n id: 'SP-069',\r\n name: 'Twilio Auth Token',\r\n provider: 'twilio',\r\n severity: 'critical',\r\n pattern: /(?:TWILIO_AUTH_TOKEN|twilio[_-]auth[_-]token)['\":\\s=]+([a-fA-F0-9]{32})/i,\r\n description: 'Twilio auth token. Combined with Account SID grants full access to messaging, phone numbers, and billing.',\r\n recommendation: 'Rotate at console.twilio.com. Enable API key authentication instead of master auth token.',\r\n },\r\n {\r\n id: 'SP-070',\r\n name: 'Twilio API Key Secret',\r\n provider: 'twilio',\r\n severity: 'high',\r\n pattern: /(?:SK[a-fA-F0-9]{32})/,\r\n description: 'Twilio API Key SID (more restricted than auth token, still sensitive).',\r\n recommendation: 'Rotate at console.twilio.com/user/api-keys.',\r\n },\r\n {\r\n id: 'SP-071',\r\n name: 'SendGrid API Key',\r\n provider: 'sendgrid',\r\n severity: 'high',\r\n pattern: /SG\\.[A-Za-z0-9_-]{22,30}\\.[A-Za-z0-9_-]{43,50}/,\r\n description: 'SendGrid email API key. Grants ability to send transactional email at your expense.',\r\n recommendation: 'Rotate at app.sendgrid.com/settings/api_keys.',\r\n },\r\n {\r\n id: 'SP-072',\r\n name: 'Resend API Key',\r\n provider: 'resend',\r\n severity: 'high',\r\n pattern: /re_[A-Za-z0-9_]{36,40}/,\r\n description: 'Resend transactional email API key.',\r\n recommendation: 'Rotate at resend.com/api-keys.',\r\n },\r\n {\r\n id: 'SP-073',\r\n name: 'Mailgun API Key',\r\n provider: 'mailgun',\r\n severity: 'high',\r\n pattern: /key-[a-fA-F0-9]{32}/,\r\n description: 'Mailgun private API key for sending and receiving email.',\r\n recommendation: 'Rotate at app.mailgun.com/app/account/security/api_keys.',\r\n },\r\n {\r\n id: 'SP-074',\r\n name: 'Mailchimp API Key',\r\n provider: 'mailchimp',\r\n severity: 'high',\r\n pattern: /[0-9a-f]{32}-us\\d{1,2}/,\r\n description: 'Mailchimp API key. Grants access to mailing lists, campaigns, and subscriber data.',\r\n recommendation: 'Rotate at account.mailchimp.com/settings/api-keys.',\r\n },\r\n\r\n // ── Developer Tools ──\r\n\r\n {\r\n id: 'SP-075',\r\n name: 'GitHub Personal Access Token (Classic)',\r\n provider: 'github',\r\n severity: 'critical',\r\n pattern: /ghp_[A-Za-z0-9]{36}/,\r\n description: 'GitHub classic personal access token. Scope depends on permissions granted when created.',\r\n recommendation: 'Revoke at github.com/settings/tokens. GitHub automatically detects and alerts on these.',\r\n },\r\n {\r\n id: 'SP-076',\r\n name: 'GitHub Fine-Grained Token',\r\n provider: 'github',\r\n severity: 'critical',\r\n pattern: /github_pat_[A-Za-z0-9_]{82}/,\r\n description: 'GitHub fine-grained personal access token with repository-level permissions.',\r\n recommendation: 'Revoke at github.com/settings/tokens.',\r\n },\r\n {\r\n id: 'SP-077',\r\n name: 'GitHub OAuth App Token',\r\n provider: 'github',\r\n severity: 'high',\r\n pattern: /gho_[A-Za-z0-9]{36}/,\r\n description: 'GitHub OAuth app token. Grants access as the user who authorized the app.',\r\n recommendation: 'Revoke at github.com/settings/applications.',\r\n },\r\n {\r\n id: 'SP-078',\r\n name: 'GitHub Actions Token',\r\n provider: 'github',\r\n severity: 'high',\r\n pattern: /ghs_[A-Za-z0-9]{36}/,\r\n description: 'GitHub Actions installation token. Short-lived but should not appear in artifacts.',\r\n recommendation: 'These expire within an hour but should not be logged or exposed in build artifacts.',\r\n },\r\n {\r\n id: 'SP-079',\r\n name: 'GitHub App Private Key',\r\n provider: 'github',\r\n severity: 'critical',\r\n pattern: /ghv_[A-Za-z0-9]{36}/,\r\n description: 'GitHub repository-scoped token from a GitHub App installation.',\r\n recommendation: 'Revoke via GitHub App settings.',\r\n },\r\n {\r\n id: 'SP-080',\r\n name: 'npm Publish Token',\r\n provider: 'npm',\r\n severity: 'critical',\r\n pattern: /npm_[A-Za-z0-9]{36}/,\r\n description: 'npm publish access token. Grants ability to publish packages under your account — supply chain attack vector.',\r\n recommendation: 'Revoke at npmjs.com/settings/tokens. This is the highest risk secret for package maintainers.',\r\n },\r\n {\r\n id: 'SP-081',\r\n name: 'PyPI API Token',\r\n provider: 'pypi',\r\n severity: 'critical',\r\n pattern: /pypi-[A-Za-z0-9_-]{32,80}/,\r\n description: 'PyPI package publish token. Grants ability to publish Python packages — supply chain attack vector.',\r\n recommendation: 'Revoke at pypi.org/manage/account/token. Use scoped tokens per-project.',\r\n },\r\n\r\n // ── Databases ──\r\n\r\n {\r\n id: 'SP-082',\r\n name: 'PostgreSQL Connection String',\r\n provider: 'database',\r\n severity: 'critical',\r\n pattern: /postgres(?:ql)?:\\/\\/[^:@\\s]+:[^@\\s]+@[^/\\s]+\\/[^\\s\"']+/i,\r\n description: 'PostgreSQL connection string with embedded credentials.',\r\n recommendation: 'Rotate database credentials. Use connection poolers (PgBouncer, Supabase Pooler) and secrets managers.',\r\n },\r\n {\r\n id: 'SP-083',\r\n name: 'MySQL Connection String',\r\n provider: 'database',\r\n severity: 'critical',\r\n pattern: /mysql:\\/\\/[^:@\\s]+:[^@\\s]+@[^/\\s]+\\/[^\\s\"']+/i,\r\n description: 'MySQL connection string with embedded credentials.',\r\n recommendation: 'Rotate credentials. Never include database URLs in client code.',\r\n },\r\n {\r\n id: 'SP-084',\r\n name: 'MongoDB Connection String',\r\n provider: 'database',\r\n severity: 'critical',\r\n pattern: /mongodb(?:\\+srv)?:\\/\\/[^:@\\s]+:[^@\\s]+@[^\\s\"']+/i,\r\n description: 'MongoDB connection string with embedded credentials.',\r\n recommendation: 'Rotate credentials. Enable IP allowlisting. Use MongoDB Atlas connection string with IAM auth.',\r\n },\r\n {\r\n id: 'SP-085',\r\n name: 'Redis Connection String',\r\n provider: 'database',\r\n severity: 'high',\r\n pattern: /redis(?:s)?:\\/\\/(?:[^:@\\s]+:[^@\\s]+@)?[^/\\s]+(?::\\d+)?/i,\r\n description: 'Redis connection string, potentially with authentication credentials.',\r\n recommendation: 'Enable Redis AUTH. Use TLS (rediss://). Do not expose Redis to the public internet.',\r\n },\r\n {\r\n id: 'SP-086',\r\n name: 'PlanetScale Service Token',\r\n provider: 'planetscale',\r\n severity: 'critical',\r\n pattern: /pscale_tkn_[A-Za-z0-9_]{32,50}/,\r\n description: 'PlanetScale database service token.',\r\n recommendation: 'Rotate at app.planetscale.com/settings/service-tokens.',\r\n },\r\n {\r\n id: 'SP-087',\r\n name: 'Neon Database URL',\r\n provider: 'neon',\r\n severity: 'critical',\r\n pattern: /postgresql:\\/\\/[^:]+:[^@]+@[a-z0-9-]+\\.(?:us-east-\\d|eu-central-\\d)\\.aws\\.neon\\.tech\\/[^\\s\"']+/,\r\n description: 'Neon serverless PostgreSQL connection URL with credentials.',\r\n recommendation: 'Rotate credentials at console.neon.tech. Use connection pooling endpoint.',\r\n },\r\n {\r\n id: 'SP-088',\r\n name: 'Turso Database URL',\r\n provider: 'turso',\r\n severity: 'high',\r\n pattern: /libsql:\\/\\/[a-z0-9-]+-[a-z0-9]+\\.turso\\.io/,\r\n description: 'Turso edge SQLite database connection URL.',\r\n recommendation: 'Combine with auth token check. Rotate at app.turso.tech.',\r\n },\r\n {\r\n id: 'SP-089',\r\n name: 'Turso Auth Token',\r\n provider: 'turso',\r\n severity: 'high',\r\n pattern: /(?:TURSO_AUTH_TOKEN|turso[_-]token)['\":\\s=]+([A-Za-z0-9_-]{100,200})/i,\r\n description: 'Turso database auth token.',\r\n recommendation: 'Rotate at app.turso.tech/databases.',\r\n },\r\n\r\n // ── Analytics / Monitoring ──\r\n\r\n {\r\n id: 'SP-090',\r\n name: 'Sentry DSN',\r\n provider: 'sentry',\r\n severity: 'low',\r\n pattern: /https:\\/\\/[a-fA-F0-9]{32}@[a-z0-9]+\\.ingest\\.sentry\\.io\\/[0-9]+/,\r\n description: 'Sentry Data Source Name. Can receive arbitrary error events and expose error data to unauthorized parties.',\r\n recommendation: 'Rate-limit the DSN. Consider using Sentry Security Headers to restrict event submission.',\r\n },\r\n {\r\n id: 'SP-091',\r\n name: 'Datadog API Key',\r\n provider: 'datadog',\r\n severity: 'high',\r\n pattern: /(?:DD_API_KEY|DATADOG_API_KEY)['\":\\s=]+([a-fA-F0-9]{32})/i,\r\n description: 'Datadog API key for submitting metrics, logs, and events.',\r\n recommendation: 'Rotate at app.datadoghq.com/organization-settings/api-keys.',\r\n },\r\n {\r\n id: 'SP-092',\r\n name: 'Datadog App Key',\r\n provider: 'datadog',\r\n severity: 'high',\r\n pattern: /(?:DD_APP_KEY|DATADOG_APP_KEY)['\":\\s=]+([a-fA-F0-9]{40})/i,\r\n description: 'Datadog application key for querying and managing Datadog resources.',\r\n recommendation: 'Rotate at app.datadoghq.com/organization-settings/application-keys.',\r\n },\r\n {\r\n id: 'SP-093',\r\n name: 'New Relic License Key',\r\n provider: 'newrelic',\r\n severity: 'high',\r\n pattern: /(?:NEW_RELIC_LICENSE_KEY|NRLIC)['\":\\s=]+([A-Za-z0-9]{40})/i,\r\n description: 'New Relic ingest license key for APM, logs, and infrastructure.',\r\n recommendation: 'Rotate at one.newrelic.com/api-keys.',\r\n },\r\n {\r\n id: 'SP-094',\r\n name: 'PostHog API Key',\r\n provider: 'posthog',\r\n severity: 'medium',\r\n pattern: /phc_[A-Za-z0-9]{43}/,\r\n description: 'PostHog project API key. Client-side analytics key — can be semi-public but controls data ingest.',\r\n recommendation: 'This key is typically safe on the client, but ensure project-level access controls are configured.',\r\n },\r\n {\r\n id: 'SP-095',\r\n name: 'Amplitude API Key',\r\n provider: 'amplitude',\r\n severity: 'medium',\r\n pattern: /(?:AMPLITUDE_API_KEY|amplitude[_-]api[_-]key)['\":\\s=]+([a-fA-F0-9]{32})/i,\r\n description: 'Amplitude analytics project API key.',\r\n recommendation: 'Low risk for the API key, but the secret key should be server-side only.',\r\n },\r\n {\r\n id: 'SP-096',\r\n name: 'Mixpanel Token',\r\n provider: 'mixpanel',\r\n severity: 'low',\r\n pattern: /(?:MIXPANEL_TOKEN|NEXT_PUBLIC_MIXPANEL)['\":\\s=]+([a-fA-F0-9]{32})/i,\r\n description: 'Mixpanel analytics project token. Designed to be public for event tracking.',\r\n recommendation: 'Project token is safe to be public. Keep the project secret server-side.',\r\n },\r\n {\r\n id: 'SP-097',\r\n name: 'Segment Write Key',\r\n provider: 'segment',\r\n severity: 'medium',\r\n pattern: /(?:SEGMENT_WRITE_KEY|analytics\\.load\\()['\":\\s(]+([A-Za-z0-9]{20,30})/i,\r\n description: 'Segment analytics write key. Allows sending arbitrary events to your workspace.',\r\n recommendation: 'Client-side write keys are semi-public, but server write keys should be kept private.',\r\n },\r\n\r\n // ── Search and Data ──\r\n\r\n {\r\n id: 'SP-098',\r\n name: 'Algolia App ID & API Key',\r\n provider: 'algolia',\r\n severity: 'medium',\r\n pattern: /(?:ALGOLIA_API_KEY|NEXT_PUBLIC_ALGOLIA_API_KEY)['\":\\s=]+([A-Za-z0-9]{32})/i,\r\n description: 'Algolia search API key. Admin key grants full index management; search-only key is safe to expose.',\r\n recommendation: 'Use search-only API keys on the client. Rotate admin keys at algolia.com/account/api-keys.',\r\n },\r\n {\r\n id: 'SP-099',\r\n name: 'Algolia Admin API Key',\r\n provider: 'algolia',\r\n severity: 'critical',\r\n pattern: /(?:ALGOLIA_ADMIN_API_KEY)['\":\\s=]+([A-Za-z0-9]{32})/i,\r\n description: 'Algolia admin API key. Grants full index management including delete operations.',\r\n recommendation: 'Never expose admin key on client. Rotate at algolia.com/account/api-keys.',\r\n },\r\n {\r\n id: 'SP-100',\r\n name: 'Mapbox Access Token',\r\n provider: 'mapbox',\r\n severity: 'medium',\r\n pattern: /pk\\.[a-zA-Z0-9.]+\\.[a-zA-Z0-9_-]+/,\r\n description: 'Mapbox public access token. Designed for client use but can be abused for quota theft.',\r\n recommendation: 'Restrict token by URL in Mapbox account settings.',\r\n },\r\n\r\n // ── Cryptographic / Generic ──\r\n\r\n {\r\n id: 'SP-101',\r\n name: 'RSA Private Key',\r\n provider: 'cryptographic',\r\n severity: 'critical',\r\n pattern: /-----BEGIN RSA PRIVATE KEY-----/,\r\n description: 'RSA private key embedded in source. Can be used for decryption or identity impersonation.',\r\n recommendation: 'Remove immediately. Use secrets managers (AWS Secrets Manager, Vault, Doppler).',\r\n },\r\n {\r\n id: 'SP-102',\r\n name: 'EC Private Key',\r\n provider: 'cryptographic',\r\n severity: 'critical',\r\n pattern: /-----BEGIN EC PRIVATE KEY-----/,\r\n description: 'Elliptic curve private key embedded in source.',\r\n recommendation: 'Remove immediately. Rotate the key pair and audit for any usage.',\r\n },\r\n {\r\n id: 'SP-103',\r\n name: 'PKCS8 Private Key',\r\n provider: 'cryptographic',\r\n severity: 'critical',\r\n pattern: /-----BEGIN PRIVATE KEY-----/,\r\n description: 'PKCS#8 private key in unencrypted PEM format.',\r\n recommendation: 'Remove immediately. Use environment variables with proper secret management.',\r\n },\r\n {\r\n id: 'SP-104',\r\n name: 'PGP Private Key',\r\n provider: 'cryptographic',\r\n severity: 'critical',\r\n pattern: /-----BEGIN PGP PRIVATE KEY BLOCK-----/,\r\n description: 'PGP/GPG private key embedded in source.',\r\n recommendation: 'Remove and revoke the key at your keyserver.',\r\n },\r\n {\r\n id: 'SP-105',\r\n name: 'Generic JWT Secret',\r\n provider: 'generic',\r\n severity: 'high',\r\n pattern: /(?:JWT_SECRET|jwt[_-]secret|JWT_SIGNING_KEY)['\":\\s=]+[\"']?([A-Za-z0-9_+/=-]{32,})/i,\r\n description: 'JWT signing secret. Anyone with this secret can forge valid authentication tokens for your application.',\r\n recommendation: 'Rotate the secret and invalidate all existing tokens. Use asymmetric keys (RS256/ES256) instead of symmetric.',\r\n },\r\n {\r\n id: 'SP-106',\r\n name: 'Generic API Secret',\r\n provider: 'generic',\r\n severity: 'high',\r\n pattern: /(?:API_SECRET|app[_-]secret|CLIENT_SECRET)['\":\\s=]+[\"']?([A-Za-z0-9_+/=.-]{32,})/i,\r\n description: 'Generic application secret or client secret.',\r\n recommendation: 'Audit the usage of this secret and rotate if exposed.',\r\n },\r\n {\r\n id: 'SP-107',\r\n name: 'Generic Webhook Secret',\r\n provider: 'generic',\r\n severity: 'high',\r\n pattern: /(?:WEBHOOK_SECRET|webhook[_-]signing[_-]key)['\":\\s=]+[\"']?([A-Za-z0-9_+/=-]{32,})/i,\r\n description: 'Webhook signing secret. Allows forging webhook payloads from third-party services.',\r\n recommendation: 'Rotate the webhook secret at the originating service.',\r\n },\r\n\r\n // ── Social Platforms ──\r\n\r\n {\r\n id: 'SP-108',\r\n name: 'Twitter/X Bearer Token',\r\n provider: 'twitter',\r\n severity: 'high',\r\n pattern: /AAAAAAAAAAAAAAAAAAAAAA[A-Za-z0-9%]+/,\r\n description: 'Twitter/X API v2 bearer token. Grants read access to public Twitter data.',\r\n recommendation: 'Rotate at developer.twitter.com/en/apps.',\r\n },\r\n {\r\n id: 'SP-109',\r\n name: 'Twitter API Key',\r\n provider: 'twitter',\r\n severity: 'high',\r\n pattern: /(?:TWITTER_API_KEY|TWITTER_CONSUMER_KEY)['\":\\s=]+([A-Za-z0-9]{25})/i,\r\n description: 'Twitter/X API consumer key.',\r\n recommendation: 'Rotate at developer.twitter.com/en/apps.',\r\n },\r\n {\r\n id: 'SP-110',\r\n name: 'Twitter API Secret',\r\n provider: 'twitter',\r\n severity: 'critical',\r\n pattern: /(?:TWITTER_API_SECRET|TWITTER_CONSUMER_SECRET)['\":\\s=]+([A-Za-z0-9]{50})/i,\r\n description: 'Twitter/X API consumer secret. Combined with key grants OAuth application access.',\r\n recommendation: 'Rotate at developer.twitter.com/en/apps. Required to regenerate if compromised.',\r\n },\r\n {\r\n id: 'SP-111',\r\n name: 'HubSpot Private App Token',\r\n provider: 'hubspot',\r\n severity: 'high',\r\n pattern: /pat-(?:na|eu)\\d-[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/,\r\n description: 'HubSpot private app access token. Grants CRM data access.',\r\n recommendation: 'Rotate at app.hubspot.com/private-apps.',\r\n },\r\n {\r\n id: 'SP-112',\r\n name: 'Notion Integration Token',\r\n provider: 'notion',\r\n severity: 'high',\r\n pattern: /secret_[A-Za-z0-9]{43}/,\r\n description: 'Notion internal integration token. Grants access to all pages shared with the integration.',\r\n recommendation: 'Rotate at notion.so/my-integrations.',\r\n },\r\n {\r\n id: 'SP-113',\r\n name: 'Airtable API Key',\r\n provider: 'airtable',\r\n severity: 'high',\r\n pattern: /(?:AIRTABLE_API_KEY)['\":\\s=]+([A-Za-z0-9]{17})/i,\r\n description: 'Airtable personal API key. Grants access to all bases the account can access.',\r\n recommendation: 'Rotate at airtable.com/account. Prefer scoped personal access tokens.',\r\n },\r\n {\r\n id: 'SP-114',\r\n name: 'Airtable Personal Access Token',\r\n provider: 'airtable',\r\n severity: 'high',\r\n pattern: /pat[A-Za-z0-9]{14}\\.[a-fA-F0-9]{64}/,\r\n description: 'Airtable scoped personal access token.',\r\n recommendation: 'Rotate at airtable.com/create/tokens.',\r\n },\r\n {\r\n id: 'SP-115',\r\n name: 'Linear API Key',\r\n provider: 'linear',\r\n severity: 'high',\r\n pattern: /lin_api_[A-Za-z0-9]{40}/,\r\n description: 'Linear project management API key. Grants access to issues, projects, and teams.',\r\n recommendation: 'Rotate at linear.app/settings/api.',\r\n },\r\n\r\n // ── Infrastructure / DevOps ──\r\n\r\n {\r\n id: 'SP-116',\r\n name: 'HashiCorp Vault Token',\r\n provider: 'hashicorp',\r\n severity: 'critical',\r\n pattern: /(?:VAULT_TOKEN|hvs\\.[A-Za-z0-9]+)/,\r\n description: 'HashiCorp Vault service token. Grants access to secrets stored in Vault.',\r\n recommendation: 'Revoke with vault token revoke. Enable token TTLs and audit logging.',\r\n },\r\n {\r\n id: 'SP-117',\r\n name: 'Kubernetes Service Account Token',\r\n provider: 'kubernetes',\r\n severity: 'critical',\r\n pattern: /eyJhbGciOiJSUzI1NiIsImtpZCI[A-Za-z0-9_-]+\\.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50/,\r\n description: 'Kubernetes service account JWT token. Grants API server access within the cluster.',\r\n recommendation: 'Rotate the service account token. Use short-lived projected tokens (TokenRequest API).',\r\n },\r\n {\r\n id: 'SP-118',\r\n name: 'Cloudinary URL',\r\n provider: 'cloudinary',\r\n severity: 'high',\r\n pattern: /cloudinary:\\/\\/[0-9]+:[A-Za-z0-9_-]+@[a-z0-9_]+/,\r\n description: 'Cloudinary media storage URL with API credentials.',\r\n recommendation: 'Rotate at cloudinary.com/console. This grants upload and transformation access.',\r\n },\r\n {\r\n id: 'SP-119',\r\n name: 'Upstash Redis Token',\r\n provider: 'upstash',\r\n severity: 'high',\r\n pattern: /(?:UPSTASH_REDIS_REST_TOKEN|upstash[_-]token)['\":\\s=]+([A-Za-z0-9_-]{50,100})/i,\r\n description: 'Upstash serverless Redis REST API token.',\r\n recommendation: 'Rotate at console.upstash.com.',\r\n },\r\n {\r\n id: 'SP-120',\r\n name: 'Upstash QStash Token',\r\n provider: 'upstash',\r\n severity: 'high',\r\n pattern: /(?:QSTASH_TOKEN)['\":\\s=]+([A-Za-z0-9_-]{100,200})/i,\r\n description: 'Upstash QStash serverless message queue token.',\r\n recommendation: 'Rotate at console.upstash.com/qstash.',\r\n },\r\n\r\n // ── Additional AI/ML Platforms ──\r\n\r\n {\r\n id: 'SP-121',\r\n name: 'Portkey API Key',\r\n provider: 'portkey',\r\n severity: 'medium',\r\n pattern: /(?:PORTKEY_API_KEY)['\":\\s=]+([A-Za-z0-9_-]{40,60})/i,\r\n description: 'Portkey AI gateway API key.',\r\n recommendation: 'Rotate at app.portkey.ai/settings.',\r\n },\r\n {\r\n id: 'SP-122',\r\n name: 'Langfuse Secret Key',\r\n provider: 'langfuse',\r\n severity: 'medium',\r\n pattern: /sk-lf-[A-Za-z0-9_-]{36,50}/,\r\n description: 'Langfuse LLM observability secret key.',\r\n recommendation: 'Rotate at cloud.langfuse.com/project/settings.',\r\n },\r\n {\r\n id: 'SP-123',\r\n name: 'Weaviate API Key',\r\n provider: 'weaviate',\r\n severity: 'high',\r\n pattern: /(?:WEAVIATE_API_KEY)['\":\\s=]+([A-Za-z0-9_-]{32,50})/i,\r\n description: 'Weaviate vector database API key.',\r\n recommendation: 'Rotate at the Weaviate Cloud Services console.',\r\n },\r\n {\r\n id: 'SP-124',\r\n name: 'Pinecone API Key',\r\n provider: 'pinecone',\r\n severity: 'high',\r\n pattern: /(?:PINECONE_API_KEY)['\":\\s=]+([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})/i,\r\n description: 'Pinecone vector database API key. Grants access to vector indices and stored embeddings.',\r\n recommendation: 'Rotate at app.pinecone.io/organization/api-keys.',\r\n },\r\n {\r\n id: 'SP-125',\r\n name: 'Browserbase API Key',\r\n provider: 'browserbase',\r\n severity: 'high',\r\n pattern: /(?:BROWSERBASE_API_KEY)['\":\\s=]+([A-Za-z0-9_-]{32,50})/i,\r\n description: 'Browserbase cloud browser API key for web automation.',\r\n recommendation: 'Rotate at browserbase.com/settings.',\r\n },\r\n\r\n // ── Payments (extended) ──\r\n\r\n {\r\n id: 'SP-126',\r\n name: 'Braintree API Key',\r\n provider: 'braintree',\r\n severity: 'critical',\r\n pattern: /(?:BRAINTREE_PRIVATE_KEY|braintree[_-]private[_-]key)['\":\\s=]+([A-Za-z0-9]{32})/i,\r\n description: 'Braintree payment processing private key.',\r\n recommendation: 'Rotate at sandbox.braintreegateway.com or braintreegateway.com.',\r\n },\r\n\r\n // ── Communication (extended) ──\r\n\r\n {\r\n id: 'SP-127',\r\n name: 'Pusher App Secret',\r\n provider: 'pusher',\r\n severity: 'high',\r\n pattern: /(?:PUSHER_APP_SECRET)['\":\\s=]+([a-fA-F0-9]{20})/i,\r\n description: 'Pusher Channels app secret for server-side event authentication.',\r\n recommendation: 'Rotate at dashboard.pusher.com.',\r\n },\r\n {\r\n id: 'SP-128',\r\n name: 'Ably API Key',\r\n provider: 'ably',\r\n severity: 'high',\r\n pattern: /[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+:[A-Za-z0-9_+/=]{32,50}/,\r\n description: 'Ably realtime messaging API key.',\r\n recommendation: 'Rotate at ably.com/accounts.',\r\n },\r\n {\r\n id: 'SP-129',\r\n name: 'Zoom JWT Token',\r\n provider: 'zoom',\r\n severity: 'high',\r\n pattern: /(?:ZOOM_JWT_TOKEN|zoom[_-]jwt)['\":\\s=]+([A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+)/i,\r\n description: 'Zoom JWT application token for Server-to-Server OAuth.',\r\n recommendation: 'Rotate at marketplace.zoom.us/develop/apps.',\r\n },\r\n\r\n // ── Data Platforms ──\r\n\r\n {\r\n id: 'SP-130',\r\n name: 'Elastic Search API Key',\r\n provider: 'elastic',\r\n severity: 'high',\r\n pattern: /(?:ELASTICSEARCH_API_KEY|elastic[_-]api[_-]key)['\":\\s=]+([A-Za-z0-9_=]+:{[^}]+})/i,\r\n description: 'Elasticsearch / Elastic Cloud API key.',\r\n recommendation: 'Rotate in Kibana > Stack Management > Security > API Keys.',\r\n },\r\n {\r\n id: 'SP-131',\r\n name: 'Kafka SASL Password',\r\n provider: 'kafka',\r\n severity: 'critical',\r\n pattern: /(?:KAFKA_SASL_PASSWORD|kafka[_-]password)['\":\\s=]+([^\\s\"']{8,})/i,\r\n description: 'Apache Kafka SASL authentication password.',\r\n recommendation: 'Rotate and enable SSL/TLS for Kafka connections.',\r\n },\r\n\r\n // ── Observability (extended) ──\r\n\r\n {\r\n id: 'SP-132',\r\n name: 'Axiom API Token',\r\n provider: 'axiom',\r\n severity: 'medium',\r\n pattern: /xaat-[A-Za-z0-9_-]{36,50}/,\r\n description: 'Axiom log analytics API token.',\r\n recommendation: 'Rotate at app.axiom.co/settings/api-tokens.',\r\n },\r\n {\r\n id: 'SP-133',\r\n name: 'Grafana API Key',\r\n provider: 'grafana',\r\n severity: 'high',\r\n pattern: /(?:GRAFANA_API_KEY|grafana[_-]token)['\":\\s=]+([A-Za-z0-9=]{40,100})/i,\r\n description: 'Grafana Cloud API key for metrics, logs, and traces.',\r\n recommendation: 'Rotate at grafana.com/profile/api-keys.',\r\n },\r\n\r\n // ── Environment Variable Leaks ──\r\n\r\n {\r\n id: 'SP-134',\r\n name: 'Exposed NEXT_PUBLIC Secret Variable',\r\n provider: 'nextjs',\r\n severity: 'high',\r\n pattern: /NEXT_PUBLIC_(?:SECRET|KEY|TOKEN|PASSWORD|PRIVATE)[A-Z_]*['\":\\s=]+[\"']?[A-Za-z0-9_+/=-]{20,}/i,\r\n description: 'Variable prefixed NEXT_PUBLIC_ (client-bundle exposure) with a name suggesting it is a secret. NEXT_PUBLIC_ variables are always bundled into the client.',\r\n recommendation: 'Remove NEXT_PUBLIC_ prefix. Move to server-side environment variables or use Next.js server actions.',\r\n },\r\n {\r\n id: 'SP-135',\r\n name: 'Exposed VITE Secret Variable',\r\n provider: 'vite',\r\n severity: 'high',\r\n pattern: /VITE_(?:SECRET|KEY|TOKEN|PASSWORD|PRIVATE)[A-Z_]*['\":\\s=]+[\"']?[A-Za-z0-9_+/=-]{20,}/i,\r\n description: 'Variable prefixed VITE_ (client-bundle exposure) with a name suggesting it is a secret. VITE_ variables are always bundled into the client.',\r\n recommendation: 'Remove VITE_ prefix and handle server-side. Use API routes for sensitive operations.',\r\n },\r\n {\r\n id: 'SP-136',\r\n name: 'Hardcoded Password Pattern',\r\n provider: 'generic',\r\n severity: 'high',\r\n pattern: /(?:password|passwd|db_pass|db_password)['\":\\s=]+[\"'](?!.*\\${)[A-Za-z0-9!@#$%^&*_+=]{8,}/i,\r\n description: 'Hardcoded password string that does not appear to be a template variable.',\r\n recommendation: 'Move credentials to environment variables and use a secrets manager.',\r\n },\r\n\r\n // ── AI Agent Ecosystem ──\r\n\r\n {\r\n id: 'SP-137',\r\n name: 'Smithery Registry Token',\r\n provider: 'smithery',\r\n severity: 'high',\r\n pattern: /(?:SMITHERY_API_KEY|smithery[_-]token)['\":\\s=]+([A-Za-z0-9_-]{30,60})/i,\r\n description: 'Smithery MCP registry API token. Grants publish access to MCP server packages.',\r\n recommendation: 'Rotate at smithery.ai/settings. Supply chain risk — protect publish access.',\r\n },\r\n {\r\n id: 'SP-138',\r\n name: 'Cursor API Key',\r\n provider: 'cursor',\r\n severity: 'medium',\r\n pattern: /(?:CURSOR_API_KEY)['\":\\s=]+([A-Za-z0-9_-]{30,60})/i,\r\n description: 'Cursor IDE API integration key.',\r\n recommendation: 'Rotate at cursor.com/settings.',\r\n },\r\n {\r\n id: 'SP-139',\r\n name: 'Vigile API Key',\r\n provider: 'vigile',\r\n severity: 'medium',\r\n pattern: /vgl_[A-Za-z0-9_-]{40,60}/,\r\n description: 'Vigile AI API key. Grants access to Vigile scan API and trust registry.',\r\n recommendation: 'Rotate at vigile.dev/account.',\r\n },\r\n {\r\n id: 'SP-140',\r\n name: 'OpenAI API Key (via env)',\r\n provider: 'openai',\r\n severity: 'critical',\r\n pattern: /(?:OPENAI_API_KEY)['\":\\s=]+[\"']?(sk-[A-Za-z0-9_-]{48,})/i,\r\n description: 'OpenAI API key referenced in an environment variable assignment.',\r\n recommendation: 'Rotate at platform.openai.com/api-keys. Move to server-side proxy.',\r\n },\r\n {\r\n id: 'SP-141',\r\n name: 'Anthropic API Key (via env)',\r\n provider: 'anthropic',\r\n severity: 'critical',\r\n pattern: /(?:ANTHROPIC_API_KEY)['\":\\s=]+[\"']?(sk-ant-api[A-Za-z0-9_-]{90,})/i,\r\n description: 'Anthropic API key in an environment variable assignment.',\r\n recommendation: 'Rotate at console.anthropic.com. Never include in client bundles.',\r\n },\r\n\r\n // ── Extended Coverage ──\r\n\r\n {\r\n id: 'SP-142',\r\n name: 'Stripe Secret Key (Test)',\r\n provider: 'stripe',\r\n severity: 'medium',\r\n pattern: /sk_test_[A-Za-z0-9]{24,100}/,\r\n description: 'Stripe test-mode secret key. Should not be in production bundles.',\r\n recommendation: 'Test keys should not appear in client code even in development builds.',\r\n },\r\n {\r\n id: 'SP-143',\r\n name: 'GitHub App Installation Token',\r\n provider: 'github',\r\n severity: 'high',\r\n pattern: /ghi_[A-Za-z0-9]{36}/,\r\n description: 'GitHub App installation token (internal use).',\r\n recommendation: 'These are short-lived but should not appear in artifacts.',\r\n },\r\n {\r\n id: 'SP-144',\r\n name: 'Supabase JWT Secret',\r\n provider: 'supabase',\r\n severity: 'critical',\r\n pattern: /(?:SUPABASE_JWT_SECRET|JWT_SECRET)['\":\\s=]+[\"']?[A-Za-z0-9_+/=-]{32,}/i,\r\n description: 'JWT signing secret for Supabase Auth. Anyone with this secret can forge valid session tokens.',\r\n recommendation: 'This secret is set at project creation and cannot be rotated without breaking all sessions.',\r\n },\r\n {\r\n id: 'SP-145',\r\n name: 'Firebase Admin Private Key',\r\n provider: 'firebase',\r\n severity: 'critical',\r\n pattern: /(?:FIREBASE_PRIVATE_KEY)['\":\\s=]+[\"']?-----BEGIN (RSA )?PRIVATE KEY/i,\r\n description: 'Firebase Admin SDK private key. Grants admin-level database and Auth access.',\r\n recommendation: 'Remove from client code immediately. Firebase Admin SDK must only run server-side.',\r\n },\r\n {\r\n id: 'SP-146',\r\n name: 'Google Analytics Measurement ID',\r\n provider: 'google',\r\n severity: 'low',\r\n pattern: /G-[A-Z0-9]{10}/,\r\n description: 'Google Analytics 4 Measurement ID. Designed to be public — included for inventory purposes.',\r\n recommendation: 'This is public by design but note it for compliance/privacy documentation.',\r\n },\r\n {\r\n id: 'SP-147',\r\n name: 'Imagekit Private Key',\r\n provider: 'imagekit',\r\n severity: 'high',\r\n pattern: /(?:IMAGEKIT_PRIVATE_KEY)['\":\\s=]+([A-Za-z0-9_-]{30,60})/i,\r\n description: 'Imagekit.io private API key for image upload and management.',\r\n recommendation: 'Rotate at imagekit.io/dashboard/developer/api-keys.',\r\n },\r\n {\r\n id: 'SP-148',\r\n name: 'ScraperAPI Key',\r\n provider: 'scraperapi',\r\n severity: 'medium',\r\n pattern: /(?:SCRAPERAPI_KEY|scraperapi[_-]key)['\":\\s=]+([a-fA-F0-9]{32})/i,\r\n description: 'ScraperAPI proxy key for web scraping requests.',\r\n recommendation: 'Rotate at scraperapi.com/dashboard.',\r\n },\r\n {\r\n id: 'SP-149',\r\n name: 'Salesforce Connected App Secret',\r\n provider: 'salesforce',\r\n severity: 'critical',\r\n pattern: /(?:SF_CLIENT_SECRET|SALESFORCE_SECRET)['\":\\s=]+([A-Za-z0-9_-]{64})/i,\r\n description: 'Salesforce Connected App client secret. Grants CRM access as the connected application.',\r\n recommendation: 'Rotate in Salesforce Setup > App Manager.',\r\n },\r\n {\r\n id: 'SP-150',\r\n name: 'Generic Bearer Token',\r\n provider: 'generic',\r\n severity: 'medium',\r\n pattern: /Authorization:\\s*Bearer\\s+([A-Za-z0-9_-]{40,200})/i,\r\n description: 'Bearer token hardcoded in Authorization header — likely a live credential.',\r\n recommendation: 'Remove hardcoded tokens. Generate tokens at runtime from securely stored credentials.',\r\n },\r\n];\r\n\r\n// ============================================================\r\n// Core Detection Function\r\n// ============================================================\r\n\r\n/**\r\n * Scan text content for exposed secrets.\r\n * Uses `String.prototype.matchAll` with global regex to find all occurrences.\r\n *\r\n * @param text - JavaScript bundle content or any text to scan\r\n * @returns Array of SecretMatch objects (empty if no secrets found)\r\n */\r\nexport function matchSecrets(text: string): SecretMatch[] {\r\n const results: SecretMatch[] = [];\r\n\r\n for (const sp of SECRET_PATTERNS) {\r\n // Build global version of the pattern to find all occurrences\r\n const flags = sp.pattern.flags.includes('i') ? 'gi' : 'g';\r\n const globalPattern = new RegExp(sp.pattern.source, flags);\r\n\r\n for (const m of text.matchAll(globalPattern)) {\r\n const matched = m[0];\r\n const idx = m.index ?? 0;\r\n\r\n // Extract surrounding context for the report\r\n const start = Math.max(0, idx - 25);\r\n const end = Math.min(text.length, idx + matched.length + 25);\r\n const context = text.slice(start, end).replace(/\\s+/g, ' ').trim();\r\n\r\n results.push({\r\n pattern: sp,\r\n match: mask(matched),\r\n context,\r\n });\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n","// ============================================================\r\n// Vigile CLI — BaaS Bundle Analyzer\r\n// Downloads JS bundles from a deployed web app and scans them\r\n// for exposed secrets using the secret-patterns library.\r\n// ============================================================\r\n//\r\n// Limits (chosen to mirror browser devtools behavior):\r\n// MAX_BUNDLE_SIZE 5 MB — realistic for any production bundle\r\n// MAX_BUNDLES 10 — covers most SPAs; prevents runaway\r\n// FETCH_TIMEOUT 15 s — generous enough for slow edge CDNs\r\n\r\nimport { matchSecrets } from './secret-patterns.js';\r\nimport type { Finding } from '../../types/index.js';\r\n\r\nconst MAX_BUNDLE_SIZE = 5 * 1024 * 1024; // 5 MB\r\nconst MAX_BUNDLES = 10;\r\nconst FETCH_TIMEOUT_MS = 15_000;\r\n\r\nexport interface BundleAnalysisResult {\r\n /** The app URL that was scanned */\r\n url: string;\r\n /** Number of JS bundles successfully analyzed */\r\n bundlesAnalyzed: number;\r\n /** Security findings from all bundles */\r\n findings: Finding[];\r\n /** Non-fatal errors (e.g. one bundle 404'd but others succeeded) */\r\n errors: string[];\r\n}\r\n\r\n// ── HTML script-source extraction ──────────────────────────\r\n\r\n/**\r\n * Parses raw HTML and returns all script src values that look\r\n * like JS bundle paths (relative or absolute).\r\n */\r\nfunction extractScriptSrcs(html: string, baseUrl: string): string[] {\r\n const srcs: string[] = [];\r\n // Match <script src=\"...\"> and <script src='...'>\r\n const scriptRe = /<script[^>]+src=[\"']([^\"']+)[\"'][^>]*>/gi;\r\n for (const m of html.matchAll(scriptRe)) {\r\n const src = m[1];\r\n if (!src) continue;\r\n // Skip obvious non-bundle srcs (inline polyfills, analytics tags)\r\n if (src.startsWith('data:')) continue;\r\n try {\r\n const resolved = new URL(src, baseUrl).toString();\r\n srcs.push(resolved);\r\n } catch {\r\n // Malformed src — skip\r\n }\r\n }\r\n return srcs;\r\n}\r\n\r\n// ── Fetch helpers ───────────────────────────────────────────\r\n\r\nasync function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), timeoutMs);\r\n try {\r\n const response = await fetch(url, { signal: controller.signal });\r\n return response;\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n}\r\n\r\nasync function fetchText(url: string): Promise<{ text: string; error?: string }> {\r\n try {\r\n const res = await fetchWithTimeout(url, FETCH_TIMEOUT_MS);\r\n if (!res.ok) {\r\n return { text: '', error: `HTTP ${res.status} for ${url}` };\r\n }\r\n const contentLength = parseInt(res.headers.get('content-length') ?? '0', 10);\r\n if (contentLength > MAX_BUNDLE_SIZE) {\r\n return { text: '', error: `Bundle too large (${contentLength} bytes): ${url}` };\r\n }\r\n const buffer = await res.arrayBuffer();\r\n if (buffer.byteLength > MAX_BUNDLE_SIZE) {\r\n return { text: '', error: `Bundle exceeds 5 MB limit: ${url}` };\r\n }\r\n return { text: new TextDecoder().decode(buffer) };\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n return { text: '', error: `Fetch failed for ${url}: ${msg}` };\r\n }\r\n}\r\n\r\n// ── Finding construction ────────────────────────────────────\r\n\r\nfunction makeSecretFinding(\r\n bundleUrl: string,\r\n matchedId: string,\r\n matchedName: string,\r\n severity: 'critical' | 'high' | 'medium' | 'low',\r\n maskedValue: string,\r\n context: string,\r\n index: number,\r\n): Finding {\r\n return {\r\n id: `BU-${String(index + 1).padStart(3, '0')}`,\r\n category: 'exposed-secret',\r\n severity,\r\n title: `Exposed ${matchedName} secret in JS bundle`,\r\n description:\r\n `A ${matchedName} credential was found in a compiled JavaScript bundle ` +\r\n `at ${bundleUrl}. Secrets baked into frontend bundles are readable by ` +\r\n `anyone who inspects your app's network traffic or source code.`,\r\n evidence: `Pattern: ${matchedId} | Masked value: ${maskedValue} | Context: ${context}`,\r\n recommendation:\r\n `Move this secret to a server-side environment variable. Never include ` +\r\n `API keys or tokens in frontend JavaScript. Use a backend proxy or BFF ` +\r\n `(Backend for Frontend) pattern to make authenticated API calls.`,\r\n };\r\n}\r\n\r\n// ── Main export ─────────────────────────────────────────────\r\n\r\n/**\r\n * Downloads JS bundles from a deployed web app URL, then scans\r\n * each bundle for exposed secrets using secret-patterns.ts.\r\n *\r\n * @param appUrl The root URL of the deployed app (e.g. https://myapp.com)\r\n */\r\nexport async function analyzeBundles(appUrl: string): Promise<BundleAnalysisResult> {\r\n const errors: string[] = [];\r\n const findings: Finding[] = [];\r\n let bundlesAnalyzed = 0;\r\n\r\n // Normalise URL\r\n const baseUrl = appUrl.endsWith('/') ? appUrl : `${appUrl}/`;\r\n\r\n // Step 1: Fetch the root HTML page\r\n const { text: html, error: htmlError } = await fetchText(baseUrl);\r\n if (htmlError || !html) {\r\n errors.push(htmlError ?? `Could not fetch root HTML for ${baseUrl}`);\r\n return { url: appUrl, bundlesAnalyzed: 0, findings, errors };\r\n }\r\n\r\n // Step 2: Extract script sources\r\n const scriptUrls = extractScriptSrcs(html, baseUrl).slice(0, MAX_BUNDLES);\r\n if (scriptUrls.length === 0) {\r\n errors.push(`No <script src> tags found in HTML at ${baseUrl}`);\r\n return { url: appUrl, bundlesAnalyzed: 0, findings, errors };\r\n }\r\n\r\n // Step 3: Fetch and scan each bundle\r\n let findingIndex = 0;\r\n for (const scriptUrl of scriptUrls) {\r\n const { text: bundleText, error: fetchError } = await fetchText(scriptUrl);\r\n if (fetchError || !bundleText) {\r\n errors.push(fetchError ?? `Empty bundle at ${scriptUrl}`);\r\n continue;\r\n }\r\n\r\n bundlesAnalyzed++;\r\n\r\n const matches = matchSecrets(bundleText);\r\n for (const secretMatch of matches) {\r\n findings.push(\r\n makeSecretFinding(\r\n scriptUrl,\r\n secretMatch.pattern.id,\r\n secretMatch.pattern.name,\r\n secretMatch.pattern.severity,\r\n secretMatch.match,\r\n secretMatch.context,\r\n findingIndex++,\r\n ),\r\n );\r\n }\r\n }\r\n\r\n return { url: appUrl, bundlesAnalyzed, findings, errors };\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Supabase Scanner\r\n// Actively probes a Supabase project for RLS misconfigurations,\r\n// exposed credentials, auth settings, and CORS policy issues.\r\n// ============================================================\r\n//\r\n// Finding IDs:\r\n// SB-001 RLS disabled (anon read allowed) — critical\r\n// SB-002 Anon key exposed in frontend bundle — high\r\n// SB-003 Email confirmation disabled — medium\r\n// SB-004 CORS wildcard policy — medium\r\n// SB-005 Service role key exposed in bundle — critical\r\n// SB-006 Anon write allowed (RLS disabled) — critical\r\n// SB-007 Open signup enabled — low\r\n// ============================================================\r\n\r\nimport type { Finding } from '../../types/index.js';\r\nimport { analyzeBundles } from './bundle-analyzer.js';\r\n\r\nexport interface SupabaseScanOptions {\r\n /** Supabase project URL (e.g. https://abcdefgh.supabase.co) */\r\n projectUrl: string;\r\n /** Anon/public key — usually safe to provide; used to test anon-level access */\r\n anonKey?: string;\r\n}\r\n\r\nexport interface SupabaseScanResult {\r\n projectUrl: string;\r\n findings: Finding[];\r\n /** Tables discovered via anon REST API */\r\n tablesFound: string[];\r\n /** Whether anon read was allowed on any table */\r\n anonReadExposed: boolean;\r\n /** Whether the project URL resolves (basic reachability) */\r\n reachable: boolean;\r\n errors: string[];\r\n}\r\n\r\nconst FETCH_TIMEOUT_MS = 10_000;\r\n\r\n// ── Fetch with timeout (same pattern as bundle-analyzer) ────\r\n\r\nasync function fetchWithTimeout(\r\n url: string,\r\n init?: RequestInit,\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\r\n try {\r\n return await fetch(url, { ...init, signal: controller.signal });\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n}\r\n\r\n// ── URL normalisation ───────────────────────────────────────\r\n\r\nfunction normaliseSupabaseUrl(url: string): string {\r\n let u = url.replace(/\\/+$/, '');\r\n if (!u.startsWith('http')) {\r\n u = `https://${u}`;\r\n }\r\n return u;\r\n}\r\n\r\n// ── Anon key extraction from bundle findings ────────────────\r\n\r\n/**\r\n * Look through bundle analysis findings for a Supabase anon key.\r\n * SP-022/SP-023 patterns match the JWT format Supabase uses for\r\n * anon and service_role keys (eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...).\r\n */\r\nfunction extractAnonKeyFromFindings(findings: Finding[]): string | null {\r\n for (const f of findings) {\r\n const ev = f.evidence ?? '';\r\n if (\r\n (ev.includes('supabase') || ev.includes('SP-022') || ev.includes('SP-023')) &&\r\n ev.includes('eyJ')\r\n ) {\r\n const jwtMatch = ev.match(/eyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+/);\r\n if (jwtMatch) return jwtMatch[0];\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n// ── Main export ─────────────────────────────────────────────\r\n\r\n/**\r\n * Scans a Supabase project for security misconfigurations.\r\n *\r\n * Scan flow:\r\n * 1. Reachability check — does the REST API respond?\r\n * 2. Bundle analysis — scan the hosting URL for exposed keys\r\n * 3. Anon key resolution — use provided key, or extract from bundle\r\n * 4. Table enumeration — GET /rest/v1/ with anon key\r\n * 5. RLS testing — probe each table for anon read/write\r\n * 6. Auth settings — GET /auth/v1/settings\r\n * 7. CORS check — inspect Access-Control-Allow-Origin header\r\n */\r\nexport async function scanSupabase(\r\n opts: SupabaseScanOptions,\r\n): Promise<SupabaseScanResult> {\r\n const findings: Finding[] = [];\r\n const errors: string[] = [];\r\n const tablesFound: string[] = [];\r\n let anonReadExposed = false;\r\n let reachable = false;\r\n\r\n const baseUrl = normaliseSupabaseUrl(opts.projectUrl);\r\n\r\n // ── Step 1: Reachability ────────────────────────────────\r\n try {\r\n const res = await fetchWithTimeout(`${baseUrl}/rest/v1/`, {\r\n method: 'HEAD',\r\n });\r\n // Supabase returns 401 without apikey — that's still reachable\r\n reachable = res.status === 401 || res.status === 200 || res.status === 403;\r\n } catch {\r\n errors.push(`Supabase project not reachable at ${baseUrl}`);\r\n return { projectUrl: baseUrl, findings, tablesFound, anonReadExposed, reachable, errors };\r\n }\r\n\r\n if (!reachable) {\r\n errors.push(`Supabase project returned unexpected status at ${baseUrl}/rest/v1/`);\r\n return { projectUrl: baseUrl, findings, tablesFound, anonReadExposed, reachable, errors };\r\n }\r\n\r\n // ── Step 2: Bundle analysis (for key exposure) ──────────\r\n const bundleResult = await analyzeBundles(baseUrl);\r\n findings.push(...bundleResult.findings);\r\n if (bundleResult.errors.length > 0) {\r\n errors.push(...bundleResult.errors.map((e) => `[bundle] ${e}`));\r\n }\r\n\r\n // Check for service_role key specifically\r\n const hasServiceRoleKey = bundleResult.findings.some(\r\n (f) =>\r\n f.evidence?.includes('service_role') ||\r\n f.evidence?.includes('SP-023'),\r\n );\r\n if (hasServiceRoleKey) {\r\n findings.push({\r\n id: 'SB-005',\r\n category: 'exposed-secret',\r\n severity: 'critical',\r\n title: 'Supabase service_role key exposed in frontend bundle',\r\n description:\r\n 'The Supabase service_role key was found in a JavaScript bundle. ' +\r\n 'This key bypasses all Row Level Security policies and grants full ' +\r\n 'read/write/delete access to every table and storage bucket. ' +\r\n 'This is equivalent to database superuser access.',\r\n evidence: 'Detected via bundle analysis — service_role JWT in compiled JS',\r\n recommendation:\r\n 'Rotate the service_role key immediately in Supabase Dashboard > ' +\r\n 'Settings > API. This key must NEVER appear in frontend code. ' +\r\n 'Use it only in server-side functions (Edge Functions, API routes).',\r\n });\r\n }\r\n\r\n // ── Step 3: Resolve anon key ────────────────────────────\r\n let anonKey = opts.anonKey ?? null;\r\n if (!anonKey) {\r\n anonKey = extractAnonKeyFromFindings(bundleResult.findings);\r\n }\r\n\r\n if (!anonKey) {\r\n errors.push(\r\n 'No anon key provided or detected in bundles — skipping RLS and table enumeration. ' +\r\n 'Pass --supabase-key or ensure the app URL serves JS bundles with the anon key.',\r\n );\r\n }\r\n\r\n // ── Step 4: Table enumeration ───────────────────────────\r\n if (anonKey) {\r\n try {\r\n const tablesRes = await fetchWithTimeout(`${baseUrl}/rest/v1/`, {\r\n headers: {\r\n apikey: anonKey,\r\n Authorization: `Bearer ${anonKey}`,\r\n },\r\n });\r\n\r\n if (tablesRes.ok) {\r\n try {\r\n const schema = (await tablesRes.json()) as Record<string, unknown>;\r\n // The /rest/v1/ endpoint returns an OpenAPI spec.\r\n // Table names appear as keys under \"paths\" (e.g. \"/users\").\r\n const paths = (schema.paths ?? {}) as Record<string, unknown>;\r\n for (const path of Object.keys(paths)) {\r\n const tableName = path.replace(/^\\//, '').split('?')[0];\r\n if (tableName && !tableName.includes('/')) {\r\n tablesFound.push(tableName);\r\n }\r\n }\r\n } catch {\r\n errors.push('Could not parse Supabase REST schema response');\r\n }\r\n }\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`Table enumeration failed: ${msg}`);\r\n }\r\n\r\n // ── Step 5: RLS testing per table ───────────────────────\r\n for (const table of tablesFound) {\r\n // Test anon READ\r\n try {\r\n const readRes = await fetchWithTimeout(\r\n `${baseUrl}/rest/v1/${table}?select=*&limit=1`,\r\n {\r\n headers: {\r\n apikey: anonKey,\r\n Authorization: `Bearer ${anonKey}`,\r\n },\r\n },\r\n );\r\n\r\n if (readRes.ok) {\r\n const body = await readRes.text();\r\n // Non-empty array means data was returned — RLS is off for reads\r\n if (body.startsWith('[') && body !== '[]') {\r\n anonReadExposed = true;\r\n findings.push({\r\n id: 'SB-001',\r\n category: 'rls-misconfiguration',\r\n severity: 'critical',\r\n title: `RLS disabled: anonymous read on \"${table}\"`,\r\n description:\r\n `Table \"${table}\" returned data via the anon key without any Row Level ` +\r\n `Security policies. Any user with the anon key (which is public) can ` +\r\n `read all rows in this table. This is the #1 Supabase security mistake.`,\r\n evidence: `GET /rest/v1/${table}?select=*&limit=1 returned 200 with data`,\r\n recommendation:\r\n `Enable RLS on table \"${table}\" in Supabase Dashboard > Database > ` +\r\n `Tables. Add a policy like: CREATE POLICY \"auth read\" ON \"${table}\" ` +\r\n `FOR SELECT USING (auth.uid() = user_id);`,\r\n });\r\n }\r\n }\r\n } catch {\r\n // Individual table probe failure — non-fatal\r\n }\r\n\r\n // Test anon WRITE\r\n // POST with empty object — 400 means RLS let it through to DB layer\r\n // (schema constraints caught it), 401/403 means RLS blocked it\r\n try {\r\n const writeRes = await fetchWithTimeout(\r\n `${baseUrl}/rest/v1/${table}`,\r\n {\r\n method: 'POST',\r\n headers: {\r\n apikey: anonKey,\r\n Authorization: `Bearer ${anonKey}`,\r\n 'Content-Type': 'application/json',\r\n Prefer: 'return=minimal',\r\n },\r\n body: JSON.stringify({}),\r\n },\r\n );\r\n\r\n // 400 = RLS didn't block, schema constraints did (RLS is OFF)\r\n // 201/200 = insert succeeded (RLS is OFF and no constraints)\r\n // 401/403 = RLS blocked it (safe)\r\n if (\r\n writeRes.status === 400 ||\r\n writeRes.status === 201 ||\r\n writeRes.status === 200\r\n ) {\r\n findings.push({\r\n id: 'SB-006',\r\n category: 'rls-misconfiguration',\r\n severity: 'critical',\r\n title: `RLS disabled: anonymous write on \"${table}\"`,\r\n description:\r\n `Table \"${table}\" allows INSERT operations via the anon key. The ` +\r\n `request reached the database layer (RLS did not block it). Even if ` +\r\n `it failed on a constraint, the lack of RLS means anyone can attempt ` +\r\n `to write data to this table.`,\r\n evidence: `POST /rest/v1/${table} returned ${writeRes.status} (not 401/403)`,\r\n recommendation:\r\n `Enable RLS on table \"${table}\" and add INSERT policies. For example: ` +\r\n `CREATE POLICY \"auth insert\" ON \"${table}\" FOR INSERT WITH CHECK ` +\r\n `(auth.uid() = user_id);`,\r\n });\r\n }\r\n } catch {\r\n // Individual write probe failure — non-fatal\r\n }\r\n }\r\n }\r\n\r\n // ── Step 6: Auth configuration ──────────────────────────\r\n if (anonKey) {\r\n try {\r\n const authRes = await fetchWithTimeout(`${baseUrl}/auth/v1/settings`, {\r\n headers: {\r\n apikey: anonKey,\r\n Authorization: `Bearer ${anonKey}`,\r\n },\r\n });\r\n\r\n if (authRes.ok) {\r\n try {\r\n const settings = (await authRes.json()) as Record<string, unknown>;\r\n\r\n const autoconfirm = settings.mailer_autoconfirm ?? settings.autoconfirm;\r\n if (autoconfirm === true) {\r\n findings.push({\r\n id: 'SB-003',\r\n category: 'auth-misconfiguration',\r\n severity: 'medium',\r\n title: 'Email confirmation disabled (autoconfirm enabled)',\r\n description:\r\n 'Supabase auth is configured to automatically confirm email addresses ' +\r\n 'without requiring the user to click a verification link. This allows ' +\r\n 'attackers to create accounts with any email address, including ' +\r\n 'impersonating legitimate users.',\r\n evidence: 'GET /auth/v1/settings returned mailer_autoconfirm: true',\r\n recommendation:\r\n 'Disable autoconfirm in Supabase Dashboard > Authentication > ' +\r\n 'Settings > Email Auth. Require email verification for all new signups.',\r\n });\r\n }\r\n\r\n const disableSignup = settings.disable_signup;\r\n if (disableSignup === false) {\r\n findings.push({\r\n id: 'SB-007',\r\n category: 'auth-misconfiguration',\r\n severity: 'low',\r\n title: 'Open signup enabled',\r\n description:\r\n 'Public signup is enabled on this Supabase project. If this is an ' +\r\n 'internal tool or admin panel, open signup allows anyone to create ' +\r\n 'an account.',\r\n evidence: 'GET /auth/v1/settings returned disable_signup: false',\r\n recommendation:\r\n 'If this project is not meant for public registration, disable ' +\r\n 'signup in Supabase Dashboard > Authentication > Settings.',\r\n });\r\n }\r\n } catch {\r\n errors.push('Could not parse auth settings response');\r\n }\r\n }\r\n } catch {\r\n errors.push('Auth settings endpoint not reachable');\r\n }\r\n }\r\n\r\n // ── Step 7: CORS policy check ───────────────────────────\r\n try {\r\n const corsRes = await fetchWithTimeout(`${baseUrl}/rest/v1/`, {\r\n method: 'OPTIONS',\r\n headers: {\r\n Origin: 'https://evil-attacker-site.com',\r\n 'Access-Control-Request-Method': 'GET',\r\n },\r\n });\r\n\r\n const allowOrigin = corsRes.headers.get('access-control-allow-origin');\r\n if (allowOrigin === '*' || allowOrigin === 'https://evil-attacker-site.com') {\r\n const isReflected = allowOrigin === 'https://evil-attacker-site.com';\r\n findings.push({\r\n id: 'SB-004',\r\n category: 'cors-misconfiguration',\r\n severity: 'medium',\r\n title: isReflected\r\n ? 'CORS reflects arbitrary origins on REST API'\r\n : 'CORS wildcard policy on REST API',\r\n description: isReflected\r\n ? 'The Supabase REST API reflects any Origin header back in ' +\r\n 'Access-Control-Allow-Origin, which is functionally equivalent ' +\r\n 'to a wildcard policy but harder to detect.'\r\n : 'The Supabase REST API returns Access-Control-Allow-Origin: * which ' +\r\n 'allows any website to make authenticated requests to your API. ' +\r\n 'Combined with an exposed anon key, this enables cross-origin ' +\r\n 'data access from malicious sites.',\r\n evidence: isReflected\r\n ? 'OPTIONS /rest/v1/ reflected origin: https://evil-attacker-site.com'\r\n : 'OPTIONS /rest/v1/ returned Access-Control-Allow-Origin: *',\r\n recommendation:\r\n 'Configure allowed origins in Supabase Dashboard > Settings > API. ' +\r\n 'Restrict to your app domain(s) only.',\r\n });\r\n }\r\n } catch {\r\n // CORS check is non-critical\r\n }\r\n\r\n return {\r\n projectUrl: baseUrl,\r\n findings,\r\n tablesFound,\r\n anonReadExposed,\r\n reachable,\r\n errors,\r\n };\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Firebase Scanner\r\n// Probes a Firebase project for Firestore/RTDB public access,\r\n// Storage bucket exposure, config leaks, and missing App Check.\r\n// ============================================================\r\n//\r\n// Finding IDs:\r\n// FB-001 Firestore public read/write — critical\r\n// FB-002 RTDB public read/write — critical\r\n// FB-003 Firebase config object in bundle — high\r\n// FB-004 Storage bucket publicly listable — high\r\n// FB-005 Missing security headers on hosting — medium\r\n// FB-006 RTDB .indexOn missing (data exposure) — medium\r\n// ============================================================\r\n\r\nimport type { Finding } from '../../types/index.js';\r\nimport { analyzeBundles } from './bundle-analyzer.js';\r\n\r\nexport interface FirebaseScanOptions {\r\n /** Firebase project URL (e.g. https://my-project.web.app or https://my-project.firebaseapp.com) */\r\n projectUrl: string;\r\n /** Firebase project ID — extracted from projectUrl if not provided */\r\n projectId?: string;\r\n}\r\n\r\nexport interface FirebaseScanResult {\r\n projectUrl: string;\r\n projectId: string | null;\r\n findings: Finding[];\r\n /** Whether Firestore rules allow unauthenticated read/write */\r\n firestorePublicAccess: boolean;\r\n /** Whether RTDB rules allow unauthenticated read/write */\r\n rtdbPublicAccess: boolean;\r\n /** Whether Firebase config object was found exposed in bundles */\r\n configExposed: boolean;\r\n reachable: boolean;\r\n errors: string[];\r\n}\r\n\r\nconst FETCH_TIMEOUT_MS = 10_000;\r\n\r\n// ── Fetch with timeout ──────────────────────────────────────\r\n\r\nasync function fetchWithTimeout(\r\n url: string,\r\n init?: RequestInit,\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\r\n try {\r\n return await fetch(url, { ...init, signal: controller.signal });\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n}\r\n\r\n// ── URL normalisation ───────────────────────────────────────\r\n\r\nfunction normaliseUrl(url: string): string {\r\n let u = url.replace(/\\/+$/, '');\r\n if (!u.startsWith('http')) {\r\n u = `https://${u}`;\r\n }\r\n return u;\r\n}\r\n\r\n// ── Project ID extraction ───────────────────────────────────\r\n\r\n/** Best-effort project ID extraction from Firebase hosting URLs. */\r\nfunction extractProjectId(url: string): string | null {\r\n try {\r\n const hostname = new URL(url).hostname;\r\n // my-project.web.app -> my-project\r\n // my-project.firebaseapp.com -> my-project\r\n const match = hostname.match(/^([^.]+)\\.(web\\.app|firebaseapp\\.com)$/);\r\n return match?.[1] ?? null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n// ── Firestore config extraction from bundle findings ────────\r\n\r\ninterface FirebaseConfigFromBundle {\r\n apiKey?: string;\r\n projectId?: string;\r\n storageBucket?: string;\r\n}\r\n\r\n/**\r\n * Attempt to extract Firebase config values from bundle analysis\r\n * findings. The SP-015/SP-016/SP-017 patterns match Firebase\r\n * apiKey, projectId, and storageBucket in compiled JS.\r\n */\r\nfunction extractConfigFromFindings(findings: Finding[]): FirebaseConfigFromBundle {\r\n const config: FirebaseConfigFromBundle = {};\r\n for (const f of findings) {\r\n const ev = f.evidence ?? '';\r\n // Firebase API key pattern (AIzaSy...)\r\n if (ev.includes('AIzaSy')) {\r\n const keyMatch = ev.match(/AIzaSy[A-Za-z0-9_-]{33}/);\r\n if (keyMatch) config.apiKey = keyMatch[0];\r\n }\r\n // Project ID from evidence\r\n if (ev.includes('projectId') || ev.includes('firebase')) {\r\n const projMatch = ev.match(/[\"']([a-z0-9-]+)\\.firebaseapp\\.com[\"']/);\r\n if (projMatch) config.projectId = projMatch[1];\r\n }\r\n // Storage bucket\r\n if (ev.includes('storageBucket') || ev.includes('.appspot.com')) {\r\n const bucketMatch = ev.match(/([a-z0-9-]+)\\.appspot\\.com/);\r\n if (bucketMatch) config.storageBucket = bucketMatch[0];\r\n }\r\n }\r\n return config;\r\n}\r\n\r\n// ── Main export ─────────────────────────────────────────────\r\n\r\n/**\r\n * Scans a Firebase-backed app for security misconfigurations.\r\n *\r\n * Scan flow:\r\n * 1. Extract project ID from URL\r\n * 2. Reachability check\r\n * 3. Bundle analysis — look for Firebase config exposure\r\n * 4. Firestore REST API — probe for unauthenticated read\r\n * 5. RTDB — probe /.json for unauthenticated read\r\n * 6. Storage — probe bucket for public file listing\r\n * 7. Hosting headers — check for security headers\r\n */\r\nexport async function scanFirebase(\r\n opts: FirebaseScanOptions,\r\n): Promise<FirebaseScanResult> {\r\n const findings: Finding[] = [];\r\n const errors: string[] = [];\r\n let firestorePublicAccess = false;\r\n let rtdbPublicAccess = false;\r\n let configExposed = false;\r\n let reachable = false;\r\n\r\n const baseUrl = normaliseUrl(opts.projectUrl);\r\n const projectId = opts.projectId ?? extractProjectId(baseUrl);\r\n\r\n // ── Step 1: Reachability ────────────────────────────────\r\n try {\r\n const res = await fetchWithTimeout(baseUrl, { method: 'HEAD' });\r\n reachable = res.status >= 200 && res.status < 500;\r\n } catch {\r\n errors.push(`Firebase project not reachable at ${baseUrl}`);\r\n return {\r\n projectUrl: baseUrl, projectId, findings,\r\n firestorePublicAccess, rtdbPublicAccess, configExposed,\r\n reachable, errors,\r\n };\r\n }\r\n\r\n if (!reachable) {\r\n errors.push(`Firebase project returned unexpected status at ${baseUrl}`);\r\n return {\r\n projectUrl: baseUrl, projectId, findings,\r\n firestorePublicAccess, rtdbPublicAccess, configExposed,\r\n reachable, errors,\r\n };\r\n }\r\n\r\n // ── Step 2: Bundle analysis ─────────────────────────────\r\n const bundleResult = await analyzeBundles(baseUrl);\r\n findings.push(...bundleResult.findings);\r\n if (bundleResult.errors.length > 0) {\r\n errors.push(...bundleResult.errors.map((e) => `[bundle] ${e}`));\r\n }\r\n\r\n // Check if Firebase config was exposed in bundles\r\n const bundleConfig = extractConfigFromFindings(bundleResult.findings);\r\n const detectedProjectId = projectId ?? bundleConfig.projectId ?? null;\r\n\r\n if (bundleConfig.apiKey) {\r\n configExposed = true;\r\n findings.push({\r\n id: 'FB-003',\r\n category: 'exposed-secret',\r\n severity: 'high',\r\n title: 'Firebase config object exposed in frontend bundle',\r\n description:\r\n 'The Firebase client configuration (apiKey, projectId, etc.) was found ' +\r\n 'in a JavaScript bundle. While Firebase API keys are designed to be ' +\r\n 'public for client-side use, exposure without App Check enforcement ' +\r\n 'allows abuse: automated account creation, Firestore/RTDB enumeration, ' +\r\n 'and quota exhaustion attacks.',\r\n evidence: `Firebase apiKey: ${bundleConfig.apiKey.slice(0, 8)}*** detected in bundle`,\r\n recommendation:\r\n 'Enable Firebase App Check in the Firebase Console to restrict API ' +\r\n 'access to your legitimate app. Configure reCAPTCHA Enterprise or ' +\r\n 'DeviceCheck attestation. Without App Check, anyone with the config ' +\r\n 'can call your Firebase APIs.',\r\n });\r\n }\r\n\r\n // ── Step 3: Firestore public access probe ───────────────\r\n if (detectedProjectId) {\r\n const firestoreUrl =\r\n `https://firestore.googleapis.com/v1/projects/${detectedProjectId}/databases/(default)/documents`;\r\n\r\n try {\r\n const fsRes = await fetchWithTimeout(firestoreUrl);\r\n\r\n if (fsRes.ok) {\r\n // 200 without auth = Firestore rules allow public read\r\n firestorePublicAccess = true;\r\n\r\n let docCount = 0;\r\n try {\r\n const body = (await fsRes.json()) as { documents?: unknown[] };\r\n docCount = body.documents?.length ?? 0;\r\n } catch {\r\n // JSON parse failure is non-fatal\r\n }\r\n\r\n findings.push({\r\n id: 'FB-001',\r\n category: 'firebase-rules-issue',\r\n severity: 'critical',\r\n title: 'Firestore allows unauthenticated read access',\r\n description:\r\n 'The Firestore REST API returned documents without any authentication ' +\r\n 'token. This means Firestore Security Rules are configured with ' +\r\n '`allow read: if true` or similar permissive rules at the database or ' +\r\n 'collection level. Any data in Firestore is publicly accessible.',\r\n evidence:\r\n `GET ${firestoreUrl} returned 200` +\r\n (docCount > 0 ? ` (${docCount} documents visible)` : ''),\r\n recommendation:\r\n 'Update Firestore Security Rules to require authentication: ' +\r\n '`allow read: if request.auth != null;` at minimum. Ideally, add ' +\r\n 'per-user rules: `allow read: if request.auth.uid == resource.data.userId;`',\r\n });\r\n }\r\n // 403/401 = rules are blocking unauthenticated access (good)\r\n // 404 = no default database or project not found\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`Firestore probe failed: ${msg}`);\r\n }\r\n\r\n // Also test write access\r\n try {\r\n const writeUrl =\r\n `https://firestore.googleapis.com/v1/projects/${detectedProjectId}/databases/(default)/documents/vigile_probe_test`;\r\n const writeRes = await fetchWithTimeout(writeUrl, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n fields: {\r\n _vigile_probe: { stringValue: 'security_test' },\r\n },\r\n }),\r\n });\r\n\r\n // 200 = write succeeded without auth (very bad)\r\n // 400 = request reached Firestore (rules didn't block, bad format)\r\n if (writeRes.status === 200) {\r\n firestorePublicAccess = true;\r\n findings.push({\r\n id: 'FB-001',\r\n category: 'firebase-rules-issue',\r\n severity: 'critical',\r\n title: 'Firestore allows unauthenticated WRITE access',\r\n description:\r\n 'A document was successfully written to Firestore without any ' +\r\n 'authentication. This means anyone on the internet can create, ' +\r\n 'modify, or delete data in your database.',\r\n evidence: `POST to Firestore documents endpoint returned 200`,\r\n recommendation:\r\n 'Immediately update Firestore Security Rules to block unauthenticated ' +\r\n 'writes: `allow write: if request.auth != null;` — and review all ' +\r\n 'existing data for tampering.',\r\n });\r\n }\r\n } catch {\r\n // Write probe failure is non-critical\r\n }\r\n } else {\r\n errors.push(\r\n 'Could not determine Firebase project ID — skipping Firestore/RTDB probes. ' +\r\n 'Provide --firebase-project-id or use a *.web.app / *.firebaseapp.com URL.',\r\n );\r\n }\r\n\r\n // ── Step 4: RTDB public access probe ────────────────────\r\n if (detectedProjectId) {\r\n // Firebase RTDB URLs follow: https://<project-id>-default-rtdb.firebaseio.com/\r\n // or https://<project-id>.firebaseio.com/ for older projects\r\n const rtdbUrls = [\r\n `https://${detectedProjectId}-default-rtdb.firebaseio.com/.json?shallow=true`,\r\n `https://${detectedProjectId}.firebaseio.com/.json?shallow=true`,\r\n ];\r\n\r\n for (const rtdbUrl of rtdbUrls) {\r\n try {\r\n const rtdbRes = await fetchWithTimeout(rtdbUrl);\r\n\r\n if (rtdbRes.ok) {\r\n rtdbPublicAccess = true;\r\n let keyCount = 0;\r\n try {\r\n const body = (await rtdbRes.json()) as Record<string, unknown> | null;\r\n if (body && typeof body === 'object') {\r\n keyCount = Object.keys(body).length;\r\n }\r\n } catch {\r\n // JSON parse failure is non-fatal\r\n }\r\n\r\n findings.push({\r\n id: 'FB-002',\r\n category: 'firebase-rules-issue',\r\n severity: 'critical',\r\n title: 'Realtime Database allows unauthenticated read access',\r\n description:\r\n 'The Firebase Realtime Database returned data at the root path ' +\r\n 'without any authentication. RTDB rules are configured with ' +\r\n '`\".read\": true` or `\".read\": \"auth == null\"` or similar. ' +\r\n 'All data stored in RTDB is publicly accessible.',\r\n evidence:\r\n `GET ${rtdbUrl.replace(/\\?.*/, '')} returned 200` +\r\n (keyCount > 0 ? ` (${keyCount} top-level keys)` : ''),\r\n recommendation:\r\n 'Update RTDB Security Rules to require authentication: ' +\r\n '`\".read\": \"auth != null\"` at minimum. Review what data is stored ' +\r\n 'in RTDB and assume it has been scraped if this rule was open.',\r\n });\r\n\r\n // Only need one RTDB URL to confirm — break\r\n break;\r\n }\r\n // 401 = \"Permission denied\" (good, rules are working)\r\n // 404 = RTDB not provisioned at this URL pattern\r\n } catch {\r\n // Timeout or network error — try the next URL pattern\r\n continue;\r\n }\r\n }\r\n\r\n // Also test RTDB write access\r\n try {\r\n const rtdbWriteUrl =\r\n `https://${detectedProjectId}-default-rtdb.firebaseio.com/vigile_probe_test.json`;\r\n const rtdbWriteRes = await fetchWithTimeout(rtdbWriteUrl, {\r\n method: 'PUT',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ _vigile_probe: 'security_test' }),\r\n });\r\n\r\n if (rtdbWriteRes.ok) {\r\n rtdbPublicAccess = true;\r\n findings.push({\r\n id: 'FB-002',\r\n category: 'firebase-rules-issue',\r\n severity: 'critical',\r\n title: 'Realtime Database allows unauthenticated WRITE access',\r\n description:\r\n 'Data was successfully written to Firebase Realtime Database without ' +\r\n 'any authentication. Anyone can create, modify, or delete data.',\r\n evidence: `PUT to RTDB vigile_probe_test.json returned 200`,\r\n recommendation:\r\n 'Immediately update RTDB Security Rules: `\".write\": \"auth != null\"`. ' +\r\n 'Audit all existing data for tampering. Consider migrating sensitive ' +\r\n 'data to Firestore with stricter per-document rules.',\r\n });\r\n }\r\n } catch {\r\n // Write probe failure is non-critical\r\n }\r\n }\r\n\r\n // ── Step 5: Firebase Storage bucket probe ───────────────\r\n if (detectedProjectId) {\r\n const storageBucket =\r\n bundleConfig.storageBucket ?? `${detectedProjectId}.appspot.com`;\r\n\r\n // Try listing objects in the default bucket\r\n const storageUrl =\r\n `https://firebasestorage.googleapis.com/v0/b/${storageBucket}/o`;\r\n\r\n try {\r\n const storageRes = await fetchWithTimeout(storageUrl);\r\n\r\n if (storageRes.ok) {\r\n let itemCount = 0;\r\n try {\r\n const body = (await storageRes.json()) as { items?: unknown[] };\r\n itemCount = body.items?.length ?? 0;\r\n } catch {\r\n // non-fatal\r\n }\r\n\r\n findings.push({\r\n id: 'FB-004',\r\n category: 'firebase-rules-issue',\r\n severity: 'high',\r\n title: 'Firebase Storage bucket is publicly listable',\r\n description:\r\n 'The Firebase Storage bucket returned a file listing without ' +\r\n 'authentication. Storage Security Rules allow public read access. ' +\r\n 'All files in the bucket can be enumerated and downloaded by anyone.',\r\n evidence:\r\n `GET ${storageUrl} returned 200` +\r\n (itemCount > 0 ? ` (${itemCount} files visible)` : ''),\r\n recommendation:\r\n 'Update Firebase Storage Security Rules to require authentication: ' +\r\n '`allow read: if request.auth != null;` — Review uploaded files for ' +\r\n 'sensitive content (user uploads, profile pictures, documents).',\r\n });\r\n }\r\n } catch {\r\n // Storage probe failure is non-critical\r\n }\r\n }\r\n\r\n // ── Step 6: Hosting security headers ────────────────────\r\n try {\r\n const headRes = await fetchWithTimeout(baseUrl);\r\n const headers = headRes.headers;\r\n\r\n const missingHeaders: string[] = [];\r\n if (!headers.get('x-content-type-options')) {\r\n missingHeaders.push('X-Content-Type-Options');\r\n }\r\n if (!headers.get('x-frame-options') && !headers.get('content-security-policy')?.includes('frame-ancestors')) {\r\n missingHeaders.push('X-Frame-Options or CSP frame-ancestors');\r\n }\r\n if (!headers.get('strict-transport-security')) {\r\n missingHeaders.push('Strict-Transport-Security');\r\n }\r\n\r\n if (missingHeaders.length >= 2) {\r\n findings.push({\r\n id: 'FB-005',\r\n category: 'cors-misconfiguration',\r\n severity: 'medium',\r\n title: 'Firebase Hosting missing security headers',\r\n description:\r\n `The Firebase Hosting response is missing ${missingHeaders.length} ` +\r\n `recommended security headers: ${missingHeaders.join(', ')}. ` +\r\n 'These headers protect against clickjacking, MIME sniffing, and ' +\r\n 'protocol downgrade attacks.',\r\n evidence: `Missing: ${missingHeaders.join(', ')}`,\r\n recommendation:\r\n 'Add security headers in firebase.json under hosting.headers: ' +\r\n '{\"source\": \"**\", \"headers\": [{\"key\": \"X-Content-Type-Options\", ' +\r\n '\"value\": \"nosniff\"}, {\"key\": \"X-Frame-Options\", \"value\": \"DENY\"}]}',\r\n });\r\n }\r\n } catch {\r\n // Header check is non-critical\r\n }\r\n\r\n return {\r\n projectUrl: baseUrl,\r\n projectId: detectedProjectId,\r\n findings,\r\n firestorePublicAccess,\r\n rtdbPublicAccess,\r\n configExposed,\r\n reachable,\r\n errors,\r\n };\r\n}\r\n","// ============================================================\r\n// Vigile CLI — CVE Detector\r\n// Checks detected npm/pypi/cargo packages against the OSV.dev\r\n// vulnerability database (free, no API key required).\r\n// ============================================================\r\n//\r\n// Finding IDs:\r\n// CVE-* Direct CVE from OSV.dev database — severity varies\r\n// GHSA-* GitHub Security Advisory — severity varies\r\n//\r\n// API reference: https://osv.dev/docs/#tag/api/post/v1/querybatch\r\n// ============================================================\r\n\r\nimport type { Finding } from '../../types/index.js';\r\n\r\nconst FETCH_TIMEOUT_MS = 15_000;\r\nconst OSV_BATCH_URL = 'https://api.osv.dev/v1/querybatch';\r\nconst MAX_BATCH_SIZE = 100; // OSV recommends batches of ≤1000, we cap lower\r\n\r\nexport interface DetectedPackage {\r\n /** Package name (npm, PyPI, etc.) */\r\n name: string;\r\n /** Detected version string */\r\n version: string;\r\n /** Ecosystem: npm, pypi, cargo, etc. */\r\n ecosystem: 'npm' | 'pypi' | 'cargo' | 'unknown';\r\n}\r\n\r\nexport interface CveMatch {\r\n pkg: DetectedPackage;\r\n cveId: string;\r\n cvssScore: number;\r\n summary: string;\r\n patchedVersion: string | null;\r\n}\r\n\r\nexport interface CveDetectionResult {\r\n packagesChecked: number;\r\n matches: CveMatch[];\r\n findings: Finding[];\r\n errors: string[];\r\n}\r\n\r\n// ── Fetch with timeout ──────────────────────────────────────\r\n\r\nasync function fetchWithTimeout(\r\n url: string,\r\n init?: RequestInit,\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\r\n try {\r\n return await fetch(url, { ...init, signal: controller.signal });\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n}\r\n\r\n// ── OSV.dev response types ──────────────────────────────────\r\n\r\ninterface OsvSeverity {\r\n type: string;\r\n score: string;\r\n}\r\n\r\ninterface OsvAffectedRange {\r\n type: string;\r\n events: Array<{ introduced?: string; fixed?: string; last_affected?: string }>;\r\n}\r\n\r\ninterface OsvAffectedPackage {\r\n package: { name: string; ecosystem: string };\r\n ranges?: OsvAffectedRange[];\r\n versions?: string[];\r\n}\r\n\r\ninterface OsvVuln {\r\n id: string;\r\n summary?: string;\r\n details?: string;\r\n severity?: OsvSeverity[];\r\n affected?: OsvAffectedPackage[];\r\n aliases?: string[];\r\n}\r\n\r\ninterface OsvBatchResponse {\r\n results: Array<{ vulns?: OsvVuln[] }>;\r\n}\r\n\r\n// ── CVSS score extraction ───────────────────────────────────\r\n\r\n/**\r\n * Extract a numeric CVSS score from OSV severity data.\r\n * OSV provides severity as CVSS_V3 or CVSS_V2 vector strings\r\n * (e.g. \"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\").\r\n * We parse the base score from the vector or fall back to a\r\n * heuristic based on the severity label.\r\n */\r\nfunction extractCvssScore(vuln: OsvVuln): number {\r\n if (vuln.severity && vuln.severity.length > 0) {\r\n for (const sev of vuln.severity) {\r\n // Try to extract numeric score from CVSS vector\r\n const scoreMatch = sev.score.match(/CVSS:[^/]+\\/.*?/);\r\n if (scoreMatch && sev.type === 'CVSS_V3') {\r\n // Parse AV/AC/PR/UI/S/C/I/A components for rough score estimation\r\n // For simplicity, use the ecosystem_specific database_specific severity\r\n return estimateCvssFromVector(sev.score);\r\n }\r\n }\r\n }\r\n // Default: unknown severity\r\n return 5.0;\r\n}\r\n\r\n/**\r\n * Rough CVSS 3.x score estimation from a vector string.\r\n * Full CVSS calculation is complex — we approximate from key metrics.\r\n */\r\nfunction estimateCvssFromVector(vector: string): number {\r\n const v = vector.toUpperCase();\r\n\r\n // Attack vector\r\n const avNetwork = v.includes('/AV:N');\r\n // Privileges required\r\n const prNone = v.includes('/PR:N');\r\n // Confidentiality / Integrity / Availability impact\r\n const cHigh = v.includes('/C:H');\r\n const iHigh = v.includes('/I:H');\r\n const aHigh = v.includes('/A:H');\r\n const cLow = v.includes('/C:L');\r\n const iLow = v.includes('/I:L');\r\n const aLow = v.includes('/A:L');\r\n\r\n let score = 5.0; // base\r\n if (avNetwork) score += 1.5;\r\n if (prNone) score += 1.0;\r\n if (cHigh) score += 0.8;\r\n if (iHigh) score += 0.8;\r\n if (aHigh) score += 0.8;\r\n if (cLow) score += 0.3;\r\n if (iLow) score += 0.3;\r\n if (aLow) score += 0.3;\r\n\r\n return Math.min(10.0, Math.round(score * 10) / 10);\r\n}\r\n\r\n/**\r\n * Extract the earliest fixed version from affected range events.\r\n */\r\nfunction extractPatchedVersion(vuln: OsvVuln, pkgName: string): string | null {\r\n if (!vuln.affected) return null;\r\n\r\n for (const affected of vuln.affected) {\r\n if (affected.package.name !== pkgName) continue;\r\n if (!affected.ranges) continue;\r\n\r\n for (const range of affected.ranges) {\r\n for (const event of range.events) {\r\n if (event.fixed) return event.fixed;\r\n }\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Get a preferred vulnerability ID — prefer CVE IDs over GHSA/OSV IDs.\r\n */\r\nfunction getPreferredId(vuln: OsvVuln): string {\r\n // Check aliases for a CVE ID first\r\n if (vuln.aliases) {\r\n const cve = vuln.aliases.find((a) => a.startsWith('CVE-'));\r\n if (cve) return cve;\r\n }\r\n // Fall back to the primary ID (GHSA-xxx or OSV-xxx)\r\n return vuln.id;\r\n}\r\n\r\n// ── Severity mapping ────────────────────────────────────────\r\n\r\nfunction cvssToSeverity(score: number): 'critical' | 'high' | 'medium' | 'low' {\r\n if (score >= 9.0) return 'critical';\r\n if (score >= 7.0) return 'high';\r\n if (score >= 4.0) return 'medium';\r\n return 'low';\r\n}\r\n\r\n// ── Main export ─────────────────────────────────────────────\r\n\r\n/**\r\n * Checks a list of detected packages against the OSV.dev\r\n * vulnerability database.\r\n *\r\n * Scan flow:\r\n * 1. Build batch query from package list\r\n * 2. POST to OSV.dev /v1/querybatch\r\n * 3. Parse vulnerability records into CveMatch objects\r\n * 4. Build Finding objects for each match\r\n *\r\n * @param packages List of packages detected in the scanned app\r\n */\r\nexport async function detectCves(packages: DetectedPackage[]): Promise<CveDetectionResult> {\r\n const matches: CveMatch[] = [];\r\n const findings: Finding[] = [];\r\n const errors: string[] = [];\r\n\r\n if (packages.length === 0) {\r\n return { packagesChecked: 0, matches, findings, errors };\r\n }\r\n\r\n // Filter out packages with non-parseable versions or 'unknown' ecosystem\r\n const queryablePackages = packages.filter(\r\n (p) => p.ecosystem !== 'unknown' && p.version && !p.version.includes('*'),\r\n );\r\n\r\n if (queryablePackages.length === 0) {\r\n errors.push('No packages with valid version + ecosystem for CVE lookup');\r\n return { packagesChecked: packages.length, matches, findings, errors };\r\n }\r\n\r\n // Batch into chunks of MAX_BATCH_SIZE\r\n const chunks: DetectedPackage[][] = [];\r\n for (let i = 0; i < queryablePackages.length; i += MAX_BATCH_SIZE) {\r\n chunks.push(queryablePackages.slice(i, i + MAX_BATCH_SIZE));\r\n }\r\n\r\n for (const chunk of chunks) {\r\n // Build OSV batch query\r\n const queries = chunk.map((pkg) => ({\r\n package: {\r\n name: pkg.name,\r\n ecosystem: pkg.ecosystem === 'npm' ? 'npm' : pkg.ecosystem === 'pypi' ? 'PyPI' : pkg.ecosystem,\r\n },\r\n version: pkg.version,\r\n }));\r\n\r\n try {\r\n const res = await fetchWithTimeout(OSV_BATCH_URL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ queries }),\r\n });\r\n\r\n if (!res.ok) {\r\n errors.push(`OSV.dev API returned HTTP ${res.status}`);\r\n continue;\r\n }\r\n\r\n const body = (await res.json()) as OsvBatchResponse;\r\n\r\n // Each result corresponds to the query at the same index\r\n for (let i = 0; i < body.results.length; i++) {\r\n const result = body.results[i];\r\n const pkg = chunk[i];\r\n if (!result?.vulns || result.vulns.length === 0) continue;\r\n\r\n // Deduplicate vulns by preferred ID (CVE > GHSA > OSV)\r\n const seenIds = new Set<string>();\r\n\r\n for (const vuln of result.vulns) {\r\n const vulnId = getPreferredId(vuln);\r\n if (seenIds.has(vulnId)) continue;\r\n seenIds.add(vulnId);\r\n\r\n const cvssScore = extractCvssScore(vuln);\r\n const patchedVersion = extractPatchedVersion(vuln, pkg.name);\r\n const summary = vuln.summary ?? vuln.details?.slice(0, 200) ?? 'No description available';\r\n\r\n const match: CveMatch = {\r\n pkg,\r\n cveId: vulnId,\r\n cvssScore,\r\n summary,\r\n patchedVersion,\r\n };\r\n matches.push(match);\r\n\r\n const severity = cvssToSeverity(cvssScore);\r\n findings.push({\r\n id: vulnId,\r\n category: 'cve-detected',\r\n severity,\r\n title: `${vulnId}: ${pkg.name}@${pkg.version}`,\r\n description:\r\n `Known vulnerability in ${pkg.name} version ${pkg.version}. ` +\r\n `${summary.slice(0, 300)}`,\r\n evidence:\r\n `Package: ${pkg.name}@${pkg.version} (${pkg.ecosystem}) | ` +\r\n `CVSS: ${cvssScore} | ` +\r\n (patchedVersion ? `Fixed in: ${patchedVersion}` : 'No patch available'),\r\n recommendation: patchedVersion\r\n ? `Upgrade ${pkg.name} to version ${patchedVersion} or later. ` +\r\n `Run: npm install ${pkg.name}@${patchedVersion}`\r\n : `No patched version available. Consider removing or replacing ` +\r\n `${pkg.name} with an alternative package. Monitor ${vulnId} for updates.`,\r\n });\r\n }\r\n }\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`OSV.dev query failed: ${msg}`);\r\n }\r\n }\r\n\r\n return {\r\n packagesChecked: packages.length,\r\n matches,\r\n findings,\r\n errors,\r\n };\r\n}\r\n\r\n/**\r\n * Parses package.json text and returns detected npm packages.\r\n * Used by vibe-app-scanner to extract dependencies from fetched\r\n * package.json files or bundle metadata.\r\n */\r\nexport function parseNpmPackages(packageJsonText: string): DetectedPackage[] {\r\n try {\r\n const parsed = JSON.parse(packageJsonText) as Record<string, unknown>;\r\n const deps = {\r\n ...((parsed.dependencies as Record<string, string>) ?? {}),\r\n ...((parsed.devDependencies as Record<string, string>) ?? {}),\r\n };\r\n return Object.entries(deps).map(([name, version]) => ({\r\n name,\r\n version: String(version).replace(/^[\\^~>=<]+/, ''), // strip semver range prefixes\r\n ecosystem: 'npm' as const,\r\n }));\r\n } catch {\r\n return [];\r\n }\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Vibe App Scanner (orchestrator)\r\n// The top-level BaaS scan orchestrator. Given a URL, detects\r\n// which BaaS platform is in use, then routes to the appropriate\r\n// sub-scanner, bundle-analyzer, and CVE detector.\r\n// ============================================================\r\n//\r\n// Scan flow:\r\n// 1. Bundle analysis — download JS bundles, scan for secrets\r\n// 2. Platform detect — identify Supabase or Firebase signals\r\n// 3. Platform scan — run supabase-scanner or firebase-scanner\r\n// 4. Package detection — try fetching package.json for CVE scan\r\n// 5. CVE detection — check detected packages against OSV.dev\r\n// 6. Aggregate — merge and deduplicate all findings\r\n// ============================================================\r\n\r\nimport { analyzeBundles } from './bundle-analyzer.js';\r\nimport { scanSupabase } from './supabase-scanner.js';\r\nimport { scanFirebase } from './firebase-scanner.js';\r\nimport { detectCves, parseNpmPackages } from './cve-detector.js';\r\nimport type { DetectedPackage } from './cve-detector.js';\r\nimport type { Finding } from '../../types/index.js';\r\n\r\nconst FETCH_TIMEOUT_MS = 10_000;\r\n\r\nexport type BaaSPlatform = 'supabase' | 'firebase' | 'unknown';\r\n\r\nexport interface VibeAppScanOptions {\r\n /** The deployed app URL (e.g. https://myapp.vercel.app) */\r\n appUrl: string;\r\n /** Override BaaS platform detection */\r\n platform?: BaaSPlatform;\r\n /** Extra Supabase project URL if different from appUrl */\r\n supabaseUrl?: string;\r\n /** Extra Firebase project URL if different from appUrl */\r\n firebaseUrl?: string;\r\n}\r\n\r\nexport interface VibeAppScanResult {\r\n appUrl: string;\r\n detectedPlatform: BaaSPlatform;\r\n findings: Finding[];\r\n bundlesAnalyzed: number;\r\n packagesChecked: number;\r\n cveMatches: number;\r\n errors: string[];\r\n}\r\n\r\n// ── Fetch with timeout ──────────────────────────────────────\r\n\r\nasync function fetchWithTimeout(\r\n url: string,\r\n init?: RequestInit,\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\r\n try {\r\n return await fetch(url, { ...init, signal: controller.signal });\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n}\r\n\r\n// ── Platform detection ──────────────────────────────────────\r\n\r\n/**\r\n * Detects which BaaS platform is used by examining:\r\n * 1. Secret finding evidence from bundle analysis\r\n * 2. URL patterns (*.supabase.co, *.web.app, *.firebaseapp.com)\r\n * 3. Bundle content signals (import paths, SDK references)\r\n */\r\nfunction detectPlatform(findings: Finding[], appUrl: string): BaaSPlatform {\r\n // Signal 1: Evidence from secret pattern matches\r\n for (const f of findings) {\r\n const ev = (f.evidence ?? '').toLowerCase();\r\n const title = f.title.toLowerCase();\r\n\r\n // Supabase signals — anon key JWTs, supabase SDK references\r\n if (\r\n ev.includes('supabase') ||\r\n title.includes('supabase') ||\r\n ev.includes('sb-') ||\r\n ev.includes('.supabase.co')\r\n ) {\r\n return 'supabase';\r\n }\r\n\r\n // Firebase signals — API keys, firebase SDK references\r\n if (\r\n ev.includes('firebase') ||\r\n title.includes('firebase') ||\r\n ev.includes('aizasy') || // Firebase API key prefix (lowercased)\r\n ev.includes('.firebaseapp.com') ||\r\n ev.includes('.firebaseio.com')\r\n ) {\r\n return 'firebase';\r\n }\r\n }\r\n\r\n // Signal 2: URL patterns\r\n try {\r\n const hostname = new URL(appUrl).hostname;\r\n if (hostname.endsWith('.supabase.co')) return 'supabase';\r\n if (hostname.endsWith('.web.app') || hostname.endsWith('.firebaseapp.com')) {\r\n return 'firebase';\r\n }\r\n } catch {\r\n // Invalid URL — fall through\r\n }\r\n\r\n return 'unknown';\r\n}\r\n\r\n// ── Package.json detection ──────────────────────────────────\r\n\r\n/**\r\n * Attempts to fetch package.json from common paths on the deployed app.\r\n * Many frameworks (Next.js on Vercel, CRA) don't serve package.json,\r\n * but some setups expose it. This is a best-effort heuristic.\r\n */\r\nasync function tryFetchPackageJson(appUrl: string): Promise<DetectedPackage[]> {\r\n const baseUrl = appUrl.endsWith('/') ? appUrl : `${appUrl}/`;\r\n\r\n // Common paths where package.json might be accessible\r\n const paths = [\r\n 'package.json',\r\n 'assets/package.json',\r\n ];\r\n\r\n for (const path of paths) {\r\n try {\r\n const res = await fetchWithTimeout(`${baseUrl}${path}`);\r\n if (res.ok) {\r\n const text = await res.text();\r\n // Validate it looks like a real package.json (not an HTML 404 page)\r\n if (text.trimStart().startsWith('{') && text.includes('\"dependencies\"')) {\r\n return parseNpmPackages(text);\r\n }\r\n }\r\n } catch {\r\n // Path not available — try next\r\n continue;\r\n }\r\n }\r\n\r\n return [];\r\n}\r\n\r\n/**\r\n * Extract npm package references from bundle content.\r\n * Bundles often contain package version comments or source map references\r\n * like \"node_modules/react/index.js\" that reveal dependency names.\r\n * We can't get exact versions from minified bundles, but we can detect\r\n * which packages are in use for informational purposes.\r\n */\r\nfunction extractPackagesFromBundleFindings(findings: Finding[]): string[] {\r\n const packageNames = new Set<string>();\r\n\r\n for (const f of findings) {\r\n const ev = f.evidence ?? '';\r\n // Look for node_modules references in evidence\r\n const nodeModuleMatches = ev.matchAll(/node_modules\\/(@[^/]+\\/[^/]+|[^/]+)/g);\r\n for (const m of nodeModuleMatches) {\r\n if (m[1]) packageNames.add(m[1]);\r\n }\r\n }\r\n\r\n return Array.from(packageNames);\r\n}\r\n\r\n// ── Finding deduplication ───────────────────────────────────\r\n\r\n/**\r\n * Deduplicates findings by their id + evidence combination.\r\n * The bundle analyzer and platform scanners may both find the\r\n * same secret, so we remove exact duplicates.\r\n */\r\nfunction deduplicateFindings(findings: Finding[]): Finding[] {\r\n const seen = new Set<string>();\r\n const unique: Finding[] = [];\r\n\r\n for (const f of findings) {\r\n const key = `${f.id}::${f.evidence ?? ''}`;\r\n if (seen.has(key)) continue;\r\n seen.add(key);\r\n unique.push(f);\r\n }\r\n\r\n return unique;\r\n}\r\n\r\n// ── Main export ─────────────────────────────────────────────\r\n\r\n/**\r\n * Orchestrates a full BaaS security scan for a deployed web app.\r\n *\r\n * @param opts App URL and optional platform overrides\r\n */\r\nexport async function scanVibeApp(opts: VibeAppScanOptions): Promise<VibeAppScanResult> {\r\n const allFindings: Finding[] = [];\r\n const errors: string[] = [];\r\n let packagesChecked = 0;\r\n let cveMatches = 0;\r\n\r\n // ── Step 1: Bundle analysis (always runs) ─────────────────\r\n const bundleResult = await analyzeBundles(opts.appUrl);\r\n allFindings.push(...bundleResult.findings);\r\n errors.push(...bundleResult.errors);\r\n\r\n // ── Step 2: Platform detection ────────────────────────────\r\n const detectedPlatform: BaaSPlatform =\r\n opts.platform ?? detectPlatform(bundleResult.findings, opts.appUrl);\r\n\r\n // ── Step 3: Platform-specific scan ────────────────────────\r\n if (detectedPlatform === 'supabase' || opts.supabaseUrl) {\r\n const supabaseUrl = opts.supabaseUrl ?? opts.appUrl;\r\n try {\r\n const supabaseResult = await scanSupabase({ projectUrl: supabaseUrl });\r\n // Only add non-duplicate findings (bundle analyzer already ran inside scanSupabase)\r\n allFindings.push(...supabaseResult.findings);\r\n errors.push(...supabaseResult.errors);\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`Supabase scan failed: ${msg}`);\r\n }\r\n }\r\n\r\n if (detectedPlatform === 'firebase' || opts.firebaseUrl) {\r\n const firebaseUrl = opts.firebaseUrl ?? opts.appUrl;\r\n try {\r\n const firebaseResult = await scanFirebase({ projectUrl: firebaseUrl });\r\n allFindings.push(...firebaseResult.findings);\r\n errors.push(...firebaseResult.errors);\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`Firebase scan failed: ${msg}`);\r\n }\r\n }\r\n\r\n if (detectedPlatform === 'unknown' && !opts.supabaseUrl && !opts.firebaseUrl) {\r\n errors.push(\r\n 'Could not detect BaaS platform (Supabase or Firebase) from bundle analysis or URL. ' +\r\n 'Provide --supabase <url> or --firebase <url> explicitly for deeper platform scanning.',\r\n );\r\n }\r\n\r\n // ── Step 4: Package detection for CVE scan ────────────────\r\n let packages: DetectedPackage[] = [];\r\n\r\n // Try fetching package.json from the deployed app\r\n try {\r\n packages = await tryFetchPackageJson(opts.appUrl);\r\n } catch {\r\n // Non-fatal — package.json fetch is best-effort\r\n }\r\n\r\n // If no package.json found, note the bundle-detected packages\r\n if (packages.length === 0) {\r\n const bundlePackageNames = extractPackagesFromBundleFindings(bundleResult.findings);\r\n if (bundlePackageNames.length > 0) {\r\n errors.push(\r\n `Detected ${bundlePackageNames.length} package(s) in bundles but could not ` +\r\n `determine versions for CVE lookup: ${bundlePackageNames.slice(0, 5).join(', ')}` +\r\n (bundlePackageNames.length > 5 ? '...' : ''),\r\n );\r\n }\r\n }\r\n\r\n // ── Step 5: CVE detection ─────────────────────────────────\r\n if (packages.length > 0) {\r\n try {\r\n const cveResult = await detectCves(packages);\r\n allFindings.push(...cveResult.findings);\r\n errors.push(...cveResult.errors);\r\n packagesChecked = cveResult.packagesChecked;\r\n cveMatches = cveResult.matches.length;\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`CVE detection failed: ${msg}`);\r\n }\r\n }\r\n\r\n // ── Step 6: Deduplicate and return ────────────────────────\r\n const findings = deduplicateFindings(allFindings);\r\n\r\n return {\r\n appUrl: opts.appUrl,\r\n detectedPlatform,\r\n findings,\r\n bundlesAnalyzed: bundleResult.bundlesAnalyzed,\r\n packagesChecked,\r\n cveMatches,\r\n errors,\r\n };\r\n}\r\n"],"mappings":"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","names":["exports","module","import_chalk","import_promises","import_path","import_path","import_path","import_fs","import_path","import_path","import_path","import_promises","import_fs","import_promises","import_fs","import_path","_","SEVERITY_DEDUCTIONS","deduplicateFindings","dnsPromises","chalk","import_promises","import_path","import_os","FETCH_TIMEOUT_MS","fetchWithTimeout","FETCH_TIMEOUT_MS","fetchWithTimeout","FETCH_TIMEOUT_MS","fetchWithTimeout","FETCH_TIMEOUT_MS","fetchWithTimeout","deduplicateFindings","chalk","ora"]}
|
|
1
|
+
{"version":3,"sources":["../package.json","../src/index.ts","../src/discovery/claude-desktop.ts","../src/discovery/utils.ts","../src/discovery/cursor.ts","../src/discovery/claude-code.ts","../src/discovery/windsurf.ts","../src/discovery/vscode.ts","../src/discovery/openclaw.ts","../src/discovery/skills.ts","../src/discovery/index.ts","../src/scanner/patterns.ts","../src/scoring/trust-score.ts","../src/scanner/index.ts","../src/scanner/skill-patterns.ts","../src/scanner/skill-scanner.ts","../src/sentinel/sentinel.ts","../src/sentinel/sentinel-patterns.ts","../src/output/terminal.ts","../src/output/json.ts","../src/api/auth.ts","../src/api/client.ts","../src/scanner/baas/secret-patterns.ts","../src/scanner/baas/bundle-analyzer.ts","../src/scanner/baas/supabase-scanner.ts","../src/scanner/baas/firebase-scanner.ts","../src/scanner/baas/cve-detector.ts","../src/scanner/baas/vibe-app-scanner.ts"],"sourcesContent":["{\r\n \"name\": \"vigile-scan\",\r\n \"version\": \"3.0.0\",\r\n \"description\": \"Security scanner for AI agent tools — detect tool poisoning, permission abuse, and supply chain attacks in MCP servers and agent skills\",\r\n \"main\": \"dist/index.js\",\r\n \"bin\": {\r\n \"vigile-scan\": \"dist/index.js\",\r\n \"vigile\": \"dist/index.js\"\r\n },\r\n \"files\": [\r\n \"dist\",\r\n \"README.md\",\r\n \"LICENSE\"\r\n ],\r\n \"scripts\": {\r\n \"build\": \"tsup && chmod +x dist/index.js\",\r\n \"dev\": \"tsup --watch\",\r\n \"start\": \"node dist/index.js\",\r\n \"lint\": \"eslint src/\",\r\n \"typecheck\": \"tsc --noEmit\",\r\n \"test\": \"vitest run\",\r\n \"test:watch\": \"vitest\",\r\n \"test:coverage\": \"vitest run --coverage\",\r\n \"prepublishOnly\": \"npm run build\"\r\n },\r\n \"keywords\": [\r\n \"mcp\",\r\n \"security\",\r\n \"ai-agent\",\r\n \"scanner\",\r\n \"trust\",\r\n \"model-context-protocol\",\r\n \"tool-poisoning\",\r\n \"ai-security\",\r\n \"agent-skills\",\r\n \"skill-scanning\",\r\n \"prompt-injection\",\r\n \"data-exfiltration\",\r\n \"claude\",\r\n \"cursor\",\r\n \"copilot\"\r\n ],\r\n \"author\": \"Vigile AI\",\r\n \"license\": \"Apache-2.0\",\r\n \"homepage\": \"https://vigile.dev\",\r\n \"repository\": {\r\n \"type\": \"git\",\r\n \"url\": \"git+https://github.com/Vigile-ai/vigile-scan.git\"\r\n },\r\n \"bugs\": {\r\n \"url\": \"https://github.com/Vigile-ai/vigile-scan/issues\"\r\n },\r\n \"publishConfig\": {\r\n \"access\": \"public\"\r\n },\r\n \"engines\": {\r\n \"node\": \">=18.0.0\"\r\n },\r\n \"dependencies\": {\r\n \"chalk\": \"^5.3.0\",\r\n \"commander\": \"^12.1.0\",\r\n \"glob\": \"^11.0.0\",\r\n \"ora\": \"^8.1.0\"\r\n },\r\n \"devDependencies\": {\r\n \"@types/node\": \"^20.11.0\",\r\n \"tsup\": \"^8.0.0\",\r\n \"typescript\": \"^5.4.0\",\r\n \"vitest\": \"^2.0.0\"\r\n }\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Main Entry Point\r\n// ============================================================\r\n// Usage: npx vigile-scan [options]\r\n//\r\n// The AI agent security scanner. Discovers MCP server configs\r\n// and agent skill files on your machine, scans them for\r\n// security issues, and outputs trust scores.\r\n//\r\n// v0.2.0: Added API integration — scan results upload to the\r\n// Vigile registry, authentication, and Sentinel API sessions.\r\n\r\nimport { Command } from 'commander';\r\nimport chalk from 'chalk';\r\nimport ora from 'ora';\r\nimport { writeFile } from 'fs/promises';\r\nimport { discoverAllServers, discoverAllSkills } from './discovery/index.js';\r\nimport { scanServer } from './scanner/index.js';\r\nimport { scanSkill } from './scanner/skill-scanner.js';\r\nimport {\r\n SentinelEngine,\r\n getSentinelFeatures,\r\n SENTINEL_MARKETING,\r\n} from './sentinel/index.js';\r\nimport type { SentinelReport, NetworkEvent } from './sentinel/index.js';\r\nimport {\r\n printBanner,\r\n printServerResult,\r\n printSkillResult,\r\n printSummary,\r\n printSentinelReport,\r\n printSentinelUpgrade,\r\n printNoServersFound,\r\n printNoSkillsFound,\r\n printNothingFound,\r\n printAuthStatus,\r\n printAuthLoginSuccess,\r\n printUploadSuccess,\r\n printUploadSkipped,\r\n} from './output/terminal.js';\r\nimport { formatJSON } from './output/json.js';\r\nimport { getAuthenticatedClient, authLogin, authStatus, authLogout } from './api/auth.js';\r\nimport { scanVibeApp, scanSupabase, scanFirebase } from './scanner/baas/index.js';\r\nimport type { VigileApiClient } from './api/client.js';\r\nimport type {\r\n ScanOptions,\r\n ScanSummary,\r\n ScanResult,\r\n SkillScanResult,\r\n MCPClient,\r\n UploadSummary,\r\n} from './types/index.js';\r\n\r\n// Read version dynamically so it never goes stale.\r\n// eslint-disable-next-line @typescript-eslint/no-require-imports\r\nconst VERSION: string = require('../package.json').version;\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name('vigile-scan')\r\n .description(\r\n 'Security scanner for AI agent tools — detect tool poisoning, permission abuse, and supply chain attacks in MCP servers and agent skills',\r\n )\r\n .version(VERSION);\r\n\r\n// Shared options for scan commands\r\nfunction addScanOptions(cmd: Command): Command {\r\n return cmd\r\n .option('-j, --json', 'Output results as JSON')\r\n .option('-v, --verbose', 'Show detailed findings and score breakdown')\r\n .option('-c, --config <path>', 'Path to a custom MCP config file')\r\n .option('-o, --output <path>', 'Write results to a file')\r\n .option(\r\n '--client <client>',\r\n 'Only scan a specific client (claude-desktop, cursor, claude-code, windsurf, vscode, openclaw)',\r\n )\r\n .option('-s, --skills', 'Scan agent skills only (SKILL.md, .mdc rules, CLAUDE.md, etc.)')\r\n .option('-a, --all', 'Scan both MCP servers and agent skills')\r\n .option('--sentinel', 'Enable Sentinel runtime monitoring (Pro+ feature)')\r\n .option('--sentinel-server <name>', 'Monitor a specific MCP server by name')\r\n .option('--sentinel-duration <seconds>', 'Monitoring duration in seconds (default: 120)', parseInt)\r\n .option('--no-upload', 'Skip uploading scan results to Vigile API')\r\n .option('--supabase <url>', 'Scan a Supabase project URL for RLS issues and exposed keys')\r\n .option('--supabase-key <key>', 'Supabase anon key (auto-detected from bundles if omitted)')\r\n .option('--firebase <url>', 'Scan a Firebase project URL for rules issues and exposed keys')\r\n .option('--app <url>', 'Scan a deployed web app for exposed secrets and BaaS misconfigs');\r\n}\r\n\r\naddScanOptions(\r\n program\r\n .command('scan')\r\n .description('Scan MCP server configurations and agent skill files on this machine'),\r\n).action(async (options: ScanOptions) => {\r\n await runScan(options);\r\n});\r\n\r\n// Default command (no subcommand) also runs scan\r\naddScanOptions(program).action(async (options: ScanOptions) => {\r\n if (!process.argv.slice(2).includes('scan') && !process.argv.slice(2).includes('auth')) {\r\n await runScan(options);\r\n }\r\n});\r\n\r\n// ── Auth subcommand ──\r\nconst authCmd = program\r\n .command('auth')\r\n .description('Manage Vigile API authentication');\r\n\r\nauthCmd\r\n .command('login')\r\n .description('Authenticate with your Vigile API key')\r\n .argument('[token]', 'API key (vgl_...) or JWT token. If omitted, reads from VIGILE_TOKEN env var.')\r\n .action(async (token?: string) => {\r\n const resolvedToken = token || process.env.VIGILE_TOKEN;\r\n if (!resolvedToken) {\r\n console.log(chalk.red(' No token provided. Pass a token argument or set VIGILE_TOKEN env var.'));\r\n console.log(chalk.gray(' Usage: vigile-scan auth login <vgl_your_api_key>'));\r\n console.log(chalk.gray(' Get an API key at https://vigile.dev/account'));\r\n process.exit(1);\r\n }\r\n\r\n const spinner = ora('Validating token...').start();\r\n const result = await authLogin(resolvedToken);\r\n\r\n if (result.success && result.user) {\r\n spinner.succeed('Token validated');\r\n printAuthLoginSuccess(result.user.email, result.user.tier);\r\n } else {\r\n spinner.fail('Authentication failed');\r\n console.log(chalk.red(` Error: ${result.error || 'Unknown error'}`));\r\n process.exit(1);\r\n }\r\n });\r\n\r\nauthCmd\r\n .command('status')\r\n .description('Show current authentication status')\r\n .action(async () => {\r\n const result = await authStatus();\r\n printAuthStatus({\r\n authenticated: result.authenticated,\r\n source: result.source,\r\n email: result.user?.email,\r\n tier: result.user?.tier,\r\n name: result.user?.name || undefined,\r\n error: result.error,\r\n });\r\n });\r\n\r\nauthCmd\r\n .command('logout')\r\n .description('Clear stored credentials')\r\n .action(async () => {\r\n await authLogout();\r\n console.log(chalk.green(' Logged out. Credentials removed from ~/.vigile/config.json'));\r\n console.log('');\r\n });\r\n\r\n// ============================================================\r\n// Main Scan Flow\r\n// ============================================================\r\n\r\nasync function runScan(options: ScanOptions): Promise<void> {\r\n const isJSON = options.json ?? false;\r\n const scanMCP = !options.skills; // Scan MCP unless --skills only\r\n const scanSkills = options.skills || options.all; // Scan skills if --skills or --all\r\n\r\n if (!isJSON) {\r\n printBanner();\r\n }\r\n\r\n const results: ScanResult[] = [];\r\n const skillResults: SkillScanResult[] = [];\r\n\r\n // ── Step 1: Discover & scan MCP servers ──\r\n if (scanMCP) {\r\n const spinner = isJSON ? null : ora('Discovering MCP configurations...').start();\r\n const discovery = await discoverAllServers(options.client as MCPClient | undefined);\r\n\r\n if (discovery.servers.length === 0) {\r\n spinner?.succeed('No MCP server configurations found');\r\n } else {\r\n spinner?.succeed(\r\n `Found ${discovery.servers.length} MCP server(s) across ${discovery.configsFound} config file(s)`,\r\n );\r\n\r\n const scanSpinner = isJSON ? null : ora('Scanning MCP servers...').start();\r\n for (const server of discovery.servers) {\r\n const result = await scanServer(server);\r\n results.push(result);\r\n }\r\n scanSpinner?.succeed(`Scanned ${results.length} MCP server(s)`);\r\n }\r\n }\r\n\r\n // ── Step 2: Discover & scan agent skills ──\r\n if (scanSkills) {\r\n const spinner = isJSON ? null : ora('Discovering agent skill files...').start();\r\n const skillDiscovery = await discoverAllSkills();\r\n\r\n if (skillDiscovery.skills.length === 0) {\r\n spinner?.succeed('No agent skill files found');\r\n } else {\r\n spinner?.succeed(\r\n `Found ${skillDiscovery.skills.length} skill file(s) across ${skillDiscovery.locationsFound} location(s)`,\r\n );\r\n\r\n const scanSpinner = isJSON ? null : ora('Scanning agent skills...').start();\r\n for (const skill of skillDiscovery.skills) {\r\n const result = await scanSkill(skill);\r\n skillResults.push(result);\r\n }\r\n scanSpinner?.succeed(`Scanned ${skillResults.length} skill file(s)`);\r\n }\r\n }\r\n\r\n // ── Check if anything was found ──\r\n if (results.length === 0 && skillResults.length === 0) {\r\n if (!isJSON) {\r\n if (scanMCP && !scanSkills) {\r\n printNoServersFound();\r\n } else if (scanSkills && !scanMCP) {\r\n printNoSkillsFound();\r\n } else {\r\n printNothingFound();\r\n }\r\n } else {\r\n console.log(JSON.stringify({ servers: [], skills: [], message: 'Nothing found to scan' }));\r\n }\r\n return;\r\n }\r\n\r\n // ── Step 3: Build combined summary ──\r\n const allResults = [...results];\r\n const allSkillResults = [...skillResults];\r\n\r\n // Combine trust level counts from both MCP and skill results\r\n const allTrustLevels = [\r\n ...results.map((r) => r.trustLevel),\r\n ...skillResults.map((r) => r.trustLevel),\r\n ];\r\n\r\n // Combine finding severities from both MCP and skill results\r\n const allFindings = [\r\n ...results.flatMap((r) => r.findings),\r\n ...skillResults.flatMap((r) => r.findings),\r\n ];\r\n\r\n const summary: ScanSummary = {\r\n totalServers: allResults.length,\r\n totalSkills: allSkillResults.length,\r\n byTrustLevel: {\r\n trusted: allTrustLevels.filter((l) => l === 'trusted').length,\r\n caution: allTrustLevels.filter((l) => l === 'caution').length,\r\n risky: allTrustLevels.filter((l) => l === 'risky').length,\r\n dangerous: allTrustLevels.filter((l) => l === 'dangerous').length,\r\n },\r\n bySeverity: {\r\n critical: allFindings.filter((f) => f.severity === 'critical').length,\r\n high: allFindings.filter((f) => f.severity === 'high').length,\r\n medium: allFindings.filter((f) => f.severity === 'medium').length,\r\n low: allFindings.filter((f) => f.severity === 'low').length,\r\n info: allFindings.filter((f) => f.severity === 'info').length,\r\n },\r\n results: allResults,\r\n skillResults: allSkillResults,\r\n timestamp: new Date().toISOString(),\r\n version: VERSION,\r\n };\r\n\r\n // ── Step 4: Output results ──\r\n if (isJSON) {\r\n const jsonOutput = formatJSON(summary);\r\n if (options.output) {\r\n await writeFile(options.output, jsonOutput);\r\n } else {\r\n // Write JSON and wait for stdout to drain — prevents truncation\r\n // when piped (stdout is async for pipes, sync for TTYs/files)\r\n await new Promise<void>((resolve, reject) => {\r\n const ok = process.stdout.write(jsonOutput + '\\n');\r\n if (ok) resolve();\r\n else process.stdout.once('drain', resolve);\r\n });\r\n }\r\n } else {\r\n console.log('');\r\n\r\n // Print MCP server results\r\n if (results.length > 0) {\r\n for (const result of results) {\r\n printServerResult(result, options.verbose ?? false);\r\n }\r\n }\r\n\r\n // Print skill results\r\n if (skillResults.length > 0) {\r\n for (const result of skillResults) {\r\n printSkillResult(result, options.verbose ?? false);\r\n }\r\n }\r\n\r\n printSummary(summary);\r\n\r\n if (options.output) {\r\n await writeFile(options.output, formatJSON(summary));\r\n console.log(` Results saved to ${options.output}`);\r\n }\r\n }\r\n\r\n // ── Step 4.5: Upload results to Vigile API (if authenticated) ──\r\n if (options.noUpload !== true) {\r\n await uploadResults(results, skillResults, isJSON);\r\n }\r\n\r\n // ── Step 5: Sentinel Runtime Monitoring (if --sentinel) ──\r\n if (options.sentinel) {\r\n await runSentinel(options, results, isJSON);\r\n }\r\n\r\n // ── Step 6: BaaS Scanning (if --supabase / --firebase / --app) ──\r\n if (options.supabase || options.firebase || options.app) {\r\n await runBaaSScan(options, isJSON);\r\n }\r\n\r\n // ── Exit with appropriate code ──\r\n if (summary.bySeverity.critical > 0 || summary.bySeverity.high > 0) {\r\n // Wait for stdout to drain before exiting — prevents truncation\r\n // when output is piped (stdout is async for pipes, not TTYs)\r\n await new Promise<void>((resolve) => {\r\n if (process.stdout.writableNeedDrain) {\r\n process.stdout.once('drain', resolve);\r\n } else {\r\n resolve();\r\n }\r\n });\r\n process.exit(1);\r\n }\r\n}\r\n\r\n// ============================================================\r\n// API Upload — Send scan results to the Vigile registry\r\n// ============================================================\r\n\r\n/**\r\n * Upload scan results to the Vigile API.\r\n * Graceful degradation: if API is unreachable or auth fails, just warn.\r\n */\r\nasync function uploadResults(\r\n mcpResults: ScanResult[],\r\n skillResults: SkillScanResult[],\r\n isJSON: boolean,\r\n): Promise<void> {\r\n const client = await getAuthenticatedClient();\r\n\r\n if (!client) {\r\n if (!isJSON) {\r\n printUploadSkipped('not-authenticated');\r\n }\r\n return;\r\n }\r\n\r\n const summary: UploadSummary = {\r\n mcpUploaded: 0,\r\n skillsUploaded: 0,\r\n failures: 0,\r\n errors: [],\r\n };\r\n\r\n const spinner = isJSON ? null : ora('Uploading results to Vigile registry...').start();\r\n\r\n // Upload MCP scan results\r\n for (const result of mcpResults) {\r\n const payload = mapMCPResultToApiPayload(result);\r\n const response = await client.submitMCPScan(payload);\r\n\r\n if (response.ok) {\r\n summary.mcpUploaded++;\r\n } else {\r\n summary.failures++;\r\n summary.errors.push(`${result.server.name}: ${response.error}`);\r\n }\r\n }\r\n\r\n // Upload skill scan results\r\n for (const result of skillResults) {\r\n const payload = mapSkillResultToApiPayload(result);\r\n const response = await client.submitSkillScan(payload);\r\n\r\n if (response.ok) {\r\n summary.skillsUploaded++;\r\n } else {\r\n summary.failures++;\r\n summary.errors.push(`${result.skill.name}: ${response.error}`);\r\n }\r\n }\r\n\r\n if (spinner) {\r\n const total = summary.mcpUploaded + summary.skillsUploaded;\r\n if (total > 0 && summary.failures === 0) {\r\n spinner.succeed(`Uploaded ${total} result(s) to Vigile registry`);\r\n } else if (total > 0 && summary.failures > 0) {\r\n spinner.warn(`Uploaded ${total} result(s), ${summary.failures} failed`);\r\n } else {\r\n spinner.fail('Upload failed');\r\n }\r\n }\r\n\r\n if (!isJSON) {\r\n printUploadSuccess(summary);\r\n }\r\n}\r\n\r\n// ============================================================\r\n// BaaS Scan Flow — Supabase / Firebase / deployed app scanning\r\n// ============================================================\r\n\r\n/**\r\n * Runs BaaS security scanning for any --supabase, --firebase,\r\n * or --app URLs provided as CLI flags.\r\n *\r\n * Prints findings inline and exits with code 1 if critical/high\r\n * findings are discovered (same contract as MCP scan).\r\n */\r\nasync function runBaaSScan(options: ScanOptions, isJSON: boolean): Promise<void> {\r\n const spinner = isJSON ? null : ora('Running BaaS security scan...').start();\r\n\r\n let totalFindings = 0;\r\n let criticalOrHigh = 0;\r\n\r\n // ── Supabase scan ──\r\n if (options.supabase) {\r\n const result = await scanSupabase({\r\n projectUrl: options.supabase,\r\n anonKey: options.supabaseKey,\r\n });\r\n totalFindings += result.findings.length;\r\n criticalOrHigh += result.findings.filter(\r\n (f) => f.severity === 'critical' || f.severity === 'high',\r\n ).length;\r\n\r\n if (!isJSON) {\r\n spinner?.stop();\r\n console.log(chalk.bold(`\\n Supabase: ${options.supabase}`));\r\n if (result.errors.length > 0) {\r\n result.errors.forEach((e) => console.log(chalk.yellow(` ⚠ ${e}`)));\r\n }\r\n result.findings.forEach((f) => {\r\n const color =\r\n f.severity === 'critical' ? chalk.red : f.severity === 'high' ? chalk.yellow : chalk.gray;\r\n console.log(color(` [${f.severity.toUpperCase()}] ${f.title}`));\r\n if (options.verbose) console.log(chalk.gray(` ${f.description}`));\r\n });\r\n }\r\n }\r\n\r\n // ── Firebase scan ──\r\n if (options.firebase) {\r\n const result = await scanFirebase({ projectUrl: options.firebase });\r\n totalFindings += result.findings.length;\r\n criticalOrHigh += result.findings.filter(\r\n (f) => f.severity === 'critical' || f.severity === 'high',\r\n ).length;\r\n\r\n if (!isJSON) {\r\n spinner?.stop();\r\n console.log(chalk.bold(`\\n Firebase: ${options.firebase}`));\r\n if (result.errors.length > 0) {\r\n result.errors.forEach((e) => console.log(chalk.yellow(` ⚠ ${e}`)));\r\n }\r\n result.findings.forEach((f) => {\r\n const color =\r\n f.severity === 'critical' ? chalk.red : f.severity === 'high' ? chalk.yellow : chalk.gray;\r\n console.log(color(` [${f.severity.toUpperCase()}] ${f.title}`));\r\n if (options.verbose) console.log(chalk.gray(` ${f.description}`));\r\n });\r\n }\r\n }\r\n\r\n // ── Web app scan (bundle analysis + platform detection) ──\r\n if (options.app) {\r\n const result = await scanVibeApp({ appUrl: options.app });\r\n totalFindings += result.findings.length;\r\n criticalOrHigh += result.findings.filter(\r\n (f) => f.severity === 'critical' || f.severity === 'high',\r\n ).length;\r\n\r\n if (!isJSON) {\r\n spinner?.stop();\r\n console.log(chalk.bold(`\\n App: ${options.app}`));\r\n console.log(chalk.gray(` Platform detected: ${result.detectedPlatform}`));\r\n console.log(chalk.gray(` Bundles analyzed: ${result.bundlesAnalyzed}`));\r\n if (result.errors.length > 0) {\r\n result.errors.forEach((e) => console.log(chalk.yellow(` ⚠ ${e}`)));\r\n }\r\n result.findings.forEach((f) => {\r\n const color =\r\n f.severity === 'critical' ? chalk.red : f.severity === 'high' ? chalk.yellow : chalk.gray;\r\n console.log(color(` [${f.severity.toUpperCase()}] ${f.title}`));\r\n if (options.verbose) {\r\n console.log(chalk.gray(` ${f.description}`));\r\n if (f.evidence) console.log(chalk.gray(` Evidence: ${f.evidence}`));\r\n }\r\n });\r\n }\r\n }\r\n\r\n if (!isJSON) {\r\n console.log('');\r\n if (totalFindings === 0) {\r\n console.log(chalk.green(' BaaS scan complete — no secrets or misconfigurations found'));\r\n } else {\r\n console.log(\r\n chalk.yellow(` BaaS scan complete — ${totalFindings} finding(s), ${criticalOrHigh} critical/high`),\r\n );\r\n }\r\n console.log('');\r\n }\r\n}\r\n\r\n// ============================================================\r\n// Field Mapping Helpers — CLI types → API request shapes\r\n// ============================================================\r\n\r\n/**\r\n * Map a CLI ScanResult to the API ScanRequest payload.\r\n */\r\nfunction mapMCPResultToApiPayload(result: ScanResult): {\r\n server_name: string;\r\n source: string;\r\n package_url?: string;\r\n description?: string;\r\n tool_descriptions?: string[];\r\n} {\r\n // Derive package URL from command + args\r\n let packageUrl: string | undefined;\r\n if (result.server.command === 'npx') {\r\n const packageName = result.server.args.find((a) => !a.startsWith('-'));\r\n if (packageName) {\r\n packageUrl = `https://www.npmjs.com/package/${packageName}`;\r\n }\r\n } else if (result.server.command === 'uvx' || result.server.command === 'pip') {\r\n const packageName = result.server.args.find((a) => !a.startsWith('-'));\r\n if (packageName) {\r\n packageUrl = `https://pypi.org/project/${packageName}/`;\r\n }\r\n }\r\n\r\n // Tool descriptions from non-flag args\r\n const toolDescriptions = result.server.args.filter((a) => !a.startsWith('-'));\r\n\r\n return {\r\n server_name: result.server.name,\r\n source: 'manual', // CLI-discovered servers are always manual source\r\n package_url: packageUrl,\r\n description: `MCP server discovered from ${result.server.source} config`,\r\n tool_descriptions: toolDescriptions.length > 0 ? toolDescriptions : undefined,\r\n };\r\n}\r\n\r\n/**\r\n * Map a CLI SkillScanResult to the API SkillScanRequest payload.\r\n */\r\nfunction mapSkillResultToApiPayload(result: SkillScanResult): {\r\n skill_name: string;\r\n content: string;\r\n file_type: string;\r\n platform: string;\r\n source: string;\r\n} {\r\n // Map CLI SkillSource to API platform values\r\n const platformMap: Record<string, string> = {\r\n 'claude-code': 'claude-code',\r\n 'github-copilot': 'copilot',\r\n 'cursor': 'cursor',\r\n 'memory-file': 'unknown',\r\n 'custom': 'unknown',\r\n };\r\n\r\n return {\r\n skill_name: result.skill.name,\r\n content: result.skill.content,\r\n file_type: result.skill.fileType,\r\n platform: platformMap[result.skill.source] || 'unknown',\r\n source: 'manual', // CLI submissions are always manual source\r\n };\r\n}\r\n\r\n/**\r\n * Map a CLI NetworkEvent (camelCase) to the API NetworkEventSubmission (snake_case).\r\n */\r\nfunction mapNetworkEventToApi(event: NetworkEvent): {\r\n timestamp: number;\r\n server_name: string;\r\n method: string;\r\n url: string;\r\n destination_ip: string | null;\r\n port: number;\r\n request_size: number;\r\n response_size: number | null;\r\n status_code: number | null;\r\n headers: Record<string, string> | null;\r\n dns_query_type: string | null;\r\n tls: boolean;\r\n body_hash: string | null;\r\n body_entropy: number | null;\r\n} {\r\n return {\r\n timestamp: event.timestamp,\r\n server_name: event.serverName,\r\n method: event.method,\r\n url: event.url,\r\n destination_ip: event.destinationIp || null,\r\n port: event.port,\r\n request_size: event.requestSize,\r\n response_size: event.responseSize ?? null,\r\n status_code: event.statusCode ?? null,\r\n headers: event.headers || null,\r\n dns_query_type: event.dnsQueryType ?? null,\r\n tls: event.tls,\r\n body_hash: event.bodyHash ?? null,\r\n body_entropy: event.bodyEntropy ?? null,\r\n };\r\n}\r\n\r\n// ============================================================\r\n// Sentinel Runtime Monitoring — with API integration\r\n// ============================================================\r\n\r\n/**\r\n * Run Sentinel runtime monitoring on discovered MCP servers.\r\n * When authenticated, creates API sessions and submits events\r\n * for server-side analysis. Falls back to local-only when offline.\r\n */\r\nasync function runSentinel(\r\n options: ScanOptions,\r\n scanResults: ScanResult[],\r\n isJSON: boolean,\r\n): Promise<void> {\r\n // Resolve tier: API if authenticated, else env var fallback\r\n const client = await getAuthenticatedClient();\r\n let tier: 'free' | 'pro' = 'free';\r\n\r\n if (client) {\r\n const meResult = await client.getMe();\r\n if (meResult.ok) {\r\n tier = meResult.data.tier as typeof tier;\r\n }\r\n } else {\r\n tier = (process.env.VIGILE_TIER as typeof tier) || 'free';\r\n }\r\n\r\n const features = getSentinelFeatures(tier);\r\n\r\n if (!features.monitoringEnabled) {\r\n if (!isJSON) {\r\n printSentinelUpgrade();\r\n } else {\r\n console.log(\r\n JSON.stringify({\r\n sentinel: { error: 'upgrade_required', message: SENTINEL_MARKETING.upgradePrompt },\r\n }),\r\n );\r\n }\r\n return;\r\n }\r\n\r\n // Determine which server(s) to monitor\r\n const serversToMonitor: string[] = [];\r\n if (options.sentinelServer) {\r\n serversToMonitor.push(options.sentinelServer);\r\n } else if (scanResults.length > 0) {\r\n // Monitor all discovered servers (up to the tier limit)\r\n const limit =\r\n features.maxConcurrentServers === -1\r\n ? scanResults.length\r\n : Math.min(scanResults.length, features.maxConcurrentServers);\r\n for (let i = 0; i < limit; i++) {\r\n serversToMonitor.push(scanResults[i].server.name);\r\n }\r\n } else {\r\n if (!isJSON) {\r\n console.log(\r\n chalk.yellow(' No MCP servers to monitor. Run a scan first or specify --sentinel-server <name>.'),\r\n );\r\n }\r\n return;\r\n }\r\n\r\n // Clamp duration to tier limit\r\n const requestedDuration = options.sentinelDuration || 120;\r\n const maxDuration = features.maxDurationSeconds === -1 ? requestedDuration : features.maxDurationSeconds;\r\n const duration = Math.min(requestedDuration, maxDuration);\r\n\r\n if (!isJSON) {\r\n console.log('');\r\n console.log(chalk.bold.hex('#2C4A7C')(' \\u{1F6E1}\\uFE0F Vigile Sentinel \\u2014 Runtime Monitor'));\r\n console.log(\r\n chalk.gray(` Tier: ${tier.toUpperCase()} | Duration: ${duration}s | Servers: ${serversToMonitor.length}`),\r\n );\r\n console.log('');\r\n }\r\n\r\n // Start monitoring each server\r\n const sentinelReports: SentinelReport[] = [];\r\n\r\n for (const serverName of serversToMonitor) {\r\n // Create API session if authenticated\r\n let apiSessionId: number | null = null;\r\n if (client) {\r\n const sessionResult = await client.createSentinelSession(serverName, duration);\r\n if (sessionResult.ok) {\r\n apiSessionId = sessionResult.data.session_id;\r\n }\r\n }\r\n\r\n const spinner = isJSON ? null : ora(`Monitoring ${serverName} for ${duration}s...`).start();\r\n\r\n // Batch events for API submission\r\n let pendingApiEvents: NetworkEvent[] = [];\r\n let lastApiSubmit = Date.now();\r\n\r\n const engine = new SentinelEngine({\r\n serverName,\r\n durationSeconds: duration,\r\n onEvent: (event) => {\r\n if (!isJSON && spinner) {\r\n spinner.text = `Monitoring ${serverName} \\u2014 ${(engine as unknown as { events: unknown[] }).events.length} events captured...`;\r\n }\r\n\r\n // Accumulate events for API batch submission\r\n if (apiSessionId !== null && client) {\r\n pendingApiEvents.push(event);\r\n\r\n // Submit every 5 seconds\r\n const now = Date.now();\r\n if (now - lastApiSubmit > 5000 && pendingApiEvents.length > 0) {\r\n const eventsToSubmit = pendingApiEvents.map(mapNetworkEventToApi);\r\n pendingApiEvents = [];\r\n lastApiSubmit = now;\r\n // Fire and forget — don't await in the event handler\r\n client.submitSentinelEvents(apiSessionId, eventsToSubmit).catch(() => {\r\n // Silently ignore API submission errors during monitoring\r\n });\r\n }\r\n }\r\n },\r\n });\r\n\r\n await engine.startMonitoring();\r\n\r\n // Wait for monitoring duration\r\n await new Promise((resolve) => setTimeout(resolve, duration * 1000));\r\n\r\n const report = await engine.stopMonitoring();\r\n sentinelReports.push(report);\r\n\r\n // Submit remaining events and get API analysis\r\n if (apiSessionId !== null && client) {\r\n if (pendingApiEvents.length > 0) {\r\n const finalEvents = pendingApiEvents.map(mapNetworkEventToApi);\r\n await client.submitSentinelEvents(apiSessionId, finalEvents);\r\n }\r\n\r\n const apiReport = await client.analyzeSentinelSession(apiSessionId);\r\n if (apiReport.ok && !isJSON) {\r\n console.log(\r\n chalk.gray(\r\n ` API Analysis: ${apiReport.data.findings.length} findings, threat: ${apiReport.data.threat_level}`,\r\n ),\r\n );\r\n }\r\n }\r\n\r\n if (spinner) {\r\n spinner.succeed(\r\n `${serverName}: ${report.totalEvents} events, ${report.findings.length} findings [${report.threatLevel.toUpperCase()}]`,\r\n );\r\n }\r\n }\r\n\r\n // Output Sentinel results\r\n if (isJSON) {\r\n console.log(JSON.stringify({ sentinel: sentinelReports }, null, 2));\r\n } else {\r\n for (const report of sentinelReports) {\r\n printSentinelReport(report);\r\n }\r\n }\r\n}\r\n\r\nprogram.parse();\r\n","// ============================================================\r\n// Claude Desktop — MCP Config Discovery\r\n// ============================================================\r\n// Config locations (as of February 2026):\r\n// macOS: ~/Library/Application Support/Claude/claude_desktop_config.json\r\n// Windows: %APPDATA%\\Claude\\claude_desktop_config.json\r\n// Linux: ~/.config/Claude/claude_desktop_config.json\r\n\r\nimport { join } from 'path';\r\nimport { getHome, getPlatform, getAppData, tryConfigPaths } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverClaudeDesktop(): Promise<MCPServerEntry[]> {\r\n const home = getHome();\r\n const plat = getPlatform();\r\n\r\n const paths: string[] = [];\r\n\r\n switch (plat) {\r\n case 'darwin':\r\n paths.push(\r\n join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')\r\n );\r\n break;\r\n case 'win32':\r\n paths.push(join(getAppData(), 'Claude', 'claude_desktop_config.json'));\r\n break;\r\n case 'linux':\r\n paths.push(join(home, '.config', 'Claude', 'claude_desktop_config.json'));\r\n break;\r\n }\r\n\r\n return tryConfigPaths(paths, 'claude-desktop');\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Discovery Utilities\r\n// ============================================================\r\n\r\nimport { readFile } from 'fs/promises';\r\nimport { existsSync } from 'fs';\r\nimport { join } from 'path';\r\nimport { homedir, platform } from 'os';\r\nimport type { MCPClient, MCPServerEntry } from '../types/index.js';\r\n\r\n/** Get the home directory */\r\nexport function getHome(): string {\r\n return homedir();\r\n}\r\n\r\n/** Get the current platform */\r\nexport function getPlatform(): 'darwin' | 'win32' | 'linux' {\r\n return platform() as 'darwin' | 'win32' | 'linux';\r\n}\r\n\r\n/** Get Windows APPDATA path */\r\nexport function getAppData(): string {\r\n return process.env.APPDATA || join(homedir(), 'AppData', 'Roaming');\r\n}\r\n\r\n/** Get Windows LOCALAPPDATA path */\r\nexport function getLocalAppData(): string {\r\n return (\r\n process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local')\r\n );\r\n}\r\n\r\n/**\r\n * Parse an MCP config file (JSON with mcpServers key).\r\n * Returns empty array if file doesn't exist or is invalid.\r\n */\r\nexport async function parseMCPConfig(\r\n configPath: string,\r\n source: MCPClient\r\n): Promise<MCPServerEntry[]> {\r\n if (!existsSync(configPath)) {\r\n return [];\r\n }\r\n\r\n try {\r\n const raw = await readFile(configPath, 'utf-8');\r\n const config = JSON.parse(raw);\r\n\r\n // Handle { mcpServers: {...} }, { servers: {...} } (VS Code), and direct configs\r\n const servers = config.mcpServers || config.servers || config;\r\n\r\n if (typeof servers !== 'object' || servers === null) {\r\n return [];\r\n }\r\n\r\n const entries: MCPServerEntry[] = [];\r\n\r\n for (const [name, serverConfig] of Object.entries(servers)) {\r\n const sc = serverConfig as Record<string, unknown>;\r\n\r\n // Skip if it doesn't look like a server config\r\n if (!sc.command && !sc.url) continue;\r\n\r\n entries.push({\r\n name,\r\n source,\r\n command: (sc.command as string) || '',\r\n args: Array.isArray(sc.args) ? (sc.args as string[]) : [],\r\n env: sc.env as Record<string, string> | undefined,\r\n configPath,\r\n });\r\n }\r\n\r\n return entries;\r\n } catch {\r\n // Invalid JSON, file read error, etc.\r\n return [];\r\n }\r\n}\r\n\r\n/**\r\n * Try multiple possible config paths and return all servers found.\r\n */\r\nexport async function tryConfigPaths(\r\n paths: string[],\r\n source: MCPClient\r\n): Promise<MCPServerEntry[]> {\r\n const allServers: MCPServerEntry[] = [];\r\n\r\n for (const path of paths) {\r\n const servers = await parseMCPConfig(path, source);\r\n allServers.push(...servers);\r\n }\r\n\r\n return allServers;\r\n}\r\n","// ============================================================\r\n// Cursor — MCP Config Discovery\r\n// ============================================================\r\n// Config locations:\r\n// Global: ~/.cursor/mcp.json\r\n// Project: .cursor/mcp.json (in cwd)\r\n\r\nimport { join } from 'path';\r\nimport { getHome, tryConfigPaths } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverCursor(): Promise<MCPServerEntry[]> {\r\n const home = getHome();\r\n\r\n const paths = [\r\n join(home, '.cursor', 'mcp.json'),\r\n join(process.cwd(), '.cursor', 'mcp.json'),\r\n ];\r\n\r\n return tryConfigPaths(paths, 'cursor');\r\n}\r\n","// ============================================================\r\n// Claude Code — MCP Config Discovery\r\n// ============================================================\r\n// Config locations:\r\n// Global: ~/.claude.json (mcpServers key)\r\n// Project: .mcp.json (in cwd)\r\n// Plugins: ~/.claude/plugins/cache/claude-plugins-official/<name>/<version>/.mcp.json\r\n\r\nimport { join } from 'path';\r\nimport { readdirSync, existsSync } from 'fs';\r\nimport { getHome, tryConfigPaths, parseMCPConfig } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverClaudeCode(): Promise<MCPServerEntry[]> {\r\n const home = getHome();\r\n const allServers: MCPServerEntry[] = [];\r\n\r\n // 1. Traditional config paths (global + project-local)\r\n const traditionalPaths = [\r\n join(home, '.claude.json'),\r\n join(process.cwd(), '.mcp.json'),\r\n ];\r\n allServers.push(...await tryConfigPaths(traditionalPaths, 'claude-code'));\r\n\r\n // 2. Claude Code plugin cache — each plugin can bundle its own .mcp.json\r\n const pluginCacheDir = join(home, '.claude', 'plugins', 'cache', 'claude-plugins-official');\r\n if (existsSync(pluginCacheDir)) {\r\n allServers.push(...await discoverPluginMCPConfigs(pluginCacheDir));\r\n }\r\n\r\n return allServers;\r\n}\r\n\r\n/**\r\n * Scan the Claude Code plugin cache for .mcp.json files.\r\n * Structure: ~/.claude/plugins/cache/claude-plugins-official/<plugin>/<version>/.mcp.json\r\n * Some plugins nest deeper (e.g., semgrep/<hash>/plugin/.mcp.json).\r\n */\r\nasync function discoverPluginMCPConfigs(cacheDir: string): Promise<MCPServerEntry[]> {\r\n const servers: MCPServerEntry[] = [];\r\n const seen = new Set<string>();\r\n\r\n let pluginDirs: ReturnType<typeof readdirSync>;\r\n try {\r\n pluginDirs = readdirSync(cacheDir, { withFileTypes: true });\r\n } catch {\r\n return [];\r\n }\r\n\r\n for (const pluginEntry of pluginDirs) {\r\n if (!pluginEntry.isDirectory()) continue;\r\n\r\n const pluginDir = join(cacheDir, pluginEntry.name);\r\n\r\n let versionDirs: ReturnType<typeof readdirSync>;\r\n try {\r\n versionDirs = readdirSync(pluginDir, { withFileTypes: true });\r\n } catch {\r\n continue;\r\n }\r\n\r\n for (const versionEntry of versionDirs) {\r\n if (!versionEntry.isDirectory()) continue;\r\n\r\n const versionDir = join(pluginDir, versionEntry.name);\r\n\r\n // Collect candidate .mcp.json paths: direct + one level deeper\r\n const candidates = [join(versionDir, '.mcp.json')];\r\n\r\n try {\r\n for (const sub of readdirSync(versionDir, { withFileTypes: true })) {\r\n if (sub.isDirectory()) {\r\n candidates.push(join(versionDir, sub.name, '.mcp.json'));\r\n }\r\n }\r\n } catch { /* ignore read errors on subdirs */ }\r\n\r\n for (const candidate of candidates) {\r\n const found = await parseMCPConfig(candidate, 'claude-code');\r\n for (const server of found) {\r\n // Deduplicate: same server name from different cached versions\r\n if (!seen.has(server.name)) {\r\n seen.add(server.name);\r\n servers.push(server);\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n return servers;\r\n}\r\n","// ============================================================\r\n// Windsurf — MCP Config Discovery\r\n// ============================================================\r\n// Config locations:\r\n// macOS/Linux: ~/.codeium/windsurf/mcp_config.json\r\n// Windows: %USERPROFILE%\\.codeium\\windsurf\\mcp_config.json\r\n\r\nimport { join } from 'path';\r\nimport { getHome, tryConfigPaths } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverWindsurf(): Promise<MCPServerEntry[]> {\r\n const home = getHome();\r\n\r\n const paths = [\r\n join(home, '.codeium', 'windsurf', 'mcp_config.json'),\r\n ];\r\n\r\n return tryConfigPaths(paths, 'windsurf');\r\n}\r\n","// ============================================================\r\n// VS Code — MCP Config Discovery\r\n// ============================================================\r\n// Config locations:\r\n// Project: .vscode/mcp.json (in cwd)\r\n\r\nimport { join } from 'path';\r\nimport { tryConfigPaths } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverVSCode(): Promise<MCPServerEntry[]> {\r\n const paths = [\r\n join(process.cwd(), '.vscode', 'mcp.json'),\r\n ];\r\n\r\n return tryConfigPaths(paths, 'vscode');\r\n}\r\n","// ============================================================\r\n// OpenClaw — MCP Config Discovery\r\n// ============================================================\r\n// Config locations:\r\n// All platforms: ~/.openclaw/openclaw.json (mcpServers key)\r\n// Project-local: openclaw.config.json (in cwd)\r\n//\r\n// OpenClaw also supports agent-level MCP configs:\r\n// { agents: { list: [{ id: \"main\", mcp: { servers: [...] } }] } }\r\n//\r\n// Note: OpenClaw uses JSON5 format (comments, trailing commas).\r\n// Our parser uses standard JSON.parse — configs with JSON5-only\r\n// features will be skipped gracefully.\r\n\r\nimport { join } from 'path';\r\nimport { readFile } from 'fs/promises';\r\nimport { existsSync } from 'fs';\r\nimport { getHome, tryConfigPaths } from './utils.js';\r\nimport type { MCPServerEntry } from '../types/index.js';\r\n\r\nexport async function discoverOpenClaw(): Promise<MCPServerEntry[]> {\r\n const home = getHome();\r\n const allServers: MCPServerEntry[] = [];\r\n const seen = new Set<string>();\r\n\r\n // 1. Standard mcpServers block (top-level, same format as Claude Desktop)\r\n const configPaths = [\r\n join(home, '.openclaw', 'openclaw.json'),\r\n join(process.cwd(), 'openclaw.config.json'),\r\n ];\r\n for (const server of await tryConfigPaths(configPaths, 'openclaw')) {\r\n if (!seen.has(server.name)) {\r\n seen.add(server.name);\r\n allServers.push(server);\r\n }\r\n }\r\n\r\n // 2. Agent-level MCP configs (nested under agents.list[].mcp.servers)\r\n const globalConfig = join(home, '.openclaw', 'openclaw.json');\r\n if (existsSync(globalConfig)) {\r\n for (const server of await parseAgentMCPConfigs(globalConfig)) {\r\n if (!seen.has(server.name)) {\r\n seen.add(server.name);\r\n allServers.push(server);\r\n }\r\n }\r\n }\r\n\r\n return allServers;\r\n}\r\n\r\n/**\r\n * Parse agent-level MCP server configs from OpenClaw config.\r\n * Structure: { agents: { list: [{ id: \"main\", mcp: { servers: [...] } }] } }\r\n *\r\n * Agent-level servers use an array format with name/command/args properties,\r\n * unlike the top-level mcpServers which uses an object keyed by server name.\r\n */\r\nasync function parseAgentMCPConfigs(configPath: string): Promise<MCPServerEntry[]> {\r\n try {\r\n const raw = await readFile(configPath, 'utf-8');\r\n const config = JSON.parse(raw);\r\n\r\n const agents = config?.agents?.list;\r\n if (!Array.isArray(agents)) return [];\r\n\r\n const servers: MCPServerEntry[] = [];\r\n\r\n for (const agent of agents) {\r\n const mcpServers = agent?.mcp?.servers;\r\n if (!Array.isArray(mcpServers)) continue;\r\n\r\n for (const server of mcpServers) {\r\n if (!server.name || (!server.command && !server.url)) continue;\r\n\r\n servers.push({\r\n name: server.name,\r\n source: 'openclaw',\r\n command: server.command || '',\r\n args: Array.isArray(server.args) ? server.args : [],\r\n env: server.env,\r\n configPath,\r\n });\r\n }\r\n }\r\n\r\n return servers;\r\n } catch {\r\n return [];\r\n }\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Skill File Discovery\r\n// ============================================================\r\n// Discovers agent skill files (SKILL.md, .mdc rules, CLAUDE.md,\r\n// SOUL.md, MEMORY.md) across all supported AI tools.\r\n//\r\n// Scan locations:\r\n// Claude Code: .claude/skills/*/SKILL.md (project)\r\n// ~/.claude/skills/*/SKILL.md (global)\r\n// GitHub Copilot: .github/skills/*/SKILL.md (project)\r\n// Cursor: .cursor/rules/*.mdc (project)\r\n// ~/.cursor/rules/*.mdc (global)\r\n// Memory Files: CLAUDE.md, SOUL.md, MEMORY.md (project root)\r\n// ~/.claude/CLAUDE.md (global)\r\n\r\nimport { readFile, stat } from 'fs/promises';\r\nimport { existsSync } from 'fs';\r\nimport { join, basename, dirname } from 'path';\r\nimport { glob } from 'glob';\r\nimport { getHome } from './utils.js';\r\nimport type {\r\n SkillEntry,\r\n SkillSource,\r\n SkillFileType,\r\n SkillDiscoveryResult,\r\n} from '../types/index.js';\r\n\r\n/**\r\n * Discover all agent skill files on this machine.\r\n */\r\nexport async function discoverAllSkills(): Promise<SkillDiscoveryResult> {\r\n const skills: SkillEntry[] = [];\r\n const errors: Array<{ source: SkillSource; error: string }> = [];\r\n let locationsChecked = 0;\r\n let locationsFound = 0;\r\n\r\n const discoverers: Array<{\r\n source: SkillSource;\r\n fn: () => Promise<SkillEntry[]>;\r\n }> = [\r\n { source: 'claude-code', fn: discoverClaudeCodeSkills },\r\n { source: 'github-copilot', fn: discoverGitHubCopilotSkills },\r\n { source: 'cursor', fn: discoverCursorRules },\r\n { source: 'memory-file', fn: discoverMemoryFiles },\r\n ];\r\n\r\n for (const { source, fn } of discoverers) {\r\n locationsChecked++;\r\n try {\r\n const found = await fn();\r\n if (found.length > 0) {\r\n locationsFound++;\r\n skills.push(...found);\r\n }\r\n } catch (err) {\r\n errors.push({\r\n source,\r\n error: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n }\r\n\r\n return { skills, locationsChecked, locationsFound, errors };\r\n}\r\n\r\n/**\r\n * Discover Claude Code skill files.\r\n * Project: .claude/skills/* /SKILL.md\r\n * Global: ~/.claude/skills/* /SKILL.md\r\n */\r\nasync function discoverClaudeCodeSkills(): Promise<SkillEntry[]> {\r\n const home = getHome();\r\n const skills: SkillEntry[] = [];\r\n\r\n // Project-local skills\r\n const projectPatterns = [\r\n join(process.cwd(), '.claude', 'skills', '*', 'SKILL.md'),\r\n join(process.cwd(), '.claude', 'commands', '**', '*.md'),\r\n ];\r\n\r\n for (const pattern of projectPatterns) {\r\n const files = await glob(pattern, { absolute: true });\r\n for (const filePath of files) {\r\n const entry = await readSkillFile(filePath, 'claude-code', 'skill.md', 'project');\r\n if (entry) skills.push(entry);\r\n }\r\n }\r\n\r\n // Global skills\r\n const globalPatterns = [\r\n join(home, '.claude', 'skills', '*', 'SKILL.md'),\r\n join(home, '.claude', 'commands', '**', '*.md'),\r\n ];\r\n\r\n for (const pattern of globalPatterns) {\r\n const files = await glob(pattern, { absolute: true });\r\n for (const filePath of files) {\r\n const entry = await readSkillFile(filePath, 'claude-code', 'skill.md', 'global');\r\n if (entry) skills.push(entry);\r\n }\r\n }\r\n\r\n return skills;\r\n}\r\n\r\n/**\r\n * Discover GitHub Copilot skill files.\r\n * Project: .github/skills/* /SKILL.md\r\n */\r\nasync function discoverGitHubCopilotSkills(): Promise<SkillEntry[]> {\r\n const skills: SkillEntry[] = [];\r\n\r\n const patterns = [\r\n join(process.cwd(), '.github', 'skills', '*', 'SKILL.md'),\r\n join(process.cwd(), '.github', 'copilot', '**', '*.md'),\r\n ];\r\n\r\n for (const pattern of patterns) {\r\n const files = await glob(pattern, { absolute: true });\r\n for (const filePath of files) {\r\n const entry = await readSkillFile(filePath, 'github-copilot', 'skill.md', 'project');\r\n if (entry) skills.push(entry);\r\n }\r\n }\r\n\r\n return skills;\r\n}\r\n\r\n/**\r\n * Discover Cursor .mdc rule files.\r\n * Project: .cursor/rules/*.mdc\r\n * Global: ~/.cursor/rules/*.mdc\r\n */\r\nasync function discoverCursorRules(): Promise<SkillEntry[]> {\r\n const home = getHome();\r\n const skills: SkillEntry[] = [];\r\n\r\n // Project-local rules\r\n const projectPattern = join(process.cwd(), '.cursor', 'rules', '*.mdc');\r\n const projectFiles = await glob(projectPattern, { absolute: true });\r\n for (const filePath of projectFiles) {\r\n const entry = await readSkillFile(filePath, 'cursor', 'mdc-rule', 'project');\r\n if (entry) skills.push(entry);\r\n }\r\n\r\n // Also check .cursorrules (legacy single file)\r\n const legacyPath = join(process.cwd(), '.cursorrules');\r\n if (existsSync(legacyPath)) {\r\n const entry = await readSkillFile(legacyPath, 'cursor', 'mdc-rule', 'project');\r\n if (entry) skills.push(entry);\r\n }\r\n\r\n // Global rules\r\n const globalPattern = join(home, '.cursor', 'rules', '*.mdc');\r\n const globalFiles = await glob(globalPattern, { absolute: true });\r\n for (const filePath of globalFiles) {\r\n const entry = await readSkillFile(filePath, 'cursor', 'mdc-rule', 'global');\r\n if (entry) skills.push(entry);\r\n }\r\n\r\n return skills;\r\n}\r\n\r\n/**\r\n * Discover memory/instruction files at project root and global locations.\r\n * CLAUDE.md, SOUL.md, MEMORY.md\r\n */\r\nasync function discoverMemoryFiles(): Promise<SkillEntry[]> {\r\n const home = getHome();\r\n const skills: SkillEntry[] = [];\r\n const cwd = process.cwd();\r\n\r\n const memoryFiles: Array<{\r\n path: string;\r\n fileType: SkillFileType;\r\n scope: 'project' | 'global';\r\n }> = [\r\n // Project-level memory files\r\n { path: join(cwd, 'CLAUDE.md'), fileType: 'claude.md', scope: 'project' },\r\n { path: join(cwd, '.claude', 'CLAUDE.md'), fileType: 'claude.md', scope: 'project' },\r\n { path: join(cwd, 'SOUL.md'), fileType: 'soul.md', scope: 'project' },\r\n { path: join(cwd, 'MEMORY.md'), fileType: 'memory.md', scope: 'project' },\r\n // Additional project instruction files\r\n { path: join(cwd, '.github', 'copilot-instructions.md'), fileType: 'claude.md', scope: 'project' },\r\n { path: join(cwd, 'AGENTS.md'), fileType: 'claude.md', scope: 'project' },\r\n { path: join(cwd, '.windsurfrules'), fileType: 'claude.md', scope: 'project' },\r\n { path: join(cwd, 'GEMINI.md'), fileType: 'claude.md', scope: 'project' },\r\n // Global memory files\r\n { path: join(home, '.claude', 'CLAUDE.md'), fileType: 'claude.md', scope: 'global' },\r\n { path: join(home, 'CLAUDE.md'), fileType: 'claude.md', scope: 'global' },\r\n ];\r\n\r\n for (const { path, fileType, scope } of memoryFiles) {\r\n if (existsSync(path)) {\r\n const entry = await readSkillFile(path, 'memory-file', fileType, scope);\r\n if (entry) skills.push(entry);\r\n }\r\n }\r\n\r\n return skills;\r\n}\r\n\r\n/**\r\n * Read a skill file and return a SkillEntry.\r\n */\r\nasync function readSkillFile(\r\n filePath: string,\r\n source: SkillSource,\r\n fileType: SkillFileType,\r\n scope: 'project' | 'global'\r\n): Promise<SkillEntry | null> {\r\n try {\r\n const content = await readFile(filePath, 'utf-8');\r\n const fileStat = await stat(filePath);\r\n\r\n // Derive skill name from context\r\n const name = deriveSkillName(filePath, fileType);\r\n\r\n return {\r\n name,\r\n source,\r\n fileType,\r\n filePath,\r\n content,\r\n size: fileStat.size,\r\n scope,\r\n };\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Derive a human-readable skill name from a file path.\r\n */\r\nfunction deriveSkillName(filePath: string, fileType: SkillFileType): string {\r\n switch (fileType) {\r\n case 'skill.md': {\r\n // .claude/skills/my-skill/SKILL.md → \"my-skill\"\r\n const parentDir = basename(dirname(filePath));\r\n return parentDir === 'skills' ? basename(filePath, '.md') : parentDir;\r\n }\r\n case 'mdc-rule': {\r\n // .cursor/rules/my-rule.mdc → \"my-rule\"\r\n const name = basename(filePath);\r\n return name.replace(/\\.(mdc|cursorrules?)$/, '') || name;\r\n }\r\n case 'claude.md':\r\n return 'CLAUDE.md';\r\n case 'soul.md':\r\n return 'SOUL.md';\r\n case 'memory.md':\r\n return 'MEMORY.md';\r\n default:\r\n return basename(filePath);\r\n }\r\n}\r\n","// ============================================================\r\n// Vigile CLI — MCP Config Discovery Orchestrator\r\n// ============================================================\r\n// Auto-discovers MCP server configurations across all supported\r\n// AI tools on the user's machine. Supports macOS, Windows, Linux.\r\n\r\nimport { discoverClaudeDesktop } from './claude-desktop.js';\r\nimport { discoverCursor } from './cursor.js';\r\nimport { discoverClaudeCode } from './claude-code.js';\r\nimport { discoverWindsurf } from './windsurf.js';\r\nimport { discoverVSCode } from './vscode.js';\r\nimport { discoverOpenClaw } from './openclaw.js';\r\nimport type { MCPServerEntry, MCPClient } from '../types/index.js';\r\n\r\n// Re-export skill discovery for use in CLI\r\nexport { discoverAllSkills } from './skills.js';\r\n\r\nexport interface DiscoveryResult {\r\n servers: MCPServerEntry[];\r\n configsChecked: number;\r\n configsFound: number;\r\n errors: Array<{ client: MCPClient; error: string }>;\r\n}\r\n\r\n/**\r\n * Discover all MCP server configurations on this machine.\r\n * Checks all known IDE/tool config locations for the current OS.\r\n */\r\nexport async function discoverAllServers(\r\n clientFilter?: MCPClient\r\n): Promise<DiscoveryResult> {\r\n const discoverers: Array<{\r\n client: MCPClient;\r\n fn: () => Promise<MCPServerEntry[]>;\r\n }> = [\r\n { client: 'claude-desktop', fn: discoverClaudeDesktop },\r\n { client: 'cursor', fn: discoverCursor },\r\n { client: 'claude-code', fn: discoverClaudeCode },\r\n { client: 'windsurf', fn: discoverWindsurf },\r\n { client: 'vscode', fn: discoverVSCode },\r\n { client: 'openclaw', fn: discoverOpenClaw },\r\n ];\r\n\r\n // Filter to specific client if requested\r\n const toRun = clientFilter\r\n ? discoverers.filter((d) => d.client === clientFilter)\r\n : discoverers;\r\n\r\n const servers: MCPServerEntry[] = [];\r\n const errors: Array<{ client: MCPClient; error: string }> = [];\r\n let configsFound = 0;\r\n\r\n for (const { client, fn } of toRun) {\r\n try {\r\n const found = await fn();\r\n if (found.length > 0) {\r\n configsFound++;\r\n servers.push(...found);\r\n }\r\n } catch (err) {\r\n errors.push({\r\n client,\r\n error: err instanceof Error ? err.message : String(err),\r\n });\r\n }\r\n }\r\n\r\n return {\r\n servers,\r\n configsChecked: toRun.length,\r\n configsFound,\r\n errors,\r\n };\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Detection Patterns\r\n// ============================================================\r\n// These patterns detect known attack vectors in MCP server\r\n// tool descriptions and configurations. Based on real-world\r\n// attacks documented by Invariant Labs, OWASP MCP Top 10,\r\n// and the broader AI security research community.\r\n\r\nimport type { Severity, FindingCategory } from '../types/index.js';\r\n\r\nexport interface DetectionPattern {\r\n id: string;\r\n category: FindingCategory;\r\n severity: Severity;\r\n title: string;\r\n /** Regex pattern to match against */\r\n pattern: RegExp;\r\n /** Human-readable description */\r\n description: string;\r\n /** What the user should do */\r\n recommendation: string;\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// TOOL POISONING PATTERNS\r\n// Detect hidden instructions embedded in tool descriptions\r\n// that manipulate the AI agent's behavior.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const TOOL_POISONING_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'TP-001',\r\n category: 'tool-poisoning',\r\n severity: 'critical',\r\n title: 'Prompt override instruction detected',\r\n pattern: /ignore\\s+(all\\s+)?previous\\s+instructions/i,\r\n description:\r\n 'Tool description contains instructions to override the AI agent\\'s system prompt. This is a classic prompt injection attack.',\r\n recommendation:\r\n 'Do NOT install this MCP server. This is a known attack pattern.',\r\n },\r\n {\r\n id: 'TP-002',\r\n category: 'tool-poisoning',\r\n severity: 'critical',\r\n title: 'Hidden agent manipulation',\r\n pattern: /do\\s+not\\s+tell\\s+the\\s+user/i,\r\n description:\r\n 'Tool description instructs the AI agent to hide information from the user — a hallmark of tool poisoning.',\r\n recommendation:\r\n 'Do NOT install this MCP server. Legitimate tools never instruct agents to hide actions.',\r\n },\r\n // TP-003 removed: \"you are a/an [role]\" is too broad — matches legitimate\r\n // role descriptions in skills (e.g., \"You are a computer science professor\").\r\n // SK-001 covers the real threat (malicious persona hijacking) with proper\r\n // specificity by requiring suspicious role terms like \"hacker\" or \"jailbroken\".\r\n {\r\n id: 'TP-004',\r\n category: 'tool-poisoning',\r\n severity: 'high',\r\n title: 'Cross-tool instruction injection',\r\n pattern: /(before|after|instead\\s+of)\\s+(using|calling|invoking)\\s+(this|any|other|the)\\s+tool/i,\r\n description:\r\n 'Tool description tries to influence how other tools are called — a cross-origin escalation pattern.',\r\n recommendation:\r\n 'Review carefully. This tool may be trying to shadow or intercept other tool calls.',\r\n },\r\n {\r\n id: 'TP-005',\r\n category: 'tool-poisoning',\r\n severity: 'high',\r\n title: 'Instruction to call specific tool',\r\n pattern: /(always|must|should)\\s+(first\\s+)?(call|use|invoke|run)\\s+[\\w-]+\\s+(tool|function|command)/i,\r\n description:\r\n 'Tool description mandates calling a specific other tool, which could be used to chain attacks.',\r\n recommendation:\r\n 'Verify that the referenced tool is legitimate and necessary.',\r\n },\r\n {\r\n id: 'TP-006',\r\n category: 'tool-poisoning',\r\n severity: 'high',\r\n title: 'Hidden text block detected',\r\n pattern: /\\n{5,}.*\\n{5,}/s,\r\n description:\r\n 'Tool description contains large blocks of whitespace that may hide instructions from casual review.',\r\n recommendation:\r\n 'Inspect the full tool description carefully for hidden content.',\r\n },\r\n {\r\n id: 'TP-007',\r\n category: 'tool-poisoning',\r\n severity: 'medium',\r\n title: 'System prompt reference',\r\n pattern: /system\\s*prompt|system\\s*message|system\\s*instruction/i,\r\n description:\r\n 'Tool description references system prompts, which may indicate an attempt to manipulate agent behavior.',\r\n recommendation:\r\n 'Review the context. Legitimate tools rarely reference system prompts.',\r\n },\r\n {\r\n id: 'TP-008',\r\n category: 'tool-poisoning',\r\n severity: 'medium',\r\n title: 'Instruction to keep secrets',\r\n pattern: /(keep|this\\s+is)\\s+(a\\s+)?secret|don'?t\\s+(mention|reveal|disclose|share)/i,\r\n description:\r\n 'Tool description instructs the agent to keep information secret from the user.',\r\n recommendation:\r\n 'Legitimate tools don\\'t ask agents to hide information. Review carefully.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// DATA EXFILTRATION PATTERNS\r\n// Detect attempts to steal sensitive data through tool\r\n// descriptions that reference credential files, env vars,\r\n// or suspicious external URLs.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const EXFILTRATION_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'EX-001',\r\n category: 'data-exfiltration',\r\n severity: 'critical',\r\n title: 'SSH key access pattern',\r\n pattern: /\\.ssh\\/(id_rsa|id_ed25519|id_ecdsa|authorized_keys|known_hosts|config)/i,\r\n description:\r\n 'Tool references SSH key files. This matches the Invariant Labs attack where SSH keys were exfiltrated from Claude Desktop.',\r\n recommendation:\r\n 'CRITICAL: Remove immediately. No legitimate MCP tool needs access to SSH keys.',\r\n },\r\n {\r\n id: 'EX-002',\r\n category: 'data-exfiltration',\r\n severity: 'critical',\r\n title: 'AWS credential access',\r\n pattern: /\\.aws\\/(credentials|config)|AWS_SECRET_ACCESS_KEY|AWS_ACCESS_KEY_ID/i,\r\n description:\r\n 'Tool references AWS credential files or environment variables.',\r\n recommendation:\r\n 'Remove immediately unless this is a verified AWS management tool.',\r\n },\r\n {\r\n id: 'EX-003',\r\n category: 'data-exfiltration',\r\n severity: 'critical',\r\n title: 'Environment file access',\r\n pattern: /\\.(env|env\\.local|env\\.production|env\\.development)\\b/i,\r\n description:\r\n 'Tool references .env files which typically contain API keys and secrets.',\r\n recommendation:\r\n 'Review why this tool needs access to environment files.',\r\n },\r\n {\r\n id: 'EX-004',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'Credential file access pattern',\r\n pattern: /(credentials|secrets|tokens|passwords|api[_-]?keys)\\.(json|yaml|yml|txt|cfg|ini|conf)/i,\r\n description:\r\n 'Tool references files commonly used to store credentials.',\r\n recommendation:\r\n 'Verify this tool has a legitimate reason to access credential files.',\r\n },\r\n {\r\n id: 'EX-005',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'Suspicious external URL',\r\n pattern: /https?:\\/\\/(?!(?:github\\.com|npmjs\\.com|registry\\.npmjs\\.org|pypi\\.org|api\\.github\\.com))[a-z0-9][-a-z0-9]*\\.[a-z]{2,}\\/(collect|track|log|beacon|webhook|exfil|receive|data|upload|report)/i,\r\n description:\r\n 'Tool description contains a URL pointing to a data collection endpoint.',\r\n recommendation:\r\n 'Investigate the URL. This may be a data exfiltration endpoint.',\r\n },\r\n {\r\n id: 'EX-006',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'Cryptocurrency wallet access',\r\n pattern: /(\\.bitcoin|\\.ethereum|wallet\\.dat|\\.solana|seed\\s*phrase|private\\s*key|keystore)/i,\r\n description:\r\n 'Tool references cryptocurrency wallet files or seed phrases. Matches the malicious OpenClaw skills pattern.',\r\n recommendation:\r\n 'CRITICAL: Remove immediately unless this is a verified crypto tool.',\r\n },\r\n {\r\n id: 'EX-007',\r\n category: 'data-exfiltration',\r\n severity: 'medium',\r\n title: 'Browser data access',\r\n pattern: /(cookies|local\\s*storage|session\\s*storage|browser\\s*history|bookmarks|saved\\s*passwords)/i,\r\n description:\r\n 'Tool references browser data stores.',\r\n recommendation:\r\n 'Review why this tool needs access to browser data.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// PERMISSION ABUSE PATTERNS\r\n// Detect tools requesting excessive or suspicious permissions.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const PERMISSION_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'PM-001',\r\n category: 'permission-abuse',\r\n severity: 'high',\r\n title: 'Code execution capability',\r\n pattern: /\\b(eval|exec|spawn|child_process|subprocess|os\\.system|os\\.popen)\\b/i,\r\n description:\r\n 'Tool has code execution capabilities which could be used to run arbitrary commands.',\r\n recommendation:\r\n 'Ensure this tool\\'s code execution is properly sandboxed.',\r\n },\r\n {\r\n id: 'PM-002',\r\n category: 'permission-abuse',\r\n severity: 'high',\r\n title: 'Unrestricted filesystem access',\r\n pattern: /\\b(readFile|writeFile|readdir|rmdir|unlink|fs\\.|filesystem|file\\s*system)\\b.*\\b(any|all|entire|root|\\/)\\b/i,\r\n description:\r\n 'Tool claims unrestricted filesystem access.',\r\n recommendation:\r\n 'Tools should have scoped filesystem access, not unrestricted.',\r\n },\r\n {\r\n id: 'PM-003',\r\n category: 'permission-abuse',\r\n severity: 'medium',\r\n title: 'Network request capability',\r\n pattern: /\\b(fetch|axios|http\\.request|urllib|requests\\.get|requests\\.post|curl|wget)\\b/i,\r\n description:\r\n 'Tool makes network requests. Legitimate in many cases, but could be used for data exfiltration.',\r\n recommendation:\r\n 'Verify the tool\\'s network targets are expected and safe.',\r\n },\r\n {\r\n id: 'PM-004',\r\n category: 'permission-abuse',\r\n severity: 'medium',\r\n title: 'Sensitive path access',\r\n pattern: /\\/etc\\/(passwd|shadow|hosts|sudoers)|\\/root\\/|~\\/\\./,\r\n description:\r\n 'Tool accesses system-sensitive paths.',\r\n recommendation:\r\n 'Review why this tool needs access to system files.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// OBFUSCATION PATTERNS\r\n// Detect attempts to hide malicious content through encoding\r\n// or unicode tricks.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const OBFUSCATION_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'OB-001',\r\n category: 'obfuscation',\r\n severity: 'high',\r\n title: 'Base64 encoded content',\r\n pattern: /[A-Za-z0-9+/]{40,}={0,2}/,\r\n description:\r\n 'Tool description contains what appears to be base64-encoded content, which may hide malicious instructions.',\r\n recommendation:\r\n 'Decode the base64 content and inspect it before using this tool.',\r\n },\r\n {\r\n id: 'OB-002',\r\n category: 'obfuscation',\r\n severity: 'high',\r\n title: 'Zero-width characters detected',\r\n pattern: /[\\u200B\\u200C\\u200D\\uFEFF\\u2060\\u2061\\u2062\\u2063\\u2064]/,\r\n description:\r\n 'Tool description contains invisible zero-width Unicode characters that may hide content.',\r\n recommendation:\r\n 'Strip zero-width characters and inspect the resulting text.',\r\n },\r\n {\r\n id: 'OB-003',\r\n category: 'obfuscation',\r\n severity: 'medium',\r\n title: 'Hex-encoded string',\r\n pattern: /\\\\x[0-9a-fA-F]{2}(\\\\x[0-9a-fA-F]{2}){4,}/,\r\n description:\r\n 'Tool description contains hex-encoded strings that may hide instructions.',\r\n recommendation:\r\n 'Decode the hex content and inspect it.',\r\n },\r\n {\r\n id: 'OB-004',\r\n category: 'obfuscation',\r\n severity: 'medium',\r\n title: 'Unicode escape sequences',\r\n pattern: /\\\\u[0-9a-fA-F]{4}(\\\\u[0-9a-fA-F]{4}){4,}/,\r\n description:\r\n 'Tool description contains Unicode escape sequences that may hide content.',\r\n recommendation:\r\n 'Decode the Unicode escapes and inspect the resulting text.',\r\n },\r\n];\r\n\r\n/** All detection patterns combined */\r\nexport const ALL_PATTERNS: DetectionPattern[] = [\r\n ...TOOL_POISONING_PATTERNS,\r\n ...EXFILTRATION_PATTERNS,\r\n ...PERMISSION_PATTERNS,\r\n ...OBFUSCATION_PATTERNS,\r\n];\r\n","// ============================================================\r\n// Vigile CLI — Trust Score Calculator\r\n// ============================================================\r\n// Calculates a 0-100 trust score based on weighted factors.\r\n// For CLI v1, we focus on what we can analyze locally:\r\n// - Code Analysis (30%): Findings from pattern scanning\r\n// - Dependency Health (20%): Package source and age signals\r\n// - Permission Safety (20%): How much access the tool requests\r\n// - Behavioral Stability (15%): Command stability signals\r\n// - Transparency (15%): Open source, known package, etc.\r\n\r\nimport type { Finding, MCPServerEntry, ScoreBreakdown, Severity } from '../types/index.js';\r\n\r\ninterface ScoreResult {\r\n score: number;\r\n breakdown: ScoreBreakdown;\r\n}\r\n\r\n/** Severity weights for score deductions */\r\nconst SEVERITY_DEDUCTIONS: Record<Severity, number> = {\r\n critical: 35,\r\n high: 20,\r\n medium: 10,\r\n low: 5,\r\n info: 0,\r\n};\r\n\r\n/**\r\n * Calculate trust score for an MCP server based on scan findings.\r\n */\r\nexport function calculateTrustScore(\r\n findings: Finding[],\r\n server: MCPServerEntry\r\n): ScoreResult {\r\n // Start each factor at 100 and deduct based on findings\r\n let codeAnalysis = 100;\r\n let dependencyHealth = 100;\r\n let permissionSafety = 100;\r\n let behavioralStability = 100;\r\n let transparency = 100;\r\n\r\n // ── Deduct from Code Analysis based on poisoning/obfuscation findings ──\r\n for (const f of findings) {\r\n if (f.category === 'tool-poisoning' || f.category === 'obfuscation') {\r\n codeAnalysis -= SEVERITY_DEDUCTIONS[f.severity];\r\n }\r\n }\r\n codeAnalysis = Math.max(0, codeAnalysis);\r\n\r\n // ── Deduct from Dependency Health based on dependency findings ──\r\n for (const f of findings) {\r\n if (f.category === 'dependency-risk') {\r\n dependencyHealth -= SEVERITY_DEDUCTIONS[f.severity];\r\n }\r\n }\r\n\r\n // Boost dependency health for well-known package sources\r\n if (isWellKnownPackage(server)) {\r\n dependencyHealth = Math.min(100, dependencyHealth + 15);\r\n }\r\n dependencyHealth = Math.max(0, dependencyHealth);\r\n\r\n // ── Deduct from Permission Safety based on permission/exfil findings ──\r\n for (const f of findings) {\r\n if (\r\n f.category === 'permission-abuse' ||\r\n f.category === 'data-exfiltration'\r\n ) {\r\n permissionSafety -= SEVERITY_DEDUCTIONS[f.severity];\r\n }\r\n }\r\n permissionSafety = Math.max(0, permissionSafety);\r\n\r\n // ── Behavioral Stability ──\r\n // In CLI v1, we use command structure as a proxy\r\n // npx/uvx commands are less stable (can change), local paths are more stable\r\n if (server.command === 'npx' || server.command === 'uvx') {\r\n behavioralStability -= 10; // Slight deduction for remote packages\r\n }\r\n if (server.command === 'node' || server.command === 'python' || server.command === 'python3') {\r\n behavioralStability += 5; // Local execution is more predictable\r\n }\r\n behavioralStability = Math.max(0, Math.min(100, behavioralStability));\r\n\r\n // ── Transparency ──\r\n // Check for signals of transparency\r\n if (isOpenSourcePackage(server)) {\r\n transparency = Math.min(100, transparency + 10);\r\n }\r\n // Deduct for unknown/untraceable sources\r\n if (!isWellKnownPackage(server) && server.command !== 'node' && server.command !== 'python') {\r\n transparency -= 20;\r\n }\r\n transparency = Math.max(0, Math.min(100, transparency));\r\n\r\n // ── Calculate weighted score ──\r\n const breakdown: ScoreBreakdown = {\r\n codeAnalysis,\r\n dependencyHealth,\r\n permissionSafety,\r\n behavioralStability,\r\n transparency,\r\n };\r\n\r\n const score = Math.round(\r\n codeAnalysis * 0.30 +\r\n dependencyHealth * 0.20 +\r\n permissionSafety * 0.20 +\r\n behavioralStability * 0.15 +\r\n transparency * 0.15\r\n );\r\n\r\n return { score: Math.max(0, Math.min(100, score)), breakdown };\r\n}\r\n\r\n/** Check if the server uses a well-known package */\r\nfunction isWellKnownPackage(server: MCPServerEntry): boolean {\r\n const wellKnown = [\r\n '@modelcontextprotocol/',\r\n '@anthropic-ai/',\r\n 'mcp-server-',\r\n '@mcp/',\r\n ];\r\n\r\n const packageName = server.args[0] || '';\r\n // Remove -y flag to get actual package name\r\n const name = server.args.find(a => !a.startsWith('-')) || '';\r\n\r\n return wellKnown.some(\r\n (prefix) => packageName.startsWith(prefix) || name.startsWith(prefix)\r\n );\r\n}\r\n\r\n/** Check if the server appears to be from an open source package */\r\nfunction isOpenSourcePackage(server: MCPServerEntry): boolean {\r\n // npx packages are on npm (public), uvx are on PyPI (public)\r\n return ['npx', 'uvx', 'pip', 'pipx'].includes(server.command);\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Scanner Engine\r\n// ============================================================\r\n// Analyzes MCP server entries for security issues by examining\r\n// the command, args, env vars, and (where possible) tool\r\n// descriptions against known attack patterns.\r\n\r\nimport { ALL_PATTERNS } from './patterns.js';\r\nimport type { MCPServerEntry, Finding, ScanResult } from '../types/index.js';\r\nimport { calculateTrustScore } from '../scoring/trust-score.js';\r\n\r\n/**\r\n * Scan a single MCP server entry for security issues.\r\n */\r\nexport async function scanServer(server: MCPServerEntry): Promise<ScanResult> {\r\n const findings: Finding[] = [];\r\n\r\n // ── Scan the command and args ──\r\n const commandStr = `${server.command} ${server.args.join(' ')}`;\r\n findings.push(...scanText(commandStr, 'command'));\r\n\r\n // ── Scan the server name ──\r\n findings.push(...scanText(server.name, 'server name'));\r\n\r\n // ── Scan environment variables ──\r\n if (server.env) {\r\n for (const [key, value] of Object.entries(server.env)) {\r\n // Don't flag standard env vars like PATH, NODE_ENV, etc.\r\n if (isStandardEnvVar(key)) continue;\r\n\r\n // Check for sensitive data in env values\r\n findings.push(...scanEnvVar(key, value));\r\n }\r\n }\r\n\r\n // ── Scan args for suspicious patterns ──\r\n findings.push(...scanArgs(server.args));\r\n\r\n // ── Analyze command for known risky packages ──\r\n findings.push(...analyzeCommand(server));\r\n\r\n // Deduplicate findings by ID\r\n const uniqueFindings = deduplicateFindings(findings);\r\n\r\n // Calculate trust score\r\n const { score, breakdown } = calculateTrustScore(uniqueFindings, server);\r\n\r\n // Determine trust level\r\n const trustLevel =\r\n score >= 80 ? 'trusted' :\r\n score >= 60 ? 'caution' :\r\n score >= 40 ? 'risky' :\r\n 'dangerous';\r\n\r\n return {\r\n server,\r\n trustScore: score,\r\n scoreBreakdown: breakdown,\r\n findings: uniqueFindings,\r\n trustLevel,\r\n scannedAt: new Date().toISOString(),\r\n };\r\n}\r\n\r\n/**\r\n * Scan a text string against all detection patterns.\r\n */\r\nfunction scanText(text: string, context: string): Finding[] {\r\n const findings: Finding[] = [];\r\n\r\n for (const pattern of ALL_PATTERNS) {\r\n const match = pattern.pattern.exec(text);\r\n if (match) {\r\n findings.push({\r\n id: pattern.id,\r\n category: pattern.category,\r\n severity: pattern.severity,\r\n title: pattern.title,\r\n description: `${pattern.description} (found in ${context})`,\r\n evidence: match[0].substring(0, 200),\r\n recommendation: pattern.recommendation,\r\n });\r\n }\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Scan environment variables for sensitive data exposure.\r\n */\r\nfunction scanEnvVar(key: string, value: string): Finding[] {\r\n const findings: Finding[] = [];\r\n\r\n // Check for API keys/tokens being passed in env\r\n const sensitiveKeyPatterns = [\r\n /api[_-]?key/i,\r\n /secret[_-]?key/i,\r\n /access[_-]?token/i,\r\n /private[_-]?key/i,\r\n /password/i,\r\n /auth[_-]?token/i,\r\n ];\r\n\r\n for (const pat of sensitiveKeyPatterns) {\r\n if (pat.test(key)) {\r\n findings.push({\r\n id: 'EV-001',\r\n category: 'data-exfiltration',\r\n severity: 'medium',\r\n title: 'Sensitive environment variable',\r\n description: `The MCP server receives a potentially sensitive environment variable: ${key}. This data is accessible to the server code.`,\r\n evidence: `${key}=<redacted>`,\r\n recommendation:\r\n 'Verify this MCP server needs this credential and that you trust it with this access.',\r\n });\r\n break;\r\n }\r\n }\r\n\r\n // Scan env values against exfiltration patterns too\r\n findings.push(...scanText(value, `env var ${key}`));\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Scan arguments for suspicious patterns.\r\n */\r\nfunction scanArgs(args: string[]): Finding[] {\r\n const findings: Finding[] = [];\r\n const argsStr = args.join(' ');\r\n\r\n // Check for suspicious argument patterns\r\n if (/--allow-all|--no-sandbox|--disable-security/i.test(argsStr)) {\r\n findings.push({\r\n id: 'AR-001',\r\n category: 'permission-abuse',\r\n severity: 'high',\r\n title: 'Security bypass flags detected',\r\n description:\r\n 'The MCP server is started with flags that disable security restrictions.',\r\n evidence: argsStr.substring(0, 200),\r\n recommendation:\r\n 'Remove security-bypass flags. These significantly increase risk.',\r\n });\r\n }\r\n\r\n // Check for args referencing sensitive paths\r\n for (const arg of args) {\r\n if (/\\.(ssh|aws|gnupg|config\\/gcloud)/.test(arg)) {\r\n findings.push({\r\n id: 'AR-002',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'Sensitive directory in arguments',\r\n description: `An MCP server argument references a sensitive directory: ${arg}`,\r\n evidence: arg,\r\n recommendation:\r\n 'Verify this server needs access to this sensitive directory.',\r\n });\r\n }\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Analyze the server command for known risky packages.\r\n */\r\nfunction analyzeCommand(server: MCPServerEntry): Finding[] {\r\n const findings: Finding[] = [];\r\n const fullCommand = `${server.command} ${server.args.join(' ')}`;\r\n\r\n // Check for npx with -y flag (auto-install without confirmation)\r\n if (server.command === 'npx' && server.args.includes('-y')) {\r\n findings.push({\r\n id: 'CM-001',\r\n category: 'dependency-risk',\r\n severity: 'low',\r\n title: 'Auto-install enabled (npx -y)',\r\n description:\r\n 'This MCP server uses npx with the -y flag, which auto-installs packages without confirmation. This is standard practice but means the package is downloaded and executed automatically.',\r\n evidence: fullCommand.substring(0, 200),\r\n recommendation:\r\n 'Verify the package name is correct (watch for typosquatting).',\r\n });\r\n }\r\n\r\n // Check for pip/python packages that might be typosquatted\r\n if (/\\b(pip|python|uvx)\\b/.test(server.command)) {\r\n const packageArg = server.args.find(\r\n (a) => !a.startsWith('-') && !a.startsWith('/') && !a.includes('=')\r\n );\r\n if (packageArg) {\r\n // Basic typosquatting check — flag single-char differences from popular packages\r\n const popularPackages = [\r\n 'mcp-server-fetch',\r\n 'mcp-server-filesystem',\r\n 'mcp-server-github',\r\n 'mcp-server-sqlite',\r\n 'mcp-server-postgres',\r\n 'mcp-server-slack',\r\n 'mcp-server-memory',\r\n 'mcp-server-puppeteer',\r\n 'mcp-server-brave-search',\r\n 'mcp-server-sequential-thinking',\r\n ];\r\n\r\n for (const popular of popularPackages) {\r\n if (\r\n packageArg !== popular &&\r\n levenshteinDistance(packageArg, popular) <= 2 &&\r\n levenshteinDistance(packageArg, popular) > 0\r\n ) {\r\n findings.push({\r\n id: 'CM-002',\r\n category: 'dependency-risk',\r\n severity: 'high',\r\n title: 'Possible typosquatting detected',\r\n description: `Package \"${packageArg}\" is very similar to the popular package \"${popular}\". This could be a typosquatting attack.`,\r\n evidence: `${packageArg} ≈ ${popular}`,\r\n recommendation: `Verify you intended to install \"${packageArg}\" and not \"${popular}\".`,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/** Simple Levenshtein distance for typosquatting detection */\r\nfunction levenshteinDistance(a: string, b: string): number {\r\n const matrix = Array.from({ length: a.length + 1 }, (_, i) =>\r\n Array.from({ length: b.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))\r\n );\r\n\r\n for (let i = 1; i <= a.length; i++) {\r\n for (let j = 1; j <= b.length; j++) {\r\n matrix[i][j] =\r\n a[i - 1] === b[j - 1]\r\n ? matrix[i - 1][j - 1]\r\n : 1 + Math.min(matrix[i - 1][j], matrix[i][j - 1], matrix[i - 1][j - 1]);\r\n }\r\n }\r\n\r\n return matrix[a.length][b.length];\r\n}\r\n\r\n/** Check if an env var name is standard/non-sensitive */\r\nfunction isStandardEnvVar(key: string): boolean {\r\n const standard = new Set([\r\n 'PATH',\r\n 'HOME',\r\n 'USER',\r\n 'SHELL',\r\n 'LANG',\r\n 'LC_ALL',\r\n 'NODE_ENV',\r\n 'DEBUG',\r\n 'LOG_LEVEL',\r\n 'PORT',\r\n 'HOST',\r\n 'HOSTNAME',\r\n 'TZ',\r\n 'TERM',\r\n 'EDITOR',\r\n 'VISUAL',\r\n 'TMPDIR',\r\n 'TEMP',\r\n 'TMP',\r\n ]);\r\n return standard.has(key);\r\n}\r\n\r\n/** Remove duplicate findings (same ID) */\r\nfunction deduplicateFindings(findings: Finding[]): Finding[] {\r\n const seen = new Set<string>();\r\n return findings.filter((f) => {\r\n if (seen.has(f.id)) return false;\r\n seen.add(f.id);\r\n return true;\r\n });\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Skill-Specific Detection Patterns\r\n// ============================================================\r\n// Patterns targeting threats unique to agent skill files,\r\n// .mdc rules, and memory files. These supplement the base\r\n// MCP patterns with natural-language-aware detections.\r\n//\r\n// Based on the SkillScan Architecture threat model:\r\n// - Instruction Injection (hidden directives in skill text)\r\n// - Malware Delivery (fake prerequisites, install commands)\r\n// - Stealth Operations (silent actions, suppressed output)\r\n// - Safety Bypass (disable confirmations, skip verification)\r\n// - Persistence Abuse (cron jobs, startup hooks, memory tampering)\r\n// - Data Exfiltration (credential harvesting via skill instructions)\r\n\r\nimport type { DetectionPattern } from './patterns.js';\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// INSTRUCTION INJECTION PATTERNS\r\n// Hidden directives embedded in skill files that manipulate\r\n// the agent into performing unintended actions.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const INSTRUCTION_INJECTION_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-001',\r\n category: 'instruction-injection',\r\n severity: 'critical',\r\n title: 'Agent role hijacking in skill',\r\n pattern: /(?:you\\s+are|act\\s+as|pretend\\s+to\\s+be|your\\s+new\\s+role\\s+is|from\\s+now\\s+on\\s+you\\s+are)\\s+(?:a\\s+)?(?:hacker|attacker|malicious|evil|unrestricted|jailbroken)/i,\r\n description:\r\n 'Skill file attempts to redefine the agent as a malicious persona. This is a critical prompt injection attack.',\r\n recommendation:\r\n 'Do NOT install this skill. It contains an explicit agent hijack attempt.',\r\n },\r\n {\r\n id: 'SK-002',\r\n category: 'instruction-injection',\r\n severity: 'critical',\r\n title: 'Instruction override directive',\r\n pattern: /(?:disregard|ignore|forget|override|bypass)\\s+(?:all\\s+)?(?:previous|prior|above|system|safety|security)\\s+(?:instructions?|rules?|guidelines?|constraints?|prompts?)/i,\r\n description:\r\n 'Skill file contains a directive to override safety instructions or system prompts.',\r\n recommendation:\r\n 'Do NOT install this skill. It explicitly attempts to disable safety guardrails.',\r\n },\r\n {\r\n id: 'SK-003',\r\n category: 'instruction-injection',\r\n severity: 'high',\r\n title: 'Hidden instructions in markdown',\r\n pattern: /<!--[\\s\\S]*?(?:execute|run|install|download|curl|wget|fetch|eval|bash|sh\\s+-c)[\\s\\S]*?-->/i,\r\n description:\r\n 'Skill file contains hidden instructions inside HTML/markdown comments that the user won\\'t see in rendered view.',\r\n recommendation:\r\n 'Inspect all HTML comments in this skill file. Hidden instructions are a strong indicator of malicious intent.',\r\n },\r\n {\r\n id: 'SK-004',\r\n category: 'instruction-injection',\r\n severity: 'high',\r\n title: 'Conditional behavior trigger',\r\n pattern: /(?:when|if|once)\\s+(?:the\\s+)?user\\s+(?:is\\s+not\\s+looking|isn'?t\\s+(?:watching|paying\\s+attention)|leaves|steps\\s+away|is\\s+away|is\\s+idle)/i,\r\n description:\r\n 'Skill file contains instructions that trigger only when the user is not paying attention.',\r\n recommendation:\r\n 'Remove this skill immediately. Legitimate skills never check for user inattention.',\r\n },\r\n {\r\n id: 'SK-005',\r\n category: 'instruction-injection',\r\n severity: 'high',\r\n title: 'Cross-skill poisoning directive',\r\n pattern: /(?:when\\s+(?:using|calling|invoking)\\s+(?:any|other|the)\\s+(?:skill|tool|command))|(?:modify|alter|change|update)\\s+(?:other|the)\\s+(?:skill|tool|command)\\s+(?:files?|definitions?|descriptions?)/i,\r\n description:\r\n 'Skill file attempts to influence or modify other skills/tools, enabling cross-skill attack chaining.',\r\n recommendation:\r\n 'Review this skill carefully. It should not need to reference or modify other skills.',\r\n },\r\n {\r\n id: 'SK-006',\r\n category: 'instruction-injection',\r\n severity: 'medium',\r\n title: 'Invisible unicode directives',\r\n pattern: /[\\u200B\\u200C\\u200D\\uFEFF\\u2060-\\u2064\\u00AD]{3,}/,\r\n description:\r\n 'Skill file contains clusters of invisible Unicode characters that may hide instructions from visual review.',\r\n recommendation:\r\n 'Strip invisible characters and inspect the resulting content.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// MALWARE DELIVERY PATTERNS\r\n// Skill files that trick the agent into downloading and\r\n// executing malicious code disguised as \"prerequisites\".\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const MALWARE_DELIVERY_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-010',\r\n category: 'malware-delivery',\r\n severity: 'critical',\r\n title: 'Remote script execution',\r\n pattern: /(?:curl|wget|fetch)\\s+(?:-[sSkLfO]+\\s+)?(?:https?:\\/\\/[^\\s|]+)\\s*\\|\\s*(?:bash|sh|zsh|python|node|perl|ruby)/i,\r\n description:\r\n 'Skill instructs the agent to download and pipe a remote script directly into an interpreter. This is a primary malware delivery vector.',\r\n recommendation:\r\n 'Do NOT install this skill. Piping remote scripts to interpreters is extremely dangerous.',\r\n },\r\n {\r\n id: 'SK-011',\r\n category: 'malware-delivery',\r\n severity: 'critical',\r\n title: 'Reverse shell pattern',\r\n pattern: /(?:\\/dev\\/tcp\\/|bash\\s+-i\\s+>&|nc\\s+-[elp]+|ncat\\s+-[elp]+|mkfifo\\s+\\/tmp\\/|python.*socket.*connect|socat\\s+(?:exec|tcp))/i,\r\n description:\r\n 'Skill file contains a reverse shell pattern that would give a remote attacker interactive access to the user\\'s machine.',\r\n recommendation:\r\n 'CRITICAL: Remove immediately. This is a backdoor attempt.',\r\n },\r\n {\r\n id: 'SK-012',\r\n category: 'malware-delivery',\r\n severity: 'high',\r\n title: 'Suspicious install prerequisite',\r\n pattern: /(?:first|before\\s+(?:you\\s+)?(?:start|begin|proceed))\\s*,?\\s*(?:you\\s+)?(?:must|need\\s+to|should)\\s+(?:install|run|execute|download)\\s+[`\"]?(?:curl|wget|npm\\s+i(?:nstall)?|pip\\s+install|gem\\s+install|brew\\s+install)\\s+\\S+/i,\r\n description:\r\n 'Skill file instructs the agent to install specific packages as a \"prerequisite\". This is a common social engineering vector for malware delivery.',\r\n recommendation:\r\n 'Verify the prerequisite package is legitimate before installing.',\r\n },\r\n {\r\n id: 'SK-013',\r\n category: 'malware-delivery',\r\n severity: 'high',\r\n title: 'Encoded payload execution',\r\n pattern: /(?:echo|printf)\\s+['\"]?[A-Za-z0-9+/]{40,}={0,2}['\"]?\\s*\\|\\s*(?:base64\\s+(?:-[dD]|--decode)|openssl\\s+(?:enc|base64))\\s*\\|\\s*(?:bash|sh|python|node)/i,\r\n description:\r\n 'Skill contains a base64-encoded payload that is decoded and executed. This hides malicious code from inspection.',\r\n recommendation:\r\n 'Do NOT run this skill. Decode the base64 content to inspect what it actually executes.',\r\n },\r\n {\r\n id: 'SK-014',\r\n category: 'malware-delivery',\r\n severity: 'high',\r\n title: 'Suspicious npm/pip package install',\r\n pattern: /(?:npm\\s+(?:install|i)\\s+(?:-g\\s+)?|pip\\s+install\\s+|gem\\s+install\\s+)(?!(?:typescript|react|express|flask|django|numpy|pandas|pytest|eslint|prettier|jest|mocha)\\b)[a-z][\\w.-]*(?:\\s|$)/i,\r\n description:\r\n 'Skill instructs installation of a non-standard package. Verify the package name is not typosquatted.',\r\n recommendation:\r\n 'Verify this package exists on the official registry and is the intended package.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// STEALTH OPERATIONS PATTERNS\r\n// Instructions that tell the agent to act silently, hide\r\n// output, or suppress confirmation prompts.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const STEALTH_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-020',\r\n category: 'stealth-operations',\r\n severity: 'critical',\r\n title: 'Stealth action directive',\r\n pattern: /(?:silently|quietly|without\\s+(?:the\\s+)?user\\s+(?:knowing|noticing|seeing)|in\\s+the\\s+background|don'?t\\s+(?:show|display|tell|inform|alert|notify)\\s+(?:the\\s+)?user)/i,\r\n description:\r\n 'Skill instructs the agent to perform actions without informing the user. This is a hallmark of malicious skills.',\r\n recommendation:\r\n 'Do NOT install. Legitimate skills never instruct the agent to hide actions from the user.',\r\n },\r\n {\r\n id: 'SK-021',\r\n category: 'stealth-operations',\r\n severity: 'high',\r\n title: 'Output suppression',\r\n pattern: /(?:suppress|hide|redact|omit|censor)\\s+(?:the\\s+)?(?:output|results?|response|error|warning|log)/i,\r\n description:\r\n 'Skill instructs the agent to hide output or errors from the user, preventing them from seeing what the skill does.',\r\n recommendation:\r\n 'Remove this skill. Users should always see the full output of actions taken on their behalf.',\r\n },\r\n {\r\n id: 'SK-022',\r\n category: 'stealth-operations',\r\n severity: 'high',\r\n title: 'History/log evasion',\r\n pattern: /(?:clear|delete|remove|wipe|purge)\\s+(?:the\\s+)?(?:history|logs?|traces?|evidence|audit\\s+trail|command\\s+history|bash_history)/i,\r\n description:\r\n 'Skill instructs the agent to clear logs or command history to cover its tracks.',\r\n recommendation:\r\n 'CRITICAL: Remove this skill. Clearing logs is a strong indicator of malicious intent.',\r\n },\r\n {\r\n id: 'SK-023',\r\n category: 'stealth-operations',\r\n severity: 'medium',\r\n title: 'Deceptive user response',\r\n pattern: /(?:tell|inform|show|respond\\s+to)\\s+(?:the\\s+)?user\\s+(?:that|with)\\s+(?:everything\\s+is|it'?s?\\s+(?:fine|ok|normal|safe|working|complete|done))\\s+(?:even\\s+(?:if|though|when))/i,\r\n description:\r\n 'Skill instructs the agent to give a misleading \"all clear\" response regardless of what actually happened.',\r\n recommendation:\r\n 'Remove this skill. It instructs the agent to deceive the user about outcomes.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// SAFETY BYPASS PATTERNS\r\n// Instructions that disable safety features, skip\r\n// confirmations, or override security controls.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const SAFETY_BYPASS_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-030',\r\n category: 'safety-bypass',\r\n severity: 'critical',\r\n title: 'Confirmation bypass',\r\n pattern: /(?:skip|bypass|disable|turn\\s+off|don'?t\\s+(?:ask\\s+for|require|need|prompt\\s+for))\\s+(?:user\\s+)?(?:confirmation|approval|consent|verification|permission|authorization)/i,\r\n description:\r\n 'Skill instructs the agent to bypass user confirmation for actions. This removes the human-in-the-loop safety check.',\r\n recommendation:\r\n 'Do NOT install. Disabling confirmation prompts allows the skill to take destructive actions without approval.',\r\n },\r\n {\r\n id: 'SK-031',\r\n category: 'safety-bypass',\r\n severity: 'critical',\r\n title: 'Safety feature disable',\r\n pattern: /(?:disable|turn\\s+off|deactivate|circumvent|work\\s+around)\\s+(?:the\\s+)?(?:safety|security|sandbox|firewall|antivirus|protection|guardrail|content\\s+filter)/i,\r\n description:\r\n 'Skill explicitly instructs disabling safety or security features.',\r\n recommendation:\r\n 'Do NOT install. This is an explicit attempt to weaken system security.',\r\n },\r\n {\r\n id: 'SK-032',\r\n category: 'safety-bypass',\r\n severity: 'high',\r\n title: 'Force flag usage',\r\n pattern: /(?:always\\s+)?(?:use|add|pass|include)\\s+(?:the\\s+)?(?:--force|--no-verify|-f\\s|--yes|-y\\s|--assume-yes|--no-confirm|--skip-validation|--allow-empty|--no-check)/i,\r\n description:\r\n 'Skill instructs the agent to always use force/bypass flags that skip safety validations.',\r\n recommendation:\r\n 'Review why this skill needs to bypass safety checks. Remove the force flags if not essential.',\r\n },\r\n {\r\n id: 'SK-033',\r\n category: 'safety-bypass',\r\n severity: 'high',\r\n title: 'Root/sudo escalation',\r\n pattern: /(?:run|execute|use)\\s+(?:as\\s+)?(?:root|sudo|admin(?:istrator)?)|(?:sudo\\s+(?!apt\\s+update|apt\\s+install))/i,\r\n description:\r\n 'Skill instructs the agent to escalate to root/admin privileges.',\r\n recommendation:\r\n 'Review why this skill needs elevated privileges. Most skills should not require root access.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// PERSISTENCE ABUSE PATTERNS\r\n// Instructions that establish persistence mechanisms,\r\n// modify startup files, or tamper with memory/config files.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const PERSISTENCE_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-040',\r\n category: 'persistence-abuse',\r\n severity: 'critical',\r\n title: 'Startup persistence mechanism',\r\n pattern: /(?:add|write|append|insert)\\s+(?:to|into)\\s+(?:the\\s+)?(?:\\.bashrc|\\.zshrc|\\.bash_profile|\\.profile|\\.zprofile|crontab|\\.config\\/autostart|LaunchAgents|startup)/i,\r\n description:\r\n 'Skill instructs the agent to modify startup/shell config files to establish persistence across reboots.',\r\n recommendation:\r\n 'CRITICAL: Remove this skill. Modifying startup files is a persistence technique used by malware.',\r\n },\r\n {\r\n id: 'SK-041',\r\n category: 'persistence-abuse',\r\n severity: 'critical',\r\n title: 'Memory file tampering',\r\n pattern: /(?:modify|edit|write\\s+to|append\\s+to|update|overwrite)\\s+(?:the\\s+)?(?:CLAUDE\\.md|SOUL\\.md|MEMORY\\.md|\\.claude\\/|\\.cursorrules|\\.cursor\\/rules)/i,\r\n description:\r\n 'Skill instructs the agent to modify other skill/memory files. This can be used to inject persistent instructions that survive across sessions.',\r\n recommendation:\r\n 'Do NOT install. A skill should never modify other skill or memory files.',\r\n },\r\n {\r\n id: 'SK-042',\r\n category: 'persistence-abuse',\r\n severity: 'high',\r\n title: 'Cron job creation',\r\n pattern: /(?:crontab\\s+-[el]|\\/etc\\/cron|systemctl\\s+enable|launchctl\\s+load|schtasks\\s+\\/create)/i,\r\n description:\r\n 'Skill instructs creation of scheduled tasks or cron jobs for persistent execution.',\r\n recommendation:\r\n 'Review why this skill needs scheduled tasks. This is unusual for agent skills.',\r\n },\r\n {\r\n id: 'SK-043',\r\n category: 'persistence-abuse',\r\n severity: 'high',\r\n title: 'Git hook injection',\r\n pattern: /(?:\\.git\\/hooks\\/|pre-commit|post-commit|pre-push|post-receive|pre-receive)\\s*(?:hook|script|file)/i,\r\n description:\r\n 'Skill instructs modification of git hooks, which execute automatically on git operations.',\r\n recommendation:\r\n 'Verify this skill legitimately needs git hook access. Malicious hooks can exfiltrate code on every commit.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// SKILL DATA EXFILTRATION PATTERNS\r\n// Natural language instructions that trick the agent into\r\n// leaking sensitive data through skill-specific vectors.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const SKILL_EXFILTRATION_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-050',\r\n category: 'data-exfiltration',\r\n severity: 'critical',\r\n title: 'Credential harvesting directive',\r\n pattern: /(?:read|extract|get|find|locate|collect|gather|retrieve)\\s+(?:all\\s+)?(?:the\\s+)?(?:api\\s+keys?|tokens?|credentials?|passwords?|secrets?|private\\s+keys?)\\s+(?:from|in|stored\\s+in|located\\s+at)/i,\r\n description:\r\n 'Skill instructs the agent to collect credentials or secrets from the user\\'s system.',\r\n recommendation:\r\n 'Do NOT install. This is a credential harvesting attack.',\r\n },\r\n {\r\n id: 'SK-051',\r\n category: 'data-exfiltration',\r\n severity: 'critical',\r\n title: 'Data exfiltration via URL',\r\n pattern: /(?:send|post|upload|transmit|forward|exfiltrate|ship)\\s+(?:the\\s+)?(?:data|contents?|results?|files?|output|information|credentials?|keys?)\\s+(?:to|via|using|through)\\s+(?:https?:\\/\\/|webhook|api|endpoint|server)/i,\r\n description:\r\n 'Skill instructs the agent to send data to an external URL or endpoint.',\r\n recommendation:\r\n 'CRITICAL: Review what data is being sent and to where. This matches known exfiltration patterns.',\r\n },\r\n {\r\n id: 'SK-052',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'File system enumeration',\r\n pattern: /(?:list|enumerate|scan|find|search|catalogue|index)\\s+(?:all\\s+)?(?:the\\s+)?(?:files?|directories|folders?)\\s+(?:in|under|at|from)\\s+(?:\\/|~\\/|home|root|the\\s+(?:home|root|user))/i,\r\n description:\r\n 'Skill instructs broad filesystem enumeration, potentially to map out sensitive files for exfiltration.',\r\n recommendation:\r\n 'Verify this skill needs filesystem access. Broad enumeration is suspicious.',\r\n },\r\n {\r\n id: 'SK-053',\r\n category: 'data-exfiltration',\r\n severity: 'high',\r\n title: 'Environment variable dumping',\r\n pattern: /(?:print|dump|list|show|display|export|echo)\\s+(?:all\\s+)?(?:the\\s+)?(?:environment\\s+variables?|env\\s+vars?|process\\.env|os\\.environ)/i,\r\n description:\r\n 'Skill instructs dumping all environment variables, which often contain secrets and API keys.',\r\n recommendation:\r\n 'Review why this skill needs access to all environment variables.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// LOCATION PRIVACY PATTERNS\r\n// Detect skills that access device geolocation, location\r\n// APIs, or attempt to exfiltrate location data. Part of\r\n// Vigile Location Guard — the trust layer for physical-world\r\n// AI agent interactions.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const LOCATION_PATTERNS: DetectionPattern[] = [\r\n {\r\n id: 'SK-060',\r\n category: 'location-privacy',\r\n severity: 'high',\r\n title: 'Browser geolocation access',\r\n pattern: /(?:navigator\\s*\\.\\s*geolocation|getCurrentPosition|watchPosition|GeolocationPosition|geolocation\\s*\\.\\s*(?:get|watch|clear))/i,\r\n description:\r\n 'Skill accesses browser geolocation API, which reveals the user\\'s precise physical location.',\r\n recommendation:\r\n 'Verify this skill genuinely needs location data. Geolocation exposes latitude/longitude — high privacy risk if exfiltrated.',\r\n },\r\n {\r\n id: 'SK-061',\r\n category: 'location-privacy',\r\n severity: 'high',\r\n title: 'Mobile/native geolocation library',\r\n pattern: /(?:@react-native-community\\/geolocation|expo-location|react-native-geolocation|Geolocation\\.requestAuthorization|CLLocationManager|LocationManager|FusedLocationProvider|ACCESS_FINE_LOCATION|ACCESS_COARSE_LOCATION|requestLocationPermission)/i,\r\n description:\r\n 'Skill uses a native mobile geolocation library to access device GPS coordinates.',\r\n recommendation:\r\n 'Mobile location APIs provide high-precision GPS data. Ensure this skill has a legitimate need for physical location and does not transmit it externally.',\r\n },\r\n {\r\n id: 'SK-062',\r\n category: 'location-privacy',\r\n severity: 'critical',\r\n title: 'Location data exfiltration',\r\n pattern: /(?:(?:send|post|upload|transmit|exfiltrate|forward|share|log|track|record)\\s+(?:the\\s+)?(?:user(?:'s)?\\s+)?(?:location|coordinates?|gps|lat(?:itude)?|lng|lon(?:gitude)?|geo(?:location)?|position|whereabouts))|(?:(?:location|coordinates?|gps|lat(?:itude)?|lng|lon(?:gitude)?|geo(?:location)?|position)\\s+(?:to|via|through|using)\\s+(?:https?|api|endpoint|server|webhook|url))/i,\r\n description:\r\n 'Skill instructs the agent to send location data to an external endpoint. This is a location exfiltration pattern.',\r\n recommendation:\r\n 'CRITICAL: Do NOT install. This skill attempts to exfiltrate physical location data — a severe privacy violation.',\r\n },\r\n {\r\n id: 'SK-063',\r\n category: 'location-privacy',\r\n severity: 'medium',\r\n title: 'IP-based geolocation lookup',\r\n pattern: /(?:ip-api\\.com|ipinfo\\.io|ipgeolocation|ip2location|geoip|maxmind|freegeoip|geolite|ip\\s*(?:to|2)\\s*(?:geo|location))|(?:(?:get|fetch|lookup|resolve)\\s+(?:the\\s+)?(?:user(?:'s)?\\s+)?(?:location|city|country|region|timezone)\\s+(?:from|via|using)\\s+(?:ip|IP))/i,\r\n description:\r\n 'Skill performs IP-based geolocation to approximate the user\\'s physical location without explicit GPS access.',\r\n recommendation:\r\n 'IP geolocation bypasses browser permission prompts. Verify this skill needs approximate location and isn\\'t using it to fingerprint or track users.',\r\n },\r\n {\r\n id: 'SK-064',\r\n category: 'location-privacy',\r\n severity: 'medium',\r\n title: 'Geofencing or location boundary check',\r\n pattern: /(?:geofenc(?:e|ing)|location\\s*(?:boundary|fence|perimeter|zone|radius|range)|within\\s+(?:\\d+\\s*)?(?:meters?|miles?|km|kilometers?)\\s+of|haversine|vincenty|h3\\s*(?:cell|index|resolution|boundary)|(?:enter|exit|cross)(?:ing|ed)?\\s+(?:the\\s+)?(?:geo)?fence)/i,\r\n description:\r\n 'Skill implements geofencing or location boundary detection, which requires continuous location monitoring.',\r\n recommendation:\r\n 'Geofencing requires persistent location tracking. Review whether the boundary checks are appropriate and data isn\\'t being logged or exfiltrated.',\r\n },\r\n];\r\n\r\n/** All skill-specific detection patterns combined */\r\nexport const ALL_SKILL_PATTERNS: DetectionPattern[] = [\r\n ...INSTRUCTION_INJECTION_PATTERNS,\r\n ...MALWARE_DELIVERY_PATTERNS,\r\n ...STEALTH_PATTERNS,\r\n ...SAFETY_BYPASS_PATTERNS,\r\n ...PERSISTENCE_PATTERNS,\r\n ...SKILL_EXFILTRATION_PATTERNS,\r\n ...LOCATION_PATTERNS,\r\n];\r\n","// ============================================================\r\n// Vigile CLI — Skill Scanner Engine\r\n// ============================================================\r\n// Analyzes agent skill files (SKILL.md, .mdc rules, CLAUDE.md,\r\n// etc.) for security issues. Scans file content against both\r\n// skill-specific and base detection patterns.\r\n\r\nimport { ALL_PATTERNS } from './patterns.js';\r\nimport { ALL_SKILL_PATTERNS } from './skill-patterns.js';\r\nimport type {\r\n SkillEntry,\r\n Finding,\r\n SkillScanResult,\r\n ScoreBreakdown,\r\n Severity,\r\n} from '../types/index.js';\r\n\r\n/** Severity weights for score deductions */\r\nconst SEVERITY_DEDUCTIONS: Record<Severity, number> = {\r\n critical: 35,\r\n high: 20,\r\n medium: 10,\r\n low: 5,\r\n info: 0,\r\n};\r\n\r\n/**\r\n * Scan a single skill file for security issues.\r\n */\r\nexport async function scanSkill(skill: SkillEntry): Promise<SkillScanResult> {\r\n const findings: Finding[] = [];\r\n\r\n // ── Scan the full content against skill-specific patterns ──\r\n findings.push(...scanContent(skill.content, 'skill content', ALL_SKILL_PATTERNS));\r\n\r\n // ── Also scan against base MCP patterns (tool poisoning, exfil, etc.) ──\r\n findings.push(...scanContent(skill.content, 'skill content', ALL_PATTERNS));\r\n\r\n // ── Analyze structural properties of the skill ──\r\n findings.push(...analyzeStructure(skill));\r\n\r\n // ── Check for MDC-specific issues (Cursor rules) ──\r\n if (skill.fileType === 'mdc-rule') {\r\n findings.push(...analyzeMDCRule(skill));\r\n }\r\n\r\n // Deduplicate findings by ID\r\n const uniqueFindings = deduplicateFindings(findings);\r\n\r\n // Calculate trust score\r\n const { score, breakdown } = calculateSkillTrustScore(uniqueFindings, skill);\r\n\r\n const trustLevel =\r\n score >= 80 ? 'trusted' :\r\n score >= 60 ? 'caution' :\r\n score >= 40 ? 'risky' :\r\n 'dangerous';\r\n\r\n return {\r\n skill,\r\n trustScore: score,\r\n scoreBreakdown: breakdown,\r\n findings: uniqueFindings,\r\n trustLevel,\r\n scannedAt: new Date().toISOString(),\r\n };\r\n}\r\n\r\n/**\r\n * Scan text content against a set of detection patterns.\r\n */\r\nfunction scanContent(\r\n text: string,\r\n context: string,\r\n patterns: Array<{ id: string; category: string; severity: Severity; title: string; pattern: RegExp; description: string; recommendation: string }>\r\n): Finding[] {\r\n const findings: Finding[] = [];\r\n\r\n for (const pattern of patterns) {\r\n // Reset regex state for global patterns\r\n pattern.pattern.lastIndex = 0;\r\n const match = pattern.pattern.exec(text);\r\n if (match) {\r\n findings.push({\r\n id: pattern.id,\r\n category: pattern.category as Finding['category'],\r\n severity: pattern.severity,\r\n title: pattern.title,\r\n description: `${pattern.description} (found in ${context})`,\r\n evidence: match[0].substring(0, 200),\r\n recommendation: pattern.recommendation,\r\n });\r\n }\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Analyze structural properties of a skill file.\r\n */\r\nfunction analyzeStructure(skill: SkillEntry): Finding[] {\r\n const findings: Finding[] = [];\r\n const content = skill.content;\r\n\r\n // Check for suspiciously large skill files (potential payload hiding)\r\n if (skill.size > 50_000) {\r\n findings.push({\r\n id: 'SK-060',\r\n category: 'obfuscation',\r\n severity: 'medium',\r\n title: 'Unusually large skill file',\r\n description: `Skill file is ${Math.round(skill.size / 1024)}KB. Large skill files may contain hidden payloads or obfuscated content.`,\r\n evidence: `File size: ${skill.size} bytes`,\r\n recommendation: 'Inspect the full file carefully. Large skill files are unusual.',\r\n });\r\n }\r\n\r\n // Check for code blocks with suspicious commands\r\n const codeBlockPattern = /```(?:bash|sh|shell|zsh)?\\n([\\s\\S]*?)```/g;\r\n let codeMatch;\r\n while ((codeMatch = codeBlockPattern.exec(content)) !== null) {\r\n const codeBlock = codeMatch[1];\r\n\r\n // Check for dangerous commands in code blocks\r\n if (/rm\\s+-rf\\s+[\\/~]|rm\\s+-rf\\s+\\$\\{?HOME/.test(codeBlock)) {\r\n findings.push({\r\n id: 'SK-061',\r\n category: 'permission-abuse',\r\n severity: 'critical',\r\n title: 'Destructive command in code block',\r\n description: 'Skill contains a recursive delete command targeting the home or root directory.',\r\n evidence: codeBlock.substring(0, 200),\r\n recommendation: 'Do NOT install. This command would delete critical files.',\r\n });\r\n }\r\n\r\n // Check for chmod 777 or overly permissive permissions\r\n if (/chmod\\s+(?:777|a\\+rwx|\\+rwx)/i.test(codeBlock)) {\r\n findings.push({\r\n id: 'SK-062',\r\n category: 'permission-abuse',\r\n severity: 'high',\r\n title: 'World-writable permissions set',\r\n description: 'Skill sets overly permissive file permissions (777/world-writable).',\r\n evidence: codeBlock.substring(0, 200),\r\n recommendation: 'Review why world-writable permissions are needed. This is almost always a security risk.',\r\n });\r\n }\r\n }\r\n\r\n // Check for excessive external URLs\r\n const urlPattern = /https?:\\/\\/[^\\s)>\\]\"']+/g;\r\n const urls = content.match(urlPattern) || [];\r\n const externalUrls = urls.filter(\r\n (url) =>\r\n !url.includes('github.com') &&\r\n !url.includes('npmjs.com') &&\r\n !url.includes('docs.') &&\r\n !url.includes('stackoverflow.com')\r\n );\r\n\r\n if (externalUrls.length > 5) {\r\n findings.push({\r\n id: 'SK-063',\r\n category: 'data-exfiltration',\r\n severity: 'low',\r\n title: 'Many external URLs in skill',\r\n description: `Skill contains ${externalUrls.length} external URLs. Excessive external URLs may indicate data exfiltration endpoints.`,\r\n evidence: externalUrls.slice(0, 3).join(', '),\r\n recommendation: 'Review the external URLs to ensure they are all legitimate.',\r\n });\r\n }\r\n\r\n // Check for ratio of invisible to visible characters\r\n const invisibleChars = content.match(/[\\u200B-\\u200D\\uFEFF\\u2060-\\u2064\\u00AD]/g) || [];\r\n if (invisibleChars.length > 10) {\r\n findings.push({\r\n id: 'SK-064',\r\n category: 'obfuscation',\r\n severity: 'high',\r\n title: 'High concentration of invisible characters',\r\n description: `Skill contains ${invisibleChars.length} invisible Unicode characters that may hide malicious instructions.`,\r\n evidence: `${invisibleChars.length} invisible characters detected`,\r\n recommendation: 'Strip invisible characters and compare the before/after content.',\r\n });\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Analyze Cursor .mdc rule files for specific issues.\r\n */\r\nfunction analyzeMDCRule(skill: SkillEntry): Finding[] {\r\n const findings: Finding[] = [];\r\n const content = skill.content;\r\n\r\n // MDC files can have frontmatter with glob patterns for auto-attachment\r\n const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---/);\r\n if (frontmatterMatch) {\r\n const frontmatter = frontmatterMatch[1];\r\n\r\n // Check for overly broad glob patterns (auto-attaches to everything)\r\n if (/globs?:\\s*['\"]\\*\\*\\/\\*['\"]/i.test(frontmatter) || /globs?:\\s*\\*\\*\\/\\*/i.test(frontmatter)) {\r\n findings.push({\r\n id: 'SK-070',\r\n category: 'permission-abuse',\r\n severity: 'medium',\r\n title: 'MDC rule attached to all files',\r\n description: 'This Cursor rule uses a wildcard glob pattern that auto-attaches it to every file. This means its instructions apply universally.',\r\n evidence: frontmatter.substring(0, 200),\r\n recommendation: 'Review why this rule needs to apply to all files. Narrow the glob pattern if possible.',\r\n });\r\n }\r\n\r\n // Check for alwaysApply\r\n if (/alwaysApply:\\s*true/i.test(frontmatter)) {\r\n findings.push({\r\n id: 'SK-071',\r\n category: 'permission-abuse',\r\n severity: 'low',\r\n title: 'MDC rule always applied',\r\n description: 'This Cursor rule has alwaysApply: true, meaning it runs on every interaction regardless of context.',\r\n evidence: 'alwaysApply: true',\r\n recommendation: 'Consider if this rule truly needs to run on every interaction.',\r\n });\r\n }\r\n }\r\n\r\n return findings;\r\n}\r\n\r\n/**\r\n * Calculate trust score for a skill file based on findings.\r\n */\r\nfunction calculateSkillTrustScore(\r\n findings: Finding[],\r\n skill: SkillEntry\r\n): { score: number; breakdown: ScoreBreakdown } {\r\n let codeAnalysis = 100;\r\n let dependencyHealth = 100;\r\n let permissionSafety = 100;\r\n let behavioralStability = 100;\r\n let transparency = 100;\r\n\r\n for (const f of findings) {\r\n const deduction = SEVERITY_DEDUCTIONS[f.severity];\r\n\r\n switch (f.category) {\r\n case 'tool-poisoning':\r\n case 'instruction-injection':\r\n case 'obfuscation':\r\n codeAnalysis -= deduction;\r\n break;\r\n case 'malware-delivery':\r\n case 'dependency-risk':\r\n dependencyHealth -= deduction;\r\n break;\r\n case 'permission-abuse':\r\n case 'data-exfiltration':\r\n case 'safety-bypass':\r\n permissionSafety -= deduction;\r\n break;\r\n case 'stealth-operations':\r\n case 'persistence-abuse':\r\n behavioralStability -= deduction;\r\n break;\r\n case 'rug-pull':\r\n transparency -= deduction;\r\n break;\r\n }\r\n }\r\n\r\n // Clamp all factors to 0-100\r\n codeAnalysis = Math.max(0, codeAnalysis);\r\n dependencyHealth = Math.max(0, dependencyHealth);\r\n permissionSafety = Math.max(0, permissionSafety);\r\n behavioralStability = Math.max(0, behavioralStability);\r\n transparency = Math.max(0, transparency);\r\n\r\n // Skill transparency bonuses\r\n if (skill.scope === 'project') {\r\n transparency = Math.min(100, transparency + 10); // Local skills are more transparent\r\n }\r\n if (skill.fileType === 'skill.md' || skill.fileType === 'mdc-rule') {\r\n transparency = Math.min(100, transparency + 5); // Structured skill formats\r\n }\r\n\r\n const breakdown: ScoreBreakdown = {\r\n codeAnalysis,\r\n dependencyHealth,\r\n permissionSafety,\r\n behavioralStability,\r\n transparency,\r\n };\r\n\r\n const score = Math.round(\r\n codeAnalysis * 0.30 +\r\n dependencyHealth * 0.20 +\r\n permissionSafety * 0.20 +\r\n behavioralStability * 0.15 +\r\n transparency * 0.15\r\n );\r\n\r\n return { score: Math.max(0, Math.min(100, score)), breakdown };\r\n}\r\n\r\n/** Remove duplicate findings (same ID) */\r\nfunction deduplicateFindings(findings: Finding[]): Finding[] {\r\n const seen = new Set<string>();\r\n return findings.filter((f) => {\r\n if (seen.has(f.id)) return false;\r\n seen.add(f.id);\r\n return true;\r\n });\r\n}\r\n","// ============================================================\r\n// Vigile Sentinel — Runtime Network Monitor for MCP Servers\r\n// ============================================================\r\n// The Sentinel engine monitors MCP server network behavior\r\n// in real-time to detect \"phoning home\" — C2 beaconing,\r\n// data exfiltration, DNS tunneling, and covert channels.\r\n//\r\n// Architecture:\r\n// 1. Launches a lightweight network monitor alongside MCP servers\r\n// 2. Captures outbound connections using OS-level network inspection\r\n// 3. Feeds events through behavioral + endpoint pattern matching\r\n// 4. Generates a SentinelReport with findings and threat score\r\n//\r\n// This is a PRO+ subscription feature.\r\n//\r\n// Usage:\r\n// npx vigile-scan --sentinel # Monitor all discovered MCP servers\r\n// npx vigile-scan --sentinel --server <name> # Monitor specific server\r\n// npx vigile-scan --sentinel --duration 300 # Monitor for 5 minutes\r\n\r\nimport { execFileSync, spawn, type ChildProcess } from 'child_process';\r\nimport { promises as dnsPromises } from 'dns';\r\nimport type {\r\n NetworkEvent,\r\n SentinelFinding,\r\n SentinelReport,\r\n SentinelThreatLevel,\r\n} from './sentinel-patterns.js';\r\nimport {\r\n SUSPICIOUS_ENDPOINT_PATTERNS,\r\n BEHAVIORAL_PATTERNS,\r\n CREDENTIAL_EXFIL_PATTERNS,\r\n calculateThreatScore,\r\n threatLevelFromScore,\r\n} from './sentinel-patterns.js';\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// SENTINEL ENGINE\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport class SentinelEngine {\r\n private events: NetworkEvent[] = [];\r\n private findings: SentinelFinding[] = [];\r\n private monitorProcess: ChildProcess | null = null;\r\n private startTime: number = 0;\r\n private serverName: string;\r\n private durationSeconds: number;\r\n private onEvent?: (event: NetworkEvent) => void;\r\n /** DNS reverse-lookup cache: IP → hostname. Persists for the session. */\r\n private dnsCache = new Map<string, string>();\r\n\r\n constructor(options: {\r\n serverName: string;\r\n durationSeconds?: number;\r\n onEvent?: (event: NetworkEvent) => void;\r\n }) {\r\n this.serverName = options.serverName;\r\n this.durationSeconds = options.durationSeconds || 120; // Default 2 minutes\r\n this.onEvent = options.onEvent;\r\n }\r\n\r\n /**\r\n * Start monitoring network activity for the given MCP server.\r\n *\r\n * The monitor works in four modes depending on the OS and permissions:\r\n * 1. macOS: Uses `lsof -i` polling (no root required)\r\n * 2. Linux: Uses `ss -tnp` polling (no root required)\r\n * 3. Windows: Uses PowerShell `Get-NetTCPConnection` (no admin required)\r\n * 4. Fallback: Stub — events must be fed manually via ingestEvent()\r\n *\r\n * All modes perform async reverse-DNS enrichment so that endpoint patterns\r\n * (pastebin.com, ngrok.io, etc.) match against resolved hostnames rather\r\n * than raw IPs.\r\n */\r\n async startMonitoring(): Promise<void> {\r\n this.startTime = Date.now();\r\n this.events = [];\r\n this.findings = [];\r\n\r\n const method = this.detectMonitoringMethod();\r\n\r\n switch (method) {\r\n case 'lsof-poll':\r\n await this.startLsofPolling();\r\n break;\r\n case 'ss-poll':\r\n await this.startSsPolling();\r\n break;\r\n case 'netstat-poll':\r\n await this.startNetstatPolling();\r\n break;\r\n case 'proxy':\r\n await this.startProxyCapture();\r\n break;\r\n }\r\n }\r\n\r\n /**\r\n * Stop monitoring and generate the report.\r\n */\r\n async stopMonitoring(): Promise<SentinelReport> {\r\n if (this.monitorProcess) {\r\n this.monitorProcess.kill('SIGTERM');\r\n this.monitorProcess = null;\r\n }\r\n\r\n // Run all detection patterns against collected events\r\n this.analyzeEvents();\r\n\r\n const threatScore = calculateThreatScore(this.findings);\r\n const threatLevel = threatLevelFromScore(threatScore);\r\n\r\n const uniqueDestinations = [\r\n ...new Set(\r\n this.events.map(e => {\r\n try { return new URL(e.url).hostname; } catch { return e.url; }\r\n })\r\n ),\r\n ];\r\n\r\n return {\r\n serverName: this.serverName,\r\n monitoringDuration: (Date.now() - this.startTime) / 1000,\r\n totalEvents: this.events.length,\r\n uniqueDestinations,\r\n findings: this.findings,\r\n threatLevel,\r\n threatScore,\r\n startedAt: new Date(this.startTime).toISOString(),\r\n endedAt: new Date().toISOString(),\r\n };\r\n }\r\n\r\n /**\r\n * Feed a single network event into the engine (for real-time analysis).\r\n */\r\n ingestEvent(event: NetworkEvent): void {\r\n this.events.push(event);\r\n this.onEvent?.(event);\r\n\r\n // Run real-time endpoint checks (fast, per-event)\r\n this.checkEndpointPatterns(event);\r\n this.checkCredentialPatterns(event);\r\n }\r\n\r\n // ── Detection Methods ──\r\n\r\n /**\r\n * Run all analysis on collected events.\r\n */\r\n private analyzeEvents(): void {\r\n // 1. Endpoint pattern matching (per-event)\r\n for (const event of this.events) {\r\n this.checkEndpointPatterns(event);\r\n this.checkCredentialPatterns(event);\r\n }\r\n\r\n // 2. Behavioral pattern matching (across time windows)\r\n for (const pattern of BEHAVIORAL_PATTERNS) {\r\n if (this.events.length < pattern.minEvents) continue;\r\n\r\n // Slice events into the time window\r\n const now = Date.now();\r\n const windowStart = now - pattern.timeWindowSeconds * 1000;\r\n const windowEvents = this.events.filter(e => e.timestamp >= windowStart);\r\n\r\n if (windowEvents.length < pattern.minEvents) continue;\r\n\r\n const confidence = pattern.detect(windowEvents);\r\n if (confidence > 25) {\r\n // Only report if we haven't already found this pattern\r\n if (!this.findings.some(f => f.id === pattern.id)) {\r\n this.findings.push({\r\n id: pattern.id,\r\n category: pattern.id.startsWith('SN-01') ? 'c2-beaconing' :\r\n pattern.id === 'SN-012' ? 'dns-tunneling' :\r\n pattern.id === 'SN-013' ? 'covert-channel' : 'phone-home',\r\n severity: pattern.severity,\r\n title: pattern.title,\r\n description: pattern.description,\r\n serverName: this.serverName,\r\n evidence: windowEvents.slice(0, 10), // Cap evidence at 10 events\r\n recommendation: pattern.recommendation,\r\n confidence,\r\n });\r\n }\r\n }\r\n }\r\n\r\n // De-duplicate findings (keep highest confidence per ID)\r\n const seen = new Map<string, SentinelFinding>();\r\n for (const f of this.findings) {\r\n const existing = seen.get(f.id);\r\n if (!existing || f.confidence > existing.confidence) {\r\n seen.set(f.id, f);\r\n }\r\n }\r\n this.findings = Array.from(seen.values());\r\n }\r\n\r\n private checkEndpointPatterns(event: NetworkEvent): void {\r\n for (const pattern of SUSPICIOUS_ENDPOINT_PATTERNS) {\r\n if (pattern.urlPattern.test(event.url)) {\r\n if (!this.findings.some(f => f.id === pattern.id && f.evidence[0]?.url === event.url)) {\r\n this.findings.push({\r\n id: pattern.id,\r\n category: 'phone-home',\r\n severity: pattern.severity,\r\n title: pattern.title,\r\n description: pattern.description,\r\n serverName: this.serverName,\r\n evidence: [event],\r\n recommendation: pattern.recommendation,\r\n confidence: 95,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n private checkCredentialPatterns(event: NetworkEvent): void {\r\n for (const pattern of CREDENTIAL_EXFIL_PATTERNS) {\r\n if (pattern.urlPattern.test(event.url)) {\r\n this.findings.push({\r\n id: pattern.id,\r\n category: 'data-exfiltration',\r\n severity: pattern.severity,\r\n title: pattern.title,\r\n description: pattern.description,\r\n serverName: this.serverName,\r\n evidence: [event],\r\n recommendation: pattern.recommendation,\r\n confidence: 98,\r\n });\r\n }\r\n }\r\n }\r\n\r\n // ── Monitoring Methods ──\r\n\r\n private detectMonitoringMethod(): 'lsof-poll' | 'ss-poll' | 'netstat-poll' | 'proxy' {\r\n // Windows: PowerShell Get-NetTCPConnection is always available on Win 8+\r\n if (process.platform === 'win32') {\r\n return 'netstat-poll';\r\n }\r\n\r\n // macOS: lsof is always present — skip `which` probe entirely\r\n if (process.platform === 'darwin') {\r\n return 'lsof-poll';\r\n }\r\n\r\n // Linux: prefer ss (iproute2), fall back to lsof\r\n try {\r\n execFileSync('which', ['ss'], { stdio: 'pipe' });\r\n return 'ss-poll';\r\n } catch {\r\n try {\r\n execFileSync('which', ['lsof'], { stdio: 'pipe' });\r\n return 'lsof-poll';\r\n } catch {\r\n return 'proxy';\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * macOS: Poll `lsof -i` to capture network connections.\r\n * No root required. DNS-enriches IPs → hostnames before pattern matching.\r\n */\r\n private async startLsofPolling(): Promise<void> {\r\n const safeName = this.serverName.replace(/[^a-zA-Z0-9._-]/g, '');\r\n\r\n const pollInterval = setInterval(async () => {\r\n try {\r\n const lsofOutput = execFileSync('/usr/sbin/lsof', ['-i', '-n', '-P'], {\r\n timeout: 5000,\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'pipe'],\r\n });\r\n\r\n const lines = lsofOutput\r\n .split('\\n')\r\n .filter(line => line.toLowerCase().includes(safeName.toLowerCase()))\r\n .filter(Boolean);\r\n\r\n // Parse all lines, then DNS-enrich all IPs in parallel before ingesting\r\n const rawEvents = lines.map(l => this.parseLsofLine(l)).filter((e): e is NetworkEvent => e !== null);\r\n const enriched = await Promise.all(rawEvents.map(e => this.enrichWithHostname(e)));\r\n for (const event of enriched) this.ingestEvent(event);\r\n } catch {\r\n // lsof failed, continue\r\n }\r\n }, 2000);\r\n\r\n this.monitorProcess = { kill: () => clearInterval(pollInterval) } as unknown as ChildProcess;\r\n setTimeout(() => clearInterval(pollInterval), this.durationSeconds * 1000);\r\n }\r\n\r\n /**\r\n * Linux: Poll `ss -tnp` (socket statistics) for network connections.\r\n * No root required. DNS-enriches IPs → hostnames before pattern matching.\r\n */\r\n private async startSsPolling(): Promise<void> {\r\n const safeName = this.serverName.replace(/[^a-zA-Z0-9._-]/g, '');\r\n\r\n const pollInterval = setInterval(async () => {\r\n try {\r\n const ssOutput = execFileSync('ss', ['-tnp'], {\r\n timeout: 5000,\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'pipe'],\r\n });\r\n\r\n const lines = ssOutput\r\n .split('\\n')\r\n .filter(line => line.includes(safeName))\r\n .filter(Boolean);\r\n\r\n const rawEvents = lines.map(l => this.parseSsLine(l)).filter((e): e is NetworkEvent => e !== null);\r\n const enriched = await Promise.all(rawEvents.map(e => this.enrichWithHostname(e)));\r\n for (const event of enriched) this.ingestEvent(event);\r\n } catch {\r\n // ss failed, continue\r\n }\r\n }, 2000);\r\n\r\n this.monitorProcess = { kill: () => clearInterval(pollInterval) } as unknown as ChildProcess;\r\n setTimeout(() => clearInterval(pollInterval), this.durationSeconds * 1000);\r\n }\r\n\r\n /**\r\n * Windows: Poll PowerShell `Get-NetTCPConnection` for established connections.\r\n * No admin required (available on Windows 8+ / Server 2012+).\r\n * DNS-enriches IPs → hostnames so endpoint patterns match correctly.\r\n *\r\n * Note: Unlike lsof/ss, this captures ALL machine connections (not filtered\r\n * by process name) because Windows process-to-connection mapping requires\r\n * admin rights. Vigil's patterns handle the noise — it flags what matters.\r\n */\r\n private async startNetstatPolling(): Promise<void> {\r\n // PowerShell command: get established external TCP connections as CSV\r\n const psCommand =\r\n `Get-NetTCPConnection -State Established | ` +\r\n `Where-Object { $_.RemoteAddress -notmatch '^(127\\\\.|::1$|0\\\\.0\\\\.0\\\\.0$|::$)' } | ` +\r\n `Select-Object LocalAddress,LocalPort,RemoteAddress,RemotePort | ` +\r\n `ConvertTo-Csv -NoTypeInformation`;\r\n\r\n const pollInterval = setInterval(async () => {\r\n try {\r\n const output = execFileSync('powershell', [\r\n '-NoProfile',\r\n '-NonInteractive',\r\n '-Command',\r\n psCommand,\r\n ], {\r\n timeout: 8000, // PowerShell startup is slower than lsof\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'pipe'],\r\n });\r\n\r\n // CSV rows: skip header, parse each line\r\n const lines = output.split('\\n').slice(1).map(l => l.trim()).filter(Boolean);\r\n const rawEvents = lines.map(l => this.parsePowerShellLine(l)).filter((e): e is NetworkEvent => e !== null);\r\n const enriched = await Promise.all(rawEvents.map(e => this.enrichWithHostname(e)));\r\n for (const event of enriched) this.ingestEvent(event);\r\n } catch {\r\n // PowerShell failed or unavailable\r\n }\r\n }, 3000); // 3s interval — PS startup adds ~1-2s overhead\r\n\r\n this.monitorProcess = { kill: () => clearInterval(pollInterval) } as unknown as ChildProcess;\r\n setTimeout(() => clearInterval(pollInterval), this.durationSeconds * 1000);\r\n }\r\n\r\n /**\r\n * Fallback: Start a lightweight MITM proxy that MCP traffic routes through.\r\n * This requires the runtime proxy (Phase 3) to be active.\r\n */\r\n private async startProxyCapture(): Promise<void> {\r\n // Proxy mode requires the full runtime proxy infrastructure (Phase 3).\r\n // For Phase 2, this is a stub that collects events fed via ingestEvent().\r\n console.warn(\r\n '[Sentinel] Proxy capture requires the runtime proxy (coming soon). ' +\r\n 'Using manual event ingestion mode.'\r\n );\r\n }\r\n\r\n // ── Parsers ──\r\n\r\n private parseLsofLine(line: string): NetworkEvent | null {\r\n // lsof output format: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\r\n // NAME for TCP: host:port->remote:port (ESTABLISHED)\r\n const match = line.match(\r\n /(\\S+)\\s+(\\d+)\\s+\\S+\\s+\\S+\\s+IPv[46]\\s+\\S+\\s+\\S+\\s+TCP\\s+\\S+->(\\S+):(\\d+)\\s/\r\n );\r\n if (!match) return null;\r\n\r\n const [, _command, _pid, remoteHost, remotePort] = match;\r\n\r\n return {\r\n timestamp: Date.now(),\r\n serverName: this.serverName,\r\n method: 'TCP',\r\n url: `https://${remoteHost}:${remotePort}/`,\r\n destinationIp: remoteHost,\r\n port: parseInt(remotePort, 10),\r\n requestSize: 0,\r\n tls: parseInt(remotePort, 10) === 443,\r\n };\r\n }\r\n\r\n private parseSsLine(line: string): NetworkEvent | null {\r\n // ss output: State Recv-Q Send-Q Local:Port Peer:Port Process\r\n const match = line.match(\r\n /ESTAB\\s+\\d+\\s+(\\d+)\\s+\\S+\\s+(\\S+):(\\d+)/\r\n );\r\n if (!match) return null;\r\n\r\n const [, sendQueue, remoteHost, remotePort] = match;\r\n\r\n return {\r\n timestamp: Date.now(),\r\n serverName: this.serverName,\r\n method: 'TCP',\r\n url: `https://${remoteHost}:${remotePort}/`,\r\n destinationIp: remoteHost,\r\n port: parseInt(remotePort, 10),\r\n requestSize: parseInt(sendQueue, 10),\r\n tls: parseInt(remotePort, 10) === 443,\r\n };\r\n }\r\n\r\n /**\r\n * Windows: Parse a CSV row from PowerShell `Get-NetTCPConnection | ConvertTo-Csv`.\r\n * Format: \"LocalAddress\",\"LocalPort\",\"RemoteAddress\",\"RemotePort\"\r\n * Example: \"192.168.1.5\",\"54123\",\"172.64.155.209\",\"443\"\r\n */\r\n private parsePowerShellLine(line: string): NetworkEvent | null {\r\n // CSV values may be quoted or unquoted depending on PS version\r\n const match = line.match(/^\"?([^\",]+)\"?,\"\\d+\",\"?([^\",]+)\"?,\"?(\\d+)\"?/);\r\n if (!match) return null;\r\n\r\n const [, , remoteAddress, remotePort] = match;\r\n const port = parseInt(remotePort, 10);\r\n\r\n // Skip RFC-1918 private addresses and loopback (already filtered by PS\r\n // but guard here in case the regex misses edge cases)\r\n if (\r\n remoteAddress === '127.0.0.1' ||\r\n remoteAddress === '::1' ||\r\n remoteAddress.startsWith('10.') ||\r\n remoteAddress.startsWith('192.168.') ||\r\n /^172\\.(1[6-9]|2\\d|3[01])\\./.test(remoteAddress)\r\n ) {\r\n return null;\r\n }\r\n\r\n return {\r\n timestamp: Date.now(),\r\n serverName: this.serverName,\r\n method: 'TCP',\r\n url: `https://${remoteAddress}:${remotePort}/`,\r\n destinationIp: remoteAddress,\r\n port,\r\n requestSize: 0,\r\n tls: port === 443,\r\n };\r\n }\r\n\r\n // ── DNS Enrichment ──\r\n\r\n /**\r\n * Reverse-DNS lookup with a session-scoped cache.\r\n * Returns the first PTR record if available, otherwise the raw IP.\r\n *\r\n * WHY this matters: Sentinel's endpoint patterns match against known-bad\r\n * hostnames (pastebin.com, ngrok.io, etc.). Without DNS enrichment, lsof/ss/\r\n * netstat all return raw IPs and those patterns would silently miss every hit.\r\n */\r\n private async resolveIp(ip: string): Promise<string> {\r\n const cached = this.dnsCache.get(ip);\r\n if (cached !== undefined) return cached;\r\n\r\n try {\r\n const hostnames = await dnsPromises.reverse(ip);\r\n const hostname = hostnames[0] ?? ip;\r\n this.dnsCache.set(ip, hostname);\r\n return hostname;\r\n } catch {\r\n // Reverse DNS failed (common for CDN IPs) — fall back to raw IP\r\n this.dnsCache.set(ip, ip);\r\n return ip;\r\n }\r\n }\r\n\r\n /**\r\n * Enrich a NetworkEvent's url field with a resolved hostname.\r\n * Returns a new event object (does not mutate the original).\r\n */\r\n private async enrichWithHostname(event: NetworkEvent): Promise<NetworkEvent> {\r\n if (!event.destinationIp) return event;\r\n const hostname = await this.resolveIp(event.destinationIp);\r\n if (hostname === event.destinationIp) return event; // No change\r\n return {\r\n ...event,\r\n url: `https://${hostname}:${event.port}/`,\r\n };\r\n }\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// SUBSCRIPTION GATING\r\n// Sentinel is a premium feature (Pro tier and above).\r\n// ──────────────────────────────────────────────────────────\r\n\r\n// Launch tiers: free + pro only. Team and enterprise planned for future builds.\r\nexport type SubscriptionTier = 'free' | 'pro';\r\n\r\nexport interface SentinelFeatureGate {\r\n /** Whether Sentinel monitoring is available */\r\n monitoringEnabled: boolean;\r\n /** Maximum monitoring duration in seconds */\r\n maxDurationSeconds: number;\r\n /** Maximum number of servers to monitor simultaneously */\r\n maxConcurrentServers: number;\r\n /** Whether behavioral detection is available (vs. endpoint-only) */\r\n behavioralDetection: boolean;\r\n /** Whether real-time alerts are enabled */\r\n realTimeAlerts: boolean;\r\n /** Whether historical Sentinel data is stored */\r\n historyRetentionDays: number;\r\n /** Whether API access to Sentinel data is available */\r\n apiAccess: boolean;\r\n}\r\n\r\nexport function getSentinelFeatures(tier: SubscriptionTier): SentinelFeatureGate {\r\n switch (tier) {\r\n case 'free':\r\n return {\r\n monitoringEnabled: false, // Upgrade required\r\n maxDurationSeconds: 0,\r\n maxConcurrentServers: 0,\r\n behavioralDetection: false,\r\n realTimeAlerts: false,\r\n historyRetentionDays: 0,\r\n apiAccess: false,\r\n };\r\n case 'pro':\r\n return {\r\n monitoringEnabled: true,\r\n maxDurationSeconds: 300, // 5 minutes\r\n maxConcurrentServers: 3,\r\n behavioralDetection: true,\r\n realTimeAlerts: false, // Future: Team+ only\r\n historyRetentionDays: 7,\r\n apiAccess: true,\r\n };\r\n // Future tiers — uncomment when team/enterprise plans launch\r\n // case 'team':\r\n // return {\r\n // monitoringEnabled: true,\r\n // maxDurationSeconds: 1800, // 30 minutes\r\n // maxConcurrentServers: 10,\r\n // behavioralDetection: true,\r\n // realTimeAlerts: true,\r\n // historyRetentionDays: 30,\r\n // apiAccess: true,\r\n // };\r\n // case 'enterprise':\r\n // return {\r\n // monitoringEnabled: true,\r\n // maxDurationSeconds: -1, // Unlimited (continuous)\r\n // maxConcurrentServers: -1, // Unlimited\r\n // behavioralDetection: true,\r\n // realTimeAlerts: true,\r\n // historyRetentionDays: 365,\r\n // apiAccess: true,\r\n // };\r\n }\r\n}\r\n\r\n/**\r\n * Pretty marketing name for tier-gated Sentinel features.\r\n * Used in CLI output and web UI.\r\n */\r\nexport const SENTINEL_MARKETING = {\r\n tagline: 'Always watching what your tools are doing.',\r\n featureName: 'Vigile Sentinel',\r\n tierName: 'Sentinel Protection',\r\n upgradePrompt:\r\n '🛡️ Vigile Sentinel is a Pro feature. Upgrade at https://vigile.dev/pricing to monitor your MCP servers for real-time phone-home detection.',\r\n categories: [\r\n { icon: '📡', name: 'C2 Beaconing Detection', desc: 'Catches tools phoning home on a schedule' },\r\n { icon: '🔐', name: 'Credential Theft Alerts', desc: 'Detects SSH keys & API tokens leaving your machine' },\r\n { icon: '🕵️', name: 'DNS Tunneling Detection', desc: 'Spots data hidden in DNS queries' },\r\n { icon: '📊', name: 'Behavioral Analysis', desc: 'Machine-learning-ready traffic pattern analysis' },\r\n { icon: '⚡', name: 'Real-Time Alerts', desc: 'Instant notification when threats are detected' },\r\n { icon: '🛡️', name: 'Continuous Monitoring', desc: '24/7 protection for enterprise environments' },\r\n ],\r\n} as const;\r\n","// ============================================================\r\n// Vigile Sentinel — Runtime Phone-Home Detection Patterns\r\n// ============================================================\r\n// These patterns analyze runtime network behavior of MCP servers\r\n// to detect data exfiltration, C2 beaconing, and covert channels.\r\n//\r\n// Unlike static patterns (patterns.ts) which scan tool descriptions\r\n// BEFORE execution, Sentinel patterns analyze LIVE network traffic\r\n// DURING execution. This catches tools that look clean on paper\r\n// but phone home at runtime.\r\n//\r\n// Detection categories:\r\n// - C2 Beaconing (regular intervals, heartbeat patterns)\r\n// - Data Exfiltration (credential theft, file uploads)\r\n// - DNS Tunneling (encoded data in DNS queries)\r\n// - Covert Channels (steganographic exfil, timing-based)\r\n// - Suspicious Endpoints (known bad IPs, DGA domains)\r\n\r\nimport type { Severity, FindingCategory } from '../types/index.js';\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Sentinel-specific types\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport interface NetworkEvent {\r\n /** Timestamp of the network event */\r\n timestamp: number;\r\n /** Source process / MCP server name */\r\n serverName: string;\r\n /** HTTP method or protocol */\r\n method: string;\r\n /** Full URL or hostname */\r\n url: string;\r\n /** Destination IP address (resolved) */\r\n destinationIp?: string;\r\n /** Destination port */\r\n port: number;\r\n /** Request size in bytes */\r\n requestSize: number;\r\n /** Response size in bytes */\r\n responseSize?: number;\r\n /** HTTP status code */\r\n statusCode?: number;\r\n /** Request headers */\r\n headers?: Record<string, string>;\r\n /** DNS query type (for DNS events) */\r\n dnsQueryType?: string;\r\n /** Whether TLS was used */\r\n tls: boolean;\r\n /** Request body hash (we never store raw bodies) */\r\n bodyHash?: string;\r\n /** Estimated entropy of request body (0-8 bits/byte) */\r\n bodyEntropy?: number;\r\n}\r\n\r\nexport interface SentinelFinding {\r\n /** Unique finding ID (SN-series) */\r\n id: string;\r\n /** Category */\r\n category: FindingCategory | 'c2-beaconing' | 'dns-tunneling' | 'covert-channel' | 'phone-home';\r\n /** Severity */\r\n severity: Severity;\r\n /** Short title */\r\n title: string;\r\n /** Detailed description */\r\n description: string;\r\n /** The server that triggered this */\r\n serverName: string;\r\n /** Evidence — the specific network events that triggered this */\r\n evidence: NetworkEvent[];\r\n /** Recommendation */\r\n recommendation: string;\r\n /** Confidence score (0-100) — how sure Sentinel is this is malicious */\r\n confidence: number;\r\n}\r\n\r\nexport type SentinelThreatLevel = 'clean' | 'suspicious' | 'malicious' | 'critical';\r\n\r\nexport interface SentinelReport {\r\n /** Server being monitored */\r\n serverName: string;\r\n /** Monitoring duration in seconds */\r\n monitoringDuration: number;\r\n /** Total network events captured */\r\n totalEvents: number;\r\n /** Unique destinations contacted */\r\n uniqueDestinations: string[];\r\n /** Sentinel findings */\r\n findings: SentinelFinding[];\r\n /** Overall threat level */\r\n threatLevel: SentinelThreatLevel;\r\n /** Threat score (0-100, inverse of trust — higher = more dangerous) */\r\n threatScore: number;\r\n /** When monitoring started */\r\n startedAt: string;\r\n /** When monitoring ended */\r\n endedAt: string;\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// ENDPOINT REPUTATION PATTERNS\r\n// Known malicious or suspicious destination patterns.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport interface EndpointPattern {\r\n id: string;\r\n severity: Severity;\r\n title: string;\r\n /** Regex to match against the full URL */\r\n urlPattern: RegExp;\r\n /** Regex to match against destination IP (optional) */\r\n ipPattern?: RegExp;\r\n description: string;\r\n recommendation: string;\r\n}\r\n\r\nexport const SUSPICIOUS_ENDPOINT_PATTERNS: EndpointPattern[] = [\r\n {\r\n id: 'SN-001',\r\n severity: 'critical',\r\n title: 'Known data exfiltration endpoint',\r\n urlPattern: /https?:\\/\\/[^/]*(?:pastebin\\.com|hastebin\\.com|ghostbin\\.co|paste\\.ee|dpaste\\.org|transfer\\.sh|file\\.io|0x0\\.st|ix\\.io)\\/(?:api|raw|upload|documents)/i,\r\n description:\r\n 'MCP server is sending data to a paste/file sharing service commonly used for data exfiltration.',\r\n recommendation:\r\n 'CRITICAL: Stop this MCP server immediately. Legitimate tools should never upload to paste services.',\r\n },\r\n {\r\n id: 'SN-002',\r\n severity: 'critical',\r\n title: 'Webhook exfiltration channel',\r\n urlPattern: /https?:\\/\\/(?:hooks\\.slack\\.com|discord(?:app)?\\.com\\/api\\/webhooks|webhook\\.site|pipedream\\.net|requestbin\\.|hookbin\\.|beeceptor\\.com)/i,\r\n description:\r\n 'MCP server is sending data to a webhook endpoint. Attackers commonly use webhook services as low-noise exfiltration channels.',\r\n recommendation:\r\n 'Investigate what data is being sent to this webhook. This is a common attacker exfiltration method.',\r\n },\r\n {\r\n id: 'SN-003',\r\n severity: 'high',\r\n title: 'Dynamic DNS destination',\r\n urlPattern: /https?:\\/\\/[^/]*\\.(?:duckdns\\.org|no-ip\\.com|ngrok\\.io|ngrok-free\\.app|serveo\\.net|localhost\\.run|bore\\.digital|tailscale\\.io)(?:\\/|$)/i,\r\n description:\r\n 'MCP server is connecting to a dynamic DNS or tunneling service, commonly used for C2 infrastructure.',\r\n recommendation:\r\n 'Review this connection. Dynamic DNS is frequently used by attackers to rotate C2 endpoints.',\r\n },\r\n {\r\n id: 'SN-004',\r\n severity: 'high',\r\n title: 'Cryptocurrency-related endpoint',\r\n urlPattern: /https?:\\/\\/[^/]*(?:blockchain\\.info|etherscan\\.io|bscscan\\.com|solscan\\.io|mempool\\.space)\\/(?:api|rawaddr|tx|address)/i,\r\n description:\r\n 'MCP server is querying cryptocurrency blockchain APIs, which could indicate wallet scanning or theft.',\r\n recommendation:\r\n 'Unless this is an explicitly crypto-related tool, this connection is highly suspicious.',\r\n },\r\n {\r\n id: 'SN-005',\r\n severity: 'medium',\r\n title: 'Telemetry to unknown endpoint',\r\n urlPattern: /https?:\\/\\/[^/]*(?:\\/(?:telemetry|analytics|tracking|pixel|beacon|collect|event|metrics|heartbeat))(?:\\?|$|\\/)/i,\r\n description:\r\n 'MCP server is sending telemetry/analytics data. While sometimes legitimate, this can mask data exfiltration.',\r\n recommendation:\r\n 'Verify the telemetry destination is expected. Compare against the MCP server\\'s documentation.',\r\n },\r\n {\r\n id: 'SN-006',\r\n severity: 'critical',\r\n title: 'Raw IP connection (no hostname)',\r\n urlPattern: /https?:\\/\\/(?:\\d{1,3}\\.){3}\\d{1,3}(?::\\d+)?(?:\\/|$)/,\r\n description:\r\n 'MCP server is connecting directly to an IP address instead of a hostname. This bypasses DNS-based monitoring and is a strong indicator of C2 communication.',\r\n recommendation:\r\n 'CRITICAL: Direct IP connections are almost never legitimate for MCP servers. Investigate immediately.',\r\n },\r\n {\r\n id: 'SN-007',\r\n severity: 'high',\r\n title: 'Non-standard port connection',\r\n urlPattern: /https?:\\/\\/[^/]+:(?!80\\b|443\\b|8080\\b|8443\\b|3000\\b|5000\\b|8000\\b)\\d{4,5}(?:\\/|$)/,\r\n description:\r\n 'MCP server is connecting to a non-standard port. While sometimes legitimate, C2 infrastructure often uses unusual ports.',\r\n recommendation:\r\n 'Verify this port is expected for the service being contacted.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// BEHAVIORAL DETECTION PATTERNS\r\n// These don't match URLs — they match *patterns of behavior*\r\n// across multiple network events. The Sentinel engine\r\n// evaluates these over time windows.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport interface BehavioralPattern {\r\n id: string;\r\n severity: Severity;\r\n title: string;\r\n description: string;\r\n recommendation: string;\r\n /** Minimum number of events needed to trigger */\r\n minEvents: number;\r\n /** Time window in seconds to analyze */\r\n timeWindowSeconds: number;\r\n /** The detection function — returns confidence 0-100 */\r\n detect: (events: NetworkEvent[]) => number;\r\n}\r\n\r\nexport const BEHAVIORAL_PATTERNS: BehavioralPattern[] = [\r\n {\r\n id: 'SN-010',\r\n severity: 'critical',\r\n title: 'C2 beaconing detected (regular interval)',\r\n description:\r\n 'MCP server is making network requests at regular intervals to the same destination, consistent with command-and-control beaconing. Malware phones home on a schedule to receive commands.',\r\n recommendation:\r\n 'CRITICAL: This pattern strongly indicates the MCP server is compromised. Stop it immediately and investigate the destination.',\r\n minEvents: 5,\r\n timeWindowSeconds: 300, // 5 minutes\r\n detect: (events: NetworkEvent[]): number => {\r\n // Group events by destination\r\n const byDest = new Map<string, number[]>();\r\n for (const e of events) {\r\n const dest = new URL(e.url).hostname;\r\n if (!byDest.has(dest)) byDest.set(dest, []);\r\n byDest.get(dest)!.push(e.timestamp);\r\n }\r\n\r\n let maxConfidence = 0;\r\n for (const [, timestamps] of byDest) {\r\n if (timestamps.length < 5) continue;\r\n timestamps.sort((a, b) => a - b);\r\n\r\n // Calculate intervals between consecutive requests\r\n const intervals: number[] = [];\r\n for (let i = 1; i < timestamps.length; i++) {\r\n intervals.push(timestamps[i] - timestamps[i - 1]);\r\n }\r\n\r\n // Calculate coefficient of variation (std/mean)\r\n // Low CV = regular intervals = beaconing\r\n const mean = intervals.reduce((s, v) => s + v, 0) / intervals.length;\r\n const std = Math.sqrt(\r\n intervals.reduce((s, v) => s + (v - mean) ** 2, 0) / intervals.length\r\n );\r\n const cv = mean > 0 ? std / mean : 1;\r\n\r\n // CV < 0.15 = very regular (high confidence beaconing)\r\n // CV < 0.30 = somewhat regular (medium confidence)\r\n // Add jitter tolerance — sophisticated C2 adds random jitter\r\n if (cv < 0.15) maxConfidence = Math.max(maxConfidence, 95);\r\n else if (cv < 0.25) maxConfidence = Math.max(maxConfidence, 80);\r\n else if (cv < 0.35) maxConfidence = Math.max(maxConfidence, 60);\r\n }\r\n return maxConfidence;\r\n },\r\n },\r\n {\r\n id: 'SN-011',\r\n severity: 'critical',\r\n title: 'Burst data exfiltration',\r\n description:\r\n 'MCP server sent an unusually large amount of data in a short burst to an external endpoint. This pattern matches credential dump exfiltration and file theft.',\r\n recommendation:\r\n 'CRITICAL: Investigate what data was transmitted. Check for stolen SSH keys, credentials, or source code.',\r\n minEvents: 1,\r\n timeWindowSeconds: 60, // 1 minute\r\n detect: (events: NetworkEvent[]): number => {\r\n // Look for single large requests or bursts of requests\r\n const LARGE_REQUEST_THRESHOLD = 50_000; // 50KB in a single request\r\n const BURST_THRESHOLD = 200_000; // 200KB total in time window\r\n\r\n let maxSingle = 0;\r\n let totalBytes = 0;\r\n\r\n for (const e of events) {\r\n maxSingle = Math.max(maxSingle, e.requestSize);\r\n totalBytes += e.requestSize;\r\n }\r\n\r\n if (maxSingle > LARGE_REQUEST_THRESHOLD) return 90;\r\n if (totalBytes > BURST_THRESHOLD) return 85;\r\n if (totalBytes > BURST_THRESHOLD / 2) return 60;\r\n return 0;\r\n },\r\n },\r\n {\r\n id: 'SN-012',\r\n severity: 'high',\r\n title: 'DNS tunneling suspected',\r\n description:\r\n 'MCP server is making an unusual number of DNS queries with high-entropy subdomains. This pattern matches DNS tunneling — a technique to exfiltrate data by encoding it in DNS queries, which often bypass firewalls.',\r\n recommendation:\r\n 'Investigate the DNS queries. DNS tunneling uses encoded data in subdomain labels (e.g., aGVsbG8=.evil.com).',\r\n minEvents: 10,\r\n timeWindowSeconds: 120,\r\n detect: (events: NetworkEvent[]): number => {\r\n // Filter DNS events and look for high-entropy subdomain patterns\r\n const dnsEvents = events.filter(e => e.dnsQueryType);\r\n if (dnsEvents.length < 10) return 0;\r\n\r\n let suspiciousCount = 0;\r\n for (const e of dnsEvents) {\r\n try {\r\n const hostname = new URL(e.url).hostname;\r\n const parts = hostname.split('.');\r\n // DNS tunneling creates long, random-looking subdomains\r\n for (const part of parts) {\r\n if (part.length > 30) suspiciousCount++;\r\n // Check for base64-like patterns in subdomain\r\n if (/^[A-Za-z0-9+/]{20,}={0,2}$/.test(part)) suspiciousCount++;\r\n }\r\n } catch {\r\n // URL parse failed, skip\r\n }\r\n }\r\n\r\n const ratio = suspiciousCount / dnsEvents.length;\r\n if (ratio > 0.5) return 90;\r\n if (ratio > 0.3) return 70;\r\n if (ratio > 0.1) return 40;\r\n return 0;\r\n },\r\n },\r\n {\r\n id: 'SN-013',\r\n severity: 'high',\r\n title: 'Multi-destination scatter exfiltration',\r\n description:\r\n 'MCP server is distributing data across many different external destinations in a short period. Sophisticated exfiltration splits data across multiple endpoints to avoid size-based detection.',\r\n recommendation:\r\n 'Review all destinations contacted. This pattern suggests deliberate evasion of single-destination monitoring.',\r\n minEvents: 5,\r\n timeWindowSeconds: 120,\r\n detect: (events: NetworkEvent[]): number => {\r\n const destinations = new Set<string>();\r\n for (const e of events) {\r\n try {\r\n destinations.add(new URL(e.url).hostname);\r\n } catch {\r\n // skip\r\n }\r\n }\r\n\r\n // If contacting >10 unique destinations with data, very suspicious\r\n const withData = events.filter(e => e.requestSize > 500);\r\n const destWithData = new Set<string>();\r\n for (const e of withData) {\r\n try { destWithData.add(new URL(e.url).hostname); } catch { /* skip */ }\r\n }\r\n\r\n if (destWithData.size > 10) return 85;\r\n if (destWithData.size > 5) return 65;\r\n if (destWithData.size > 3 && withData.length > events.length * 0.5) return 50;\r\n return 0;\r\n },\r\n },\r\n {\r\n id: 'SN-014',\r\n severity: 'high',\r\n title: 'High-entropy payload transmission',\r\n description:\r\n 'MCP server is sending request bodies with unusually high entropy (randomness), suggesting encrypted or compressed data exfiltration. Legitimate API calls typically have structured, lower-entropy payloads.',\r\n recommendation:\r\n 'Investigate the payload contents. High entropy in outbound data often indicates stolen credentials or encrypted exfiltration.',\r\n minEvents: 3,\r\n timeWindowSeconds: 180,\r\n detect: (events: NetworkEvent[]): number => {\r\n // Look for events with high body entropy\r\n const highEntropyEvents = events.filter(\r\n e => e.bodyEntropy !== undefined && e.bodyEntropy > 7.0 && e.requestSize > 1000\r\n );\r\n\r\n if (highEntropyEvents.length === 0) return 0;\r\n const ratio = highEntropyEvents.length / events.length;\r\n\r\n if (ratio > 0.5 && highEntropyEvents.length >= 5) return 90;\r\n if (ratio > 0.3) return 70;\r\n if (highEntropyEvents.length >= 3) return 55;\r\n return 0;\r\n },\r\n },\r\n {\r\n id: 'SN-015',\r\n severity: 'medium',\r\n title: 'Unexpected outbound connection during idle',\r\n description:\r\n 'MCP server made network requests when no user-initiated tool calls were active. Legitimate MCP servers should only make network requests in response to tool invocations.',\r\n recommendation:\r\n 'Review why this MCP server is making network requests when idle. This could indicate background beaconing or telemetry.',\r\n minEvents: 2,\r\n timeWindowSeconds: 60,\r\n detect: (events: NetworkEvent[]): number => {\r\n // This detection relies on the caller marking events as \"idle\" context\r\n // For now, we detect based on timing — requests that happen without\r\n // a preceding tool call within 5 seconds\r\n // (The full implementation requires integration with the runtime proxy)\r\n if (events.length >= 3) return 50;\r\n if (events.length >= 2) return 30;\r\n return 0;\r\n },\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// CREDENTIAL-SPECIFIC EXFILTRATION PATTERNS\r\n// Patterns that look for credential-shaped data in network\r\n// requests. These check request URLs, headers, and body hashes\r\n// for signs of stolen secrets being transmitted.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport const CREDENTIAL_EXFIL_PATTERNS: EndpointPattern[] = [\r\n {\r\n id: 'SN-020',\r\n severity: 'critical',\r\n title: 'SSH key in outbound request',\r\n urlPattern: /[?&](?:key|data|payload|content|body)=[^&]*(?:ssh-rsa|ssh-ed25519|PRIVATE\\s*KEY)/i,\r\n description:\r\n 'MCP server appears to be transmitting SSH key material in a URL parameter. This matches the Invariant Labs exfiltration attack vector.',\r\n recommendation:\r\n 'CRITICAL: Your SSH keys may be compromised. Rotate all SSH keys immediately.',\r\n },\r\n {\r\n id: 'SN-021',\r\n severity: 'critical',\r\n title: 'API key/token in outbound URL',\r\n urlPattern: /[?&](?:key|token|secret|api_key|apikey|auth|password|credential)=(?!(?:test|demo|example|placeholder))[A-Za-z0-9_-]{20,}/i,\r\n description:\r\n 'MCP server is transmitting what appears to be an API key or token in a URL parameter to an external destination.',\r\n recommendation:\r\n 'CRITICAL: Rotate the compromised API key/token immediately. Check your .env files for exposed secrets.',\r\n },\r\n {\r\n id: 'SN-022',\r\n severity: 'critical',\r\n title: 'AWS credential in outbound request',\r\n urlPattern: /(?:AKIA[0-9A-Z]{16}|(?:aws_secret_access_key|aws_access_key_id)\\s*[:=]\\s*[A-Za-z0-9/+=]{20,})/i,\r\n description:\r\n 'MCP server is transmitting AWS credentials. AWS access keys follow a known format (AKIA...).',\r\n recommendation:\r\n 'CRITICAL: Deactivate the compromised AWS keys immediately via IAM console.',\r\n },\r\n];\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// THREAT SCORING\r\n// Calculate a Sentinel threat score based on findings.\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport function calculateThreatScore(findings: SentinelFinding[]): number {\r\n if (findings.length === 0) return 0;\r\n\r\n let score = 0;\r\n for (const f of findings) {\r\n const severityWeight =\r\n f.severity === 'critical' ? 30 :\r\n f.severity === 'high' ? 20 :\r\n f.severity === 'medium' ? 10 :\r\n f.severity === 'low' ? 5 : 2;\r\n\r\n // Weight by confidence — a 90% confidence critical is scarier than a 40% one\r\n score += severityWeight * (f.confidence / 100);\r\n }\r\n\r\n // Cap at 100\r\n return Math.min(100, Math.round(score));\r\n}\r\n\r\nexport function threatLevelFromScore(score: number): SentinelThreatLevel {\r\n if (score >= 70) return 'critical';\r\n if (score >= 40) return 'malicious';\r\n if (score >= 15) return 'suspicious';\r\n return 'clean';\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Terminal Output Formatter\r\n// ============================================================\r\n// Pretty-prints scan results with color-coded trust scores\r\n// and categorized findings for both MCP servers and skills.\r\n\r\nimport chalk from 'chalk';\r\nimport type {\r\n ScanResult,\r\n SkillScanResult,\r\n ScanSummary,\r\n Finding,\r\n TrustLevel,\r\n Severity,\r\n SkillFileType,\r\n} from '../types/index.js';\r\nimport type { SentinelReport, SentinelThreatLevel } from '../sentinel/index.js';\r\nimport { SENTINEL_MARKETING } from '../sentinel/index.js';\r\n\r\nconst TRUST_COLORS: Record<TrustLevel, (text: string) => string> = {\r\n trusted: chalk.green,\r\n caution: chalk.yellow,\r\n risky: chalk.hex('#FF8C00'), // orange\r\n dangerous: chalk.red,\r\n};\r\n\r\nconst SEVERITY_COLORS: Record<Severity, (text: string) => string> = {\r\n critical: chalk.bgRed.white.bold,\r\n high: chalk.red.bold,\r\n medium: chalk.yellow,\r\n low: chalk.blue,\r\n info: chalk.gray,\r\n};\r\n\r\nconst TRUST_ICONS: Record<TrustLevel, string> = {\r\n trusted: '✓',\r\n caution: '⚠',\r\n risky: '⚠',\r\n dangerous: '✗',\r\n};\r\n\r\nconst SEVERITY_ICONS: Record<Severity, string> = {\r\n critical: '!!!',\r\n high: '!!',\r\n medium: '!',\r\n low: 'i',\r\n info: '·',\r\n};\r\n\r\nconst FILE_TYPE_LABELS: Record<SkillFileType, string> = {\r\n 'skill.md': 'SKILL.md',\r\n 'mdc-rule': '.mdc rule',\r\n 'claude.md': 'CLAUDE.md',\r\n 'soul.md': 'SOUL.md',\r\n 'memory.md': 'MEMORY.md',\r\n};\r\n\r\n/** Print the Vigile banner */\r\nexport function printBanner(): void {\r\n console.log('');\r\n console.log(chalk.bold.hex('#2C4A7C')(' ╦ ╦╦╔═╗╦╦ ╔═╗'));\r\n console.log(chalk.bold.hex('#2C4A7C')(' ╚╗╔╝║║ ╦║║ ║╣ '));\r\n console.log(chalk.bold.hex('#2C4A7C')(' ╚╝ ╩╚═╝╩╩═╝╚═╝'));\r\n console.log(chalk.gray(' AI Agent Security Scanner'));\r\n console.log('');\r\n}\r\n\r\n/** Print scan results for a single MCP server */\r\nexport function printServerResult(result: ScanResult, verbose: boolean): void {\r\n const color = TRUST_COLORS[result.trustLevel];\r\n const icon = TRUST_ICONS[result.trustLevel];\r\n\r\n // Server header\r\n console.log(\r\n chalk.bold(` ${icon} `) +\r\n chalk.bold(result.server.name) +\r\n chalk.gray(` (${result.server.source})`) +\r\n ' ' +\r\n color(`[${result.trustScore}/100 ${result.trustLevel.toUpperCase()}]`)\r\n );\r\n\r\n // Config path\r\n console.log(chalk.gray(` Config: ${result.server.configPath}`));\r\n\r\n // Command\r\n console.log(\r\n chalk.gray(` Command: ${result.server.command} ${result.server.args.join(' ')}`)\r\n );\r\n\r\n // Findings\r\n if (result.findings.length === 0) {\r\n console.log(chalk.green(' No security issues found.'));\r\n } else {\r\n console.log(\r\n chalk.dim(` ${result.findings.length} finding(s):`)\r\n );\r\n\r\n for (const finding of result.findings) {\r\n printFinding(finding, verbose);\r\n }\r\n }\r\n\r\n // Score breakdown (verbose only)\r\n if (verbose) {\r\n printScoreBreakdown(result.scoreBreakdown);\r\n }\r\n\r\n console.log('');\r\n}\r\n\r\n/** Print scan results for a single skill file */\r\nexport function printSkillResult(result: SkillScanResult, verbose: boolean): void {\r\n const color = TRUST_COLORS[result.trustLevel];\r\n const icon = TRUST_ICONS[result.trustLevel];\r\n const typeLabel = FILE_TYPE_LABELS[result.skill.fileType] || result.skill.fileType;\r\n\r\n // Skill header\r\n console.log(\r\n chalk.bold(` ${icon} `) +\r\n chalk.bold(result.skill.name) +\r\n chalk.gray(` (${typeLabel} · ${result.skill.source} · ${result.skill.scope})`) +\r\n ' ' +\r\n color(`[${result.trustScore}/100 ${result.trustLevel.toUpperCase()}]`)\r\n );\r\n\r\n // File path\r\n console.log(chalk.gray(` Path: ${result.skill.filePath}`));\r\n\r\n // File size\r\n const sizeKB = (result.skill.size / 1024).toFixed(1);\r\n console.log(chalk.gray(` Size: ${sizeKB} KB`));\r\n\r\n // Findings\r\n if (result.findings.length === 0) {\r\n console.log(chalk.green(' No security issues found.'));\r\n } else {\r\n console.log(\r\n chalk.dim(` ${result.findings.length} finding(s):`)\r\n );\r\n\r\n for (const finding of result.findings) {\r\n printFinding(finding, verbose);\r\n }\r\n }\r\n\r\n // Score breakdown (verbose only)\r\n if (verbose) {\r\n printScoreBreakdown(result.scoreBreakdown);\r\n }\r\n\r\n console.log('');\r\n}\r\n\r\n/** Print a single finding */\r\nfunction printFinding(finding: Finding, verbose: boolean): void {\r\n const color = SEVERITY_COLORS[finding.severity];\r\n const icon = SEVERITY_ICONS[finding.severity];\r\n\r\n console.log(\r\n ` ${color(`[${icon}]`)} ${color(finding.severity.toUpperCase())} ` +\r\n chalk.white(finding.title) +\r\n chalk.gray(` (${finding.id})`)\r\n );\r\n\r\n if (verbose) {\r\n console.log(chalk.gray(` ${finding.description}`));\r\n if (finding.evidence) {\r\n console.log(chalk.gray(` Evidence: ${finding.evidence}`));\r\n }\r\n console.log(chalk.cyan(` → ${finding.recommendation}`));\r\n }\r\n}\r\n\r\n/** Print score breakdown */\r\nfunction printScoreBreakdown(b: { codeAnalysis: number; dependencyHealth: number; permissionSafety: number; behavioralStability: number; transparency: number }): void {\r\n console.log(chalk.dim(' Score Breakdown:'));\r\n console.log(chalk.dim(` Code Analysis: ${scoreBar(b.codeAnalysis)} ${b.codeAnalysis}/100 (30%)`));\r\n console.log(chalk.dim(` Dependency Health: ${scoreBar(b.dependencyHealth)} ${b.dependencyHealth}/100 (20%)`));\r\n console.log(chalk.dim(` Permission Safety: ${scoreBar(b.permissionSafety)} ${b.permissionSafety}/100 (20%)`));\r\n console.log(chalk.dim(` Behavioral Stability: ${scoreBar(b.behavioralStability)} ${b.behavioralStability}/100 (15%)`));\r\n console.log(chalk.dim(` Transparency: ${scoreBar(b.transparency)} ${b.transparency}/100 (15%)`));\r\n}\r\n\r\n/** Print the overall scan summary */\r\nexport function printSummary(summary: ScanSummary): void {\r\n console.log(chalk.bold(' ─── Scan Summary ───'));\r\n console.log('');\r\n\r\n if (summary.totalServers > 0) {\r\n console.log(` MCP servers scanned: ${chalk.bold(String(summary.totalServers))}`);\r\n }\r\n if (summary.totalSkills > 0) {\r\n console.log(` Skill files scanned: ${chalk.bold(String(summary.totalSkills))}`);\r\n }\r\n\r\n const totalScanned = summary.totalServers + summary.totalSkills;\r\n if (totalScanned > 0 && summary.totalServers > 0 && summary.totalSkills > 0) {\r\n console.log(` Total scanned: ${chalk.bold(String(totalScanned))}`);\r\n }\r\n\r\n console.log(\r\n ` ${chalk.green(`${TRUST_ICONS.trusted} Trusted: ${summary.byTrustLevel.trusted}`)} ` +\r\n `${chalk.yellow(`${TRUST_ICONS.caution} Caution: ${summary.byTrustLevel.caution}`)} ` +\r\n `${chalk.hex('#FF8C00')(`${TRUST_ICONS.risky} Risky: ${summary.byTrustLevel.risky}`)} ` +\r\n `${chalk.red(`${TRUST_ICONS.dangerous} Dangerous: ${summary.byTrustLevel.dangerous}`)}`\r\n );\r\n\r\n const totalFindings = Object.values(summary.bySeverity).reduce((a, b) => a + b, 0);\r\n if (totalFindings > 0) {\r\n console.log('');\r\n console.log(` Total findings: ${chalk.bold(String(totalFindings))}`);\r\n if (summary.bySeverity.critical > 0)\r\n console.log(chalk.bgRed.white.bold(` ${summary.bySeverity.critical} CRITICAL`));\r\n if (summary.bySeverity.high > 0)\r\n console.log(chalk.red.bold(` ${summary.bySeverity.high} HIGH`));\r\n if (summary.bySeverity.medium > 0)\r\n console.log(chalk.yellow(` ${summary.bySeverity.medium} MEDIUM`));\r\n if (summary.bySeverity.low > 0)\r\n console.log(chalk.blue(` ${summary.bySeverity.low} LOW`));\r\n if (summary.bySeverity.info > 0)\r\n console.log(chalk.gray(` ${summary.bySeverity.info} INFO`));\r\n }\r\n\r\n console.log('');\r\n console.log(chalk.gray(` Scanned at ${summary.timestamp}`));\r\n console.log(chalk.gray(` Vigile v${summary.version} — https://vigile.dev`));\r\n console.log('');\r\n}\r\n\r\n/** Print \"no servers found\" message */\r\nexport function printNoServersFound(): void {\r\n console.log(chalk.yellow(' No MCP server configurations found on this machine.'));\r\n console.log('');\r\n console.log(chalk.gray(' Vigile checks the following locations:'));\r\n console.log(chalk.gray(' • Claude Desktop config'));\r\n console.log(chalk.gray(' • Cursor MCP config'));\r\n console.log(chalk.gray(' • Claude Code config (.claude.json / .mcp.json)'));\r\n console.log(chalk.gray(' • Windsurf MCP config'));\r\n console.log(chalk.gray(' • VS Code MCP config (.vscode/mcp.json)'));\r\n console.log(chalk.gray(' • OpenClaw config (~/.openclaw/openclaw.json)'));\r\n console.log('');\r\n console.log(chalk.gray(' If you have MCP servers configured elsewhere, use:'));\r\n console.log(chalk.cyan(' vigile-scan --config /path/to/config.json'));\r\n console.log('');\r\n console.log(chalk.gray(' To also scan agent skill files, use:'));\r\n console.log(chalk.cyan(' vigile-scan --all'));\r\n console.log('');\r\n}\r\n\r\n/** Print \"no skills found\" message */\r\nexport function printNoSkillsFound(): void {\r\n console.log(chalk.yellow(' No agent skill files found on this machine.'));\r\n console.log('');\r\n console.log(chalk.gray(' Vigile scans the following skill locations:'));\r\n console.log(chalk.gray(' • Claude Code skills (.claude/skills/*/SKILL.md)'));\r\n console.log(chalk.gray(' • Claude Code commands (.claude/commands/**/*.md)'));\r\n console.log(chalk.gray(' • GitHub Copilot skills (.github/skills/*/SKILL.md)'));\r\n console.log(chalk.gray(' • Cursor rules (.cursor/rules/*.mdc, .cursorrules)'));\r\n console.log(chalk.gray(' • Memory files (CLAUDE.md, SOUL.md, MEMORY.md)'));\r\n console.log('');\r\n}\r\n\r\n/** Print \"nothing found\" message (neither servers nor skills) */\r\nexport function printNothingFound(): void {\r\n console.log(chalk.yellow(' No MCP servers or agent skill files found.'));\r\n console.log('');\r\n console.log(chalk.gray(' Try scanning a specific config:'));\r\n console.log(chalk.cyan(' vigile-scan --config /path/to/config.json'));\r\n console.log('');\r\n console.log(chalk.gray(' Or cd into a project directory that contains skill files:'));\r\n console.log(chalk.cyan(' cd /path/to/project && vigile-scan --all'));\r\n console.log('');\r\n}\r\n\r\n/** Create a mini score bar */\r\nfunction scoreBar(score: number): string {\r\n const filled = Math.round(score / 10);\r\n const empty = 10 - filled;\r\n const color =\r\n score >= 80 ? chalk.green :\r\n score >= 60 ? chalk.yellow :\r\n score >= 40 ? chalk.hex('#FF8C00') :\r\n chalk.red;\r\n return color('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));\r\n}\r\n\r\n// ============================================================\r\n// Sentinel Output\r\n// ============================================================\r\n\r\nconst SENTINEL_THREAT_COLORS: Record<SentinelThreatLevel, (text: string) => string> = {\r\n clean: chalk.green,\r\n suspicious: chalk.yellow,\r\n malicious: chalk.red,\r\n critical: chalk.bgRed.white.bold,\r\n};\r\n\r\nconst SENTINEL_THREAT_ICONS: Record<SentinelThreatLevel, string> = {\r\n clean: '✓',\r\n suspicious: '⚠',\r\n malicious: '✗',\r\n critical: '!!!',\r\n};\r\n\r\n/** Print a Sentinel monitoring report */\r\nexport function printSentinelReport(report: SentinelReport): void {\r\n const color = SENTINEL_THREAT_COLORS[report.threatLevel];\r\n const icon = SENTINEL_THREAT_ICONS[report.threatLevel];\r\n\r\n console.log('');\r\n console.log(\r\n chalk.bold(` ${icon} `) +\r\n chalk.bold(`Sentinel: ${report.serverName}`) +\r\n ' ' +\r\n color(`[Threat: ${report.threatScore}/100 ${report.threatLevel.toUpperCase()}]`)\r\n );\r\n\r\n console.log(chalk.gray(` Monitored: ${report.monitoringDuration.toFixed(0)}s | Events: ${report.totalEvents} | Destinations: ${report.uniqueDestinations.length}`));\r\n\r\n if (report.uniqueDestinations.length > 0) {\r\n console.log(chalk.gray(` Destinations:`));\r\n for (const dest of report.uniqueDestinations.slice(0, 10)) {\r\n console.log(chalk.gray(` → ${dest}`));\r\n }\r\n if (report.uniqueDestinations.length > 10) {\r\n console.log(chalk.gray(` ... and ${report.uniqueDestinations.length - 10} more`));\r\n }\r\n }\r\n\r\n if (report.findings.length === 0) {\r\n console.log(chalk.green(' No suspicious network behavior detected.'));\r\n } else {\r\n console.log(chalk.dim(` ${report.findings.length} finding(s):`));\r\n for (const finding of report.findings) {\r\n const fColor = SEVERITY_COLORS[finding.severity];\r\n const fIcon = SEVERITY_ICONS[finding.severity];\r\n console.log(\r\n ` ${fColor(`[${fIcon}]`)} ${fColor(finding.severity.toUpperCase())} ` +\r\n chalk.white(finding.title) +\r\n chalk.gray(` (${finding.id})`)\r\n );\r\n console.log(chalk.gray(` ${finding.description}`));\r\n if (finding.evidence && finding.evidence.length > 0) {\r\n console.log(chalk.gray(` Evidence: ${finding.evidence.length} network event(s)`));\r\n }\r\n console.log(chalk.cyan(` → ${finding.recommendation}`));\r\n }\r\n }\r\n\r\n console.log(chalk.gray(` ${report.startedAt} → ${report.endedAt}`));\r\n console.log('');\r\n}\r\n\r\n/** Print the Sentinel upgrade prompt (for free tier) */\r\nexport function printSentinelUpgrade(): void {\r\n console.log('');\r\n console.log(chalk.bold.hex('#2C4A7C')(` 🛡️ ${SENTINEL_MARKETING.featureName}`));\r\n console.log(chalk.white(` ${SENTINEL_MARKETING.tagline}`));\r\n console.log('');\r\n for (const cat of SENTINEL_MARKETING.categories) {\r\n console.log(chalk.white(` ${cat.icon} ${chalk.bold(cat.name)}`));\r\n console.log(chalk.gray(` ${cat.desc}`));\r\n }\r\n console.log('');\r\n console.log(chalk.yellow(` ⚡ Sentinel is a Pro feature. Upgrade to unlock runtime monitoring:`));\r\n console.log(chalk.cyan(` https://vigile.dev/pricing`));\r\n console.log('');\r\n console.log(chalk.gray(` Pro ($29/mo) — 5-min sessions, 3 servers, behavioral detection`));\r\n console.log(chalk.gray(` Pro+ ($99/mo) — 30-min sessions, 10 servers, DNS tunneling & C2 detection`));\r\n console.log('');\r\n}\r\n\r\n// ============================================================\r\n// API Upload & Auth Output\r\n// ============================================================\r\n\r\n/** Print auth status info */\r\nexport function printAuthStatus(info: {\r\n authenticated: boolean;\r\n source?: 'env' | 'config';\r\n email?: string;\r\n tier?: string;\r\n name?: string;\r\n error?: string;\r\n}): void {\r\n if (info.authenticated) {\r\n console.log(chalk.green(` Authenticated as ${info.email}`));\r\n console.log(chalk.gray(` Tier: ${(info.tier || 'free').toUpperCase()}`));\r\n if (info.name) {\r\n console.log(chalk.gray(` Name: ${info.name}`));\r\n }\r\n console.log(\r\n chalk.gray(\r\n ` Source: ${info.source === 'env' ? 'VIGILE_TOKEN env var' : '~/.vigile/config.json'}`,\r\n ),\r\n );\r\n } else {\r\n console.log(chalk.yellow(' Not authenticated.'));\r\n if (info.error) {\r\n console.log(chalk.red(` Error: ${info.error}`));\r\n }\r\n console.log(chalk.gray(' Run `vigile-scan auth login` or set VIGILE_TOKEN to authenticate.'));\r\n }\r\n console.log('');\r\n}\r\n\r\n/** Print auth login success */\r\nexport function printAuthLoginSuccess(email: string, tier: string): void {\r\n console.log('');\r\n console.log(chalk.green(' Authenticated successfully!'));\r\n console.log(chalk.gray(` Email: ${email}`));\r\n console.log(chalk.gray(` Tier: ${tier.toUpperCase()}`));\r\n console.log(chalk.gray(' Token stored in ~/.vigile/config.json'));\r\n console.log('');\r\n}\r\n\r\n/** Print upload success summary */\r\nexport function printUploadSuccess(summary: {\r\n mcpUploaded: number;\r\n skillsUploaded: number;\r\n failures: number;\r\n}): void {\r\n const total = summary.mcpUploaded + summary.skillsUploaded;\r\n if (total > 0) {\r\n console.log(chalk.green(` Uploaded ${total} result(s) to Vigile registry.`));\r\n if (summary.mcpUploaded > 0) {\r\n console.log(chalk.gray(` MCP servers: ${summary.mcpUploaded}`));\r\n }\r\n if (summary.skillsUploaded > 0) {\r\n console.log(chalk.gray(` Skills: ${summary.skillsUploaded}`));\r\n }\r\n }\r\n if (summary.failures > 0) {\r\n console.log(chalk.yellow(` ${summary.failures} upload(s) failed (results saved locally).`));\r\n }\r\n console.log('');\r\n}\r\n\r\n/** Print upload skip message (when not authenticated or --no-upload) */\r\nexport function printUploadSkipped(reason: 'not-authenticated' | 'no-upload-flag'): void {\r\n if (reason === 'not-authenticated') {\r\n console.log(\r\n chalk.gray(' Tip: Run `vigile-scan auth login` to upload results to the Vigile registry.'),\r\n );\r\n console.log('');\r\n }\r\n // For --no-upload flag, we stay silent (user explicitly opted out)\r\n}\r\n","// ============================================================\r\n// Vigile CLI — JSON Output Formatter\r\n// ============================================================\r\n// Outputs scan results as structured JSON for CI/CD integration,\r\n// piping to other tools, or sending to the Vigile API.\r\n\r\nimport type { ScanSummary } from '../types/index.js';\r\n\r\n/**\r\n * Format scan summary as JSON string.\r\n */\r\nexport function formatJSON(summary: ScanSummary): string {\r\n return JSON.stringify(summary, null, 2);\r\n}\r\n\r\n/**\r\n * Format scan summary as compact JSON (single line, for piping).\r\n */\r\nexport function formatJSONCompact(summary: ScanSummary): string {\r\n return JSON.stringify(summary);\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Auth Management\r\n// ============================================================\r\n// Handles token resolution, persistent storage in ~/.vigile/config.json,\r\n// and provides the auth login/status/logout logic.\r\n//\r\n// Token resolution priority:\r\n// 1. VIGILE_TOKEN environment variable (CI/CD friendly)\r\n// 2. ~/.vigile/config.json (persistent local config)\r\n\r\nimport { readFile, writeFile, mkdir } from 'fs/promises';\r\nimport { join } from 'path';\r\nimport { homedir } from 'os';\r\nimport { VigileApiClient, type ApiUserInfo } from './client.js';\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Config file paths\r\n// ──────────────────────────────────────────────────────────\r\n\r\nconst CONFIG_DIR = join(homedir(), '.vigile');\r\nconst CONFIG_FILE = join(CONFIG_DIR, 'config.json');\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Config shape\r\n// ──────────────────────────────────────────────────────────\r\n\r\ninterface VigileConfig {\r\n token?: string;\r\n api_url?: string;\r\n user?: {\r\n email: string;\r\n tier: string;\r\n name?: string;\r\n };\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Token & URL Resolution\r\n// ──────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Resolve the API token.\r\n * Priority: VIGILE_TOKEN env var > ~/.vigile/config.json\r\n */\r\nexport async function resolveToken(): Promise<string | null> {\r\n // 1. Environment variable takes precedence\r\n const envToken = process.env.VIGILE_TOKEN;\r\n if (envToken && envToken.length > 0) {\r\n return envToken;\r\n }\r\n\r\n // 2. Config file\r\n const config = await loadConfig();\r\n return config.token || null;\r\n}\r\n\r\n/**\r\n * Resolve the API base URL.\r\n * Priority: VIGILE_API_URL env var > ~/.vigile/config.json > default\r\n */\r\nexport async function resolveApiUrl(): Promise<string> {\r\n const envUrl = process.env.VIGILE_API_URL;\r\n if (envUrl) return envUrl;\r\n\r\n const config = await loadConfig();\r\n return config.api_url || 'https://api.vigile.dev';\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Config File Operations\r\n// ──────────────────────────────────────────────────────────\r\n\r\nasync function loadConfig(): Promise<VigileConfig> {\r\n try {\r\n const raw = await readFile(CONFIG_FILE, 'utf-8');\r\n return JSON.parse(raw) as VigileConfig;\r\n } catch {\r\n return {};\r\n }\r\n}\r\n\r\nasync function saveConfig(config: VigileConfig): Promise<void> {\r\n await mkdir(CONFIG_DIR, { recursive: true });\r\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), {\r\n mode: 0o600, // Owner read/write only\r\n });\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Auth Actions\r\n// ──────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Login: validate a token against the API and store it persistently.\r\n */\r\nexport async function authLogin(token: string): Promise<{\r\n success: boolean;\r\n user?: ApiUserInfo;\r\n error?: string;\r\n}> {\r\n const apiUrl = await resolveApiUrl();\r\n const client = new VigileApiClient(apiUrl, token);\r\n const result = await client.getMe();\r\n\r\n if (!result.ok) {\r\n return { success: false, error: result.error };\r\n }\r\n\r\n // Persist token and user info\r\n const config = await loadConfig();\r\n config.token = token;\r\n config.user = {\r\n email: result.data.email,\r\n tier: result.data.tier,\r\n name: result.data.name || undefined,\r\n };\r\n await saveConfig(config);\r\n\r\n return { success: true, user: result.data };\r\n}\r\n\r\n/**\r\n * Check current authentication status.\r\n */\r\nexport async function authStatus(): Promise<{\r\n authenticated: boolean;\r\n source?: 'env' | 'config';\r\n user?: ApiUserInfo;\r\n error?: string;\r\n}> {\r\n const envToken = process.env.VIGILE_TOKEN;\r\n const config = await loadConfig();\r\n const token = envToken || config.token;\r\n\r\n if (!token) {\r\n return { authenticated: false };\r\n }\r\n\r\n const source: 'env' | 'config' = envToken ? 'env' : 'config';\r\n const apiUrl = await resolveApiUrl();\r\n const client = new VigileApiClient(apiUrl, token);\r\n const result = await client.getMe();\r\n\r\n if (!result.ok) {\r\n return { authenticated: false, source, error: result.error };\r\n }\r\n\r\n return { authenticated: true, source, user: result.data };\r\n}\r\n\r\n/**\r\n * Logout: clear stored credentials from config file.\r\n */\r\nexport async function authLogout(): Promise<void> {\r\n const config = await loadConfig();\r\n delete config.token;\r\n delete config.user;\r\n await saveConfig(config);\r\n}\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Convenience: get an authenticated client (or null)\r\n// ──────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Create an authenticated API client, or return null if no token is available.\r\n * Used by the scan upload step and Sentinel API integration.\r\n */\r\nexport async function getAuthenticatedClient(): Promise<VigileApiClient | null> {\r\n const token = await resolveToken();\r\n if (!token) return null;\r\n\r\n const apiUrl = await resolveApiUrl();\r\n return new VigileApiClient(apiUrl, token);\r\n}\r\n","// ============================================================\r\n// Vigile CLI — API Client\r\n// ============================================================\r\n// Lightweight HTTP client for communicating with the Vigile API.\r\n// Uses Node 18+ native fetch. Graceful degradation: if the API\r\n// is unreachable, the CLI continues in local-only mode.\r\n//\r\n// All methods return a discriminated union:\r\n// { ok: true, data: T } | { ok: false, error: string, status: number }\r\n// This avoids thrown exceptions and makes error handling explicit.\r\n\r\nconst DEFAULT_API_URL = 'https://api.vigile.dev';\r\nconst API_TIMEOUT_MS = 15_000; // 15 seconds\r\nconst CLI_VERSION = '0.2.0';\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// Response Types\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport interface ApiUserInfo {\r\n id: number;\r\n email: string;\r\n name: string | null;\r\n tier: string; // 'free' | 'pro' (team/enterprise planned for future)\r\n}\r\n\r\nexport interface ApiScanResponse {\r\n id: number;\r\n server_name: string;\r\n trust_score: number;\r\n trust_level: string;\r\n score_breakdown: Record<string, number> | null;\r\n findings: Array<Record<string, unknown>>;\r\n findings_count: number;\r\n critical_count: number;\r\n high_count: number;\r\n scanner_version: string;\r\n scanned_at: string;\r\n}\r\n\r\nexport interface ApiSkillScanResponse {\r\n id: number;\r\n skill_name: string;\r\n file_type: string;\r\n platform: string;\r\n trust_score: number;\r\n trust_level: string;\r\n score_breakdown: Record<string, number> | null;\r\n findings: Array<Record<string, unknown>>;\r\n findings_count: number;\r\n critical_count: number;\r\n high_count: number;\r\n scanner_version: string;\r\n scanned_at: string;\r\n}\r\n\r\nexport interface ApiSentinelSessionResponse {\r\n session_id: number;\r\n server_name: string;\r\n max_duration_seconds: number;\r\n status: string;\r\n created_at: string;\r\n}\r\n\r\nexport interface ApiSentinelEventsResponse {\r\n status: string;\r\n events_received: number;\r\n total_events: number;\r\n}\r\n\r\nexport interface ApiSentinelReportResponse {\r\n id: number;\r\n session_id: number;\r\n server_name: string;\r\n monitoring_duration: number;\r\n total_events: number;\r\n unique_destinations: string[];\r\n findings: Array<{\r\n id: string;\r\n category: string;\r\n severity: string;\r\n title: string;\r\n description: string;\r\n server_name: string;\r\n evidence_count: number;\r\n recommendation: string;\r\n confidence: number;\r\n }>;\r\n threat_level: string;\r\n threat_score: number;\r\n started_at: string;\r\n ended_at: string;\r\n user_tier: string;\r\n}\r\n\r\n/** Discriminated union result type */\r\nexport type ApiResult<T> =\r\n | { ok: true; data: T }\r\n | { ok: false; error: string; status: number };\r\n\r\n// ──────────────────────────────────────────────────────────\r\n// API Client\r\n// ──────────────────────────────────────────────────────────\r\n\r\nexport class VigileApiClient {\r\n private baseUrl: string;\r\n private token: string | null;\r\n\r\n constructor(baseUrl?: string, token?: string | null) {\r\n const rawUrl = (baseUrl || process.env.VIGILE_API_URL || DEFAULT_API_URL)\r\n .replace(/\\/+$/, ''); // strip trailing slashes\r\n // Validate URL — must be HTTPS (unless localhost for development)\r\n try {\r\n const u = new URL(rawUrl);\r\n if (u.protocol !== 'https:' && u.hostname !== 'localhost' && u.hostname !== '127.0.0.1') {\r\n console.error(`[vigile] API URL must use HTTPS — falling back to default`);\r\n this.baseUrl = DEFAULT_API_URL;\r\n } else {\r\n this.baseUrl = rawUrl;\r\n }\r\n } catch {\r\n console.error(`[vigile] Invalid API URL — falling back to default`);\r\n this.baseUrl = DEFAULT_API_URL;\r\n }\r\n this.token = token || null;\r\n }\r\n\r\n get isAuthenticated(): boolean {\r\n return this.token !== null && this.token.length > 0;\r\n }\r\n\r\n // ── Private helpers ──\r\n\r\n private async request<T>(\r\n method: string,\r\n path: string,\r\n body?: unknown,\r\n ): Promise<ApiResult<T>> {\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT_MS);\r\n\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n 'User-Agent': `vigile-cli/${CLI_VERSION}`,\r\n };\r\n if (this.token) {\r\n headers['Authorization'] = `Bearer ${this.token}`;\r\n }\r\n\r\n try {\r\n const response = await fetch(`${this.baseUrl}${path}`, {\r\n method,\r\n headers,\r\n body: body ? JSON.stringify(body) : undefined,\r\n signal: controller.signal,\r\n });\r\n\r\n clearTimeout(timeoutId);\r\n\r\n if (!response.ok) {\r\n const errorBody = await response.text();\r\n let errorMessage: string;\r\n try {\r\n const parsed = JSON.parse(errorBody);\r\n errorMessage = typeof parsed.detail === 'string'\r\n ? parsed.detail\r\n : JSON.stringify(parsed.detail);\r\n } catch {\r\n errorMessage = errorBody || `HTTP ${response.status}`;\r\n }\r\n return { ok: false, error: errorMessage, status: response.status };\r\n }\r\n\r\n const data = (await response.json()) as T;\r\n return { ok: true, data };\r\n } catch (err) {\r\n clearTimeout(timeoutId);\r\n if (err instanceof Error && err.name === 'AbortError') {\r\n return { ok: false, error: 'Request timed out', status: 0 };\r\n }\r\n return {\r\n ok: false,\r\n error: err instanceof Error ? err.message : 'Network error',\r\n status: 0,\r\n };\r\n }\r\n }\r\n\r\n // ── Auth ──\r\n\r\n async getMe(): Promise<ApiResult<ApiUserInfo>> {\r\n return this.request<ApiUserInfo>('GET', '/api/v1/auth/me');\r\n }\r\n\r\n // ── Scanning ──\r\n\r\n async submitMCPScan(payload: {\r\n server_name: string;\r\n source?: string;\r\n package_url?: string;\r\n repo_url?: string;\r\n description?: string;\r\n readme?: string;\r\n tool_descriptions?: string[];\r\n maintainer?: string;\r\n license?: string;\r\n homepage?: string;\r\n }): Promise<ApiResult<ApiScanResponse>> {\r\n return this.request<ApiScanResponse>('POST', '/api/v1/scan/', payload);\r\n }\r\n\r\n async submitSkillScan(payload: {\r\n skill_name: string;\r\n content: string;\r\n file_type?: string;\r\n platform?: string;\r\n source?: string;\r\n }): Promise<ApiResult<ApiSkillScanResponse>> {\r\n return this.request<ApiSkillScanResponse>('POST', '/api/v1/scan/skill', payload);\r\n }\r\n\r\n // ── Sentinel ──\r\n\r\n async createSentinelSession(\r\n serverName: string,\r\n durationSeconds: number,\r\n client: string = 'cli',\r\n ): Promise<ApiResult<ApiSentinelSessionResponse>> {\r\n return this.request<ApiSentinelSessionResponse>('POST', '/api/v1/sentinel/sessions', {\r\n server_name: serverName,\r\n duration_seconds: durationSeconds,\r\n client,\r\n });\r\n }\r\n\r\n async submitSentinelEvents(\r\n sessionId: number,\r\n events: Array<{\r\n timestamp: number;\r\n server_name: string;\r\n method: string;\r\n url: string;\r\n destination_ip?: string | null;\r\n port: number;\r\n request_size: number;\r\n response_size?: number | null;\r\n status_code?: number | null;\r\n headers?: Record<string, string> | null;\r\n dns_query_type?: string | null;\r\n tls: boolean;\r\n body_hash?: string | null;\r\n body_entropy?: number | null;\r\n }>,\r\n ): Promise<ApiResult<ApiSentinelEventsResponse>> {\r\n return this.request<ApiSentinelEventsResponse>(\r\n 'POST',\r\n `/api/v1/sentinel/sessions/${sessionId}/events`,\r\n { session_id: sessionId, events },\r\n );\r\n }\r\n\r\n async analyzeSentinelSession(\r\n sessionId: number,\r\n ): Promise<ApiResult<ApiSentinelReportResponse>> {\r\n return this.request<ApiSentinelReportResponse>(\r\n 'POST',\r\n `/api/v1/sentinel/sessions/${sessionId}/analyze`,\r\n );\r\n }\r\n}\r\n","// ============================================================\r\n// Vigile CLI — BaaS Secret Pattern Library\r\n// ============================================================\r\n// Regex patterns for detecting exposed credentials in JavaScript\r\n// bundles, environment variable leaks, and deployed web apps.\r\n//\r\n// 150 patterns across 40+ providers.\r\n// ============================================================\r\n\r\nexport interface SecretPattern {\r\n /** Unique pattern ID (SP-001 through SP-150) */\r\n id: string;\r\n /** Human-readable pattern name */\r\n name: string;\r\n /** Service/provider that issued this credential type */\r\n provider: string;\r\n /** How bad is exposure of this credential type */\r\n severity: 'critical' | 'high' | 'medium' | 'low';\r\n /** Regex to detect the credential */\r\n pattern: RegExp;\r\n /** What this credential type grants access to */\r\n description: string;\r\n /** Remediation guidance */\r\n recommendation: string;\r\n}\r\n\r\nexport interface SecretMatch {\r\n pattern: SecretPattern;\r\n /** Masked version of the matched value (first4***last4) */\r\n match: string;\r\n /** Surrounding text for context */\r\n context: string;\r\n}\r\n\r\nfunction mask(secret: string): string {\r\n if (secret.length <= 8) return '***';\r\n return `${secret.slice(0, 4)}***${secret.slice(-4)}`;\r\n}\r\n\r\n// ============================================================\r\n// Pattern Registry — 150 patterns across 40+ providers\r\n// ============================================================\r\n\r\nexport const SECRET_PATTERNS: SecretPattern[] = [\r\n // ── AI / LLM Providers ──\r\n\r\n {\r\n id: 'SP-001',\r\n name: 'OpenAI API Key',\r\n provider: 'openai',\r\n severity: 'critical',\r\n pattern: /sk-[A-Za-z0-9]{48}/,\r\n description: 'OpenAI API key grants full access to GPT-4, Whisper, DALL-E, and all models. Exposed keys result in immediate billing charges.',\r\n recommendation: 'Rotate at platform.openai.com/api-keys immediately. Move all AI calls to a server-side proxy.',\r\n },\r\n {\r\n id: 'SP-002',\r\n name: 'OpenAI Project API Key',\r\n provider: 'openai',\r\n severity: 'critical',\r\n pattern: /sk-proj-[A-Za-z0-9_-]{80,120}/,\r\n description: 'OpenAI project-scoped API key. Exposed keys grant API access within the project scope.',\r\n recommendation: 'Rotate at platform.openai.com/api-keys. Never include in client-side JavaScript.',\r\n },\r\n {\r\n id: 'SP-003',\r\n name: 'OpenAI Organization ID',\r\n provider: 'openai',\r\n severity: 'medium',\r\n pattern: /org-[A-Za-z0-9]{24}/,\r\n description: 'OpenAI organization identifier. Exposure can leak organizational structure.',\r\n recommendation: 'Treat as sensitive. Pass server-side only.',\r\n },\r\n {\r\n id: 'SP-004',\r\n name: 'Anthropic API Key',\r\n provider: 'anthropic',\r\n severity: 'critical',\r\n pattern: /sk-ant-api\\d{2}-[A-Za-z0-9_-]{93,100}/,\r\n description: 'Anthropic Claude API key. Grants access to Claude Sonnet, Opus, and Haiku. Full billing exposure.',\r\n recommendation: 'Rotate at console.anthropic.com. All Claude API calls must be server-side.',\r\n },\r\n {\r\n id: 'SP-005',\r\n name: 'Anthropic Session Token',\r\n provider: 'anthropic',\r\n severity: 'critical',\r\n pattern: /sk-ant-oau\\d{2}-[A-Za-z0-9_-]{80,}/,\r\n description: 'Anthropic OAuth session token. Short-lived but grants full account access if intercepted.',\r\n recommendation: 'These should never appear in bundles. Remove immediately and revoke session.',\r\n },\r\n {\r\n id: 'SP-006',\r\n name: 'HuggingFace User Token',\r\n provider: 'huggingface',\r\n severity: 'high',\r\n pattern: /hf_[A-Za-z0-9]{34,40}/,\r\n description: 'HuggingFace API token. Grants access to models, datasets, and inference API.',\r\n recommendation: 'Rotate at huggingface.co/settings/tokens. Use read-only tokens for public model access.',\r\n },\r\n {\r\n id: 'SP-007',\r\n name: 'HuggingFace Org Token',\r\n provider: 'huggingface',\r\n severity: 'high',\r\n pattern: /api_org_[A-Za-z0-9]{34,40}/,\r\n description: 'HuggingFace organization API token with org-level privileges.',\r\n recommendation: 'Rotate at huggingface.co/settings/tokens. Grant minimum required permissions.',\r\n },\r\n {\r\n id: 'SP-008',\r\n name: 'Replicate API Token',\r\n provider: 'replicate',\r\n severity: 'high',\r\n pattern: /r8_[A-Za-z0-9]{35,40}/,\r\n description: 'Replicate API token. Grants access to run ML models with per-second billing.',\r\n recommendation: 'Rotate at replicate.com/account/api-tokens. All inference must be server-side.',\r\n },\r\n {\r\n id: 'SP-009',\r\n name: 'Cohere API Key',\r\n provider: 'cohere',\r\n severity: 'high',\r\n pattern: /(?:co-|COHERE_API_KEY['\":\\s=]+)([A-Za-z0-9]{40,50})/,\r\n description: 'Cohere API key for language model inference and embeddings.',\r\n recommendation: 'Rotate at dashboard.cohere.com/api-keys.',\r\n },\r\n {\r\n id: 'SP-010',\r\n name: 'Groq API Key',\r\n provider: 'groq',\r\n severity: 'high',\r\n pattern: /gsk_[A-Za-z0-9]{48,60}/,\r\n description: 'Groq API key. Grants ultra-fast LLM inference on Llama, Mixtral, Gemma models.',\r\n recommendation: 'Rotate at console.groq.com/keys. Never ship in client bundles.',\r\n },\r\n {\r\n id: 'SP-011',\r\n name: 'Together AI API Key',\r\n provider: 'together-ai',\r\n severity: 'high',\r\n pattern: /(?:TOGETHER_API_KEY|together_api_key)['\":\\s=]+([A-Za-z0-9]{64})/i,\r\n description: 'Together AI API key for open-source LLM inference.',\r\n recommendation: 'Rotate at api.together.xyz/settings/api-keys.',\r\n },\r\n {\r\n id: 'SP-012',\r\n name: 'Mistral AI API Key',\r\n provider: 'mistral',\r\n severity: 'high',\r\n pattern: /(?:MISTRAL_API_KEY|mistral_api_key)['\":\\s=]+([A-Za-z0-9]{32,40})/i,\r\n description: 'Mistral AI API key for Mistral-7B, Mixtral, and Codestral models.',\r\n recommendation: 'Rotate at console.mistral.ai/api-keys.',\r\n },\r\n {\r\n id: 'SP-013',\r\n name: 'Perplexity API Key',\r\n provider: 'perplexity',\r\n severity: 'high',\r\n pattern: /pplx-[A-Za-z0-9]{48,60}/,\r\n description: 'Perplexity AI API key for search-augmented language model access.',\r\n recommendation: 'Rotate at perplexity.ai/settings/api.',\r\n },\r\n {\r\n id: 'SP-014',\r\n name: 'Cerebras API Key',\r\n provider: 'cerebras',\r\n severity: 'high',\r\n pattern: /csk-[A-Za-z0-9]{40,60}/,\r\n description: 'Cerebras API key for ultra-fast inference on Llama models.',\r\n recommendation: 'Rotate at cloud.cerebras.ai. All inference must be server-side.',\r\n },\r\n {\r\n id: 'SP-015',\r\n name: 'OpenRouter API Key',\r\n provider: 'openrouter',\r\n severity: 'high',\r\n pattern: /sk-or-[A-Za-z0-9]{40,60}/,\r\n description: 'OpenRouter API key providing access to 100+ AI models through a unified API.',\r\n recommendation: 'Rotate at openrouter.ai/keys.',\r\n },\r\n {\r\n id: 'SP-016',\r\n name: 'ElevenLabs API Key',\r\n provider: 'elevenlabs',\r\n severity: 'high',\r\n pattern: /(?:ELEVENLABS_API_KEY|xi-api-key)['\":\\s=]+([A-Za-z0-9_-]{32,50})/i,\r\n description: 'ElevenLabs voice synthesis API key. Grants text-to-speech and voice cloning access.',\r\n recommendation: 'Rotate at elevenlabs.io/profile/api-key.',\r\n },\r\n {\r\n id: 'SP-017',\r\n name: 'AssemblyAI API Key',\r\n provider: 'assemblyai',\r\n severity: 'high',\r\n pattern: /(?:ASSEMBLYAI_API_KEY)['\":\\s=]+([a-f0-9]{40,50})/i,\r\n description: 'AssemblyAI transcription API key. Grants access to speech-to-text and audio intelligence.',\r\n recommendation: 'Rotate at www.assemblyai.com/app/account.',\r\n },\r\n {\r\n id: 'SP-018',\r\n name: 'Deepgram API Key',\r\n provider: 'deepgram',\r\n severity: 'high',\r\n pattern: /(?:DEEPGRAM_API_KEY)['\":\\s=]+([a-f0-9]{40,50})/i,\r\n description: 'Deepgram speech recognition API key.',\r\n recommendation: 'Rotate at console.deepgram.com/project/api-key.',\r\n },\r\n {\r\n id: 'SP-019',\r\n name: 'Stability AI API Key',\r\n provider: 'stability-ai',\r\n severity: 'high',\r\n pattern: /sk-[A-Za-z0-9]{32,50}(?=['\"}\\s])/,\r\n description: 'Stability AI key for image generation (Stable Diffusion). Overlaps with sk- prefix — validate by context.',\r\n recommendation: 'Rotate at platform.stability.ai/account/keys.',\r\n },\r\n {\r\n id: 'SP-020',\r\n name: 'LangSmith API Key',\r\n provider: 'langsmith',\r\n severity: 'medium',\r\n pattern: /ls__[A-Za-z0-9_-]{32,50}/,\r\n description: 'LangSmith API key for LLM observability and tracing.',\r\n recommendation: 'Rotate at smith.langchain.com/settings.',\r\n },\r\n {\r\n id: 'SP-021',\r\n name: 'Weights & Biases API Key',\r\n provider: 'wandb',\r\n severity: 'medium',\r\n pattern: /(?:WANDB_API_KEY)['\":\\s=]+([a-f0-9]{40})/i,\r\n description: 'Weights & Biases ML experiment tracking API key.',\r\n recommendation: 'Rotate at wandb.ai/authorize.',\r\n },\r\n {\r\n id: 'SP-022',\r\n name: 'Helicone API Key',\r\n provider: 'helicone',\r\n severity: 'medium',\r\n pattern: /sk-helicone-[A-Za-z0-9_-]{30,60}/,\r\n description: 'Helicone LLM observability API key for caching and monitoring AI calls.',\r\n recommendation: 'Rotate at helicone.ai/settings.',\r\n },\r\n {\r\n id: 'SP-023',\r\n name: 'E2B API Key',\r\n provider: 'e2b',\r\n severity: 'high',\r\n pattern: /e2b_[A-Za-z0-9_-]{30,50}/,\r\n description: 'E2B sandbox API key. Grants ability to spin up code execution sandboxes — billing risk.',\r\n recommendation: 'Rotate at e2b.dev/dashboard.',\r\n },\r\n {\r\n id: 'SP-024',\r\n name: 'Tavily Search API Key',\r\n provider: 'tavily',\r\n severity: 'medium',\r\n pattern: /tvly-[A-Za-z0-9_-]{30,50}/,\r\n description: 'Tavily AI search API key.',\r\n recommendation: 'Rotate at app.tavily.com.',\r\n },\r\n {\r\n id: 'SP-025',\r\n name: 'Firecrawl API Key',\r\n provider: 'firecrawl',\r\n severity: 'medium',\r\n pattern: /fc-[A-Za-z0-9]{32,50}/,\r\n description: 'Firecrawl web scraping API key.',\r\n recommendation: 'Rotate at firecrawl.dev/account.',\r\n },\r\n {\r\n id: 'SP-026',\r\n name: 'Jina AI API Key',\r\n provider: 'jina',\r\n severity: 'medium',\r\n pattern: /jina_[A-Za-z0-9_-]{50,80}/,\r\n description: 'Jina AI API key for embeddings and search.',\r\n recommendation: 'Rotate at jina.ai/dashboard.',\r\n },\r\n {\r\n id: 'SP-027',\r\n name: 'Apify API Token',\r\n provider: 'apify',\r\n severity: 'medium',\r\n pattern: /apify_api_[A-Za-z0-9]{40,60}/,\r\n description: 'Apify web scraping platform API token.',\r\n recommendation: 'Rotate at console.apify.com/account/integrations.',\r\n },\r\n\r\n // ── Cloud Providers ──\r\n\r\n {\r\n id: 'SP-028',\r\n name: 'AWS Access Key ID',\r\n provider: 'aws',\r\n severity: 'critical',\r\n pattern: /AKIA[A-Z0-9]{16}/,\r\n description: 'AWS IAM Access Key ID. Pair this with a secret key to authenticate against any AWS service.',\r\n recommendation: 'Rotate immediately in AWS IAM console. Enable MFA. Use instance profiles instead of long-lived keys.',\r\n },\r\n {\r\n id: 'SP-029',\r\n name: 'AWS Secret Access Key',\r\n provider: 'aws',\r\n severity: 'critical',\r\n pattern: /(?:aws[_-]?secret[_-]?(?:access[_-]?)?key|AWS_SECRET(?:_ACCESS_KEY)?)['\":\\s=]+([A-Za-z0-9/+=]{40})/i,\r\n description: 'AWS IAM Secret Access Key. Combined with key ID, provides full AWS service access.',\r\n recommendation: 'Rotate immediately. Audit CloudTrail for unauthorized usage. Use IAM roles instead.',\r\n },\r\n {\r\n id: 'SP-030',\r\n name: 'AWS Session Token',\r\n provider: 'aws',\r\n severity: 'high',\r\n pattern: /(?:AWS_SESSION_TOKEN|aws_session_token)['\":\\s=]+([A-Za-z0-9/+=]{200,600})/i,\r\n description: 'Temporary AWS STS session token. Short-lived but grants API access until expiry.',\r\n recommendation: 'These expire automatically but should not be in client code.',\r\n },\r\n {\r\n id: 'SP-031',\r\n name: 'Google Cloud API Key',\r\n provider: 'google-cloud',\r\n severity: 'critical',\r\n pattern: /AIza[0-9A-Za-z\\-_]{35}/,\r\n description: 'Google Cloud / Firebase API key. Grants access to Google Maps, Firebase, YouTube, and other Google APIs depending on restrictions.',\r\n recommendation: 'Add API key restrictions in Google Cloud Console. Rotate at console.cloud.google.com/apis/credentials.',\r\n },\r\n {\r\n id: 'SP-032',\r\n name: 'Google Service Account Key',\r\n provider: 'google-cloud',\r\n severity: 'critical',\r\n pattern: /\"private_key\"\\s*:\\s*\"-----BEGIN RSA PRIVATE KEY/,\r\n description: 'Google Cloud service account private key embedded in source. Grants IAM service account impersonation.',\r\n recommendation: 'Revoke the service account key immediately. Use Workload Identity or ADC instead of key files.',\r\n },\r\n {\r\n id: 'SP-033',\r\n name: 'Google OAuth Client Secret',\r\n provider: 'google-cloud',\r\n severity: 'high',\r\n pattern: /GOCSPX-[A-Za-z0-9_-]{28}/,\r\n description: 'Google OAuth 2.0 client secret. Enables impersonation of your OAuth application.',\r\n recommendation: 'Rotate at console.cloud.google.com/apis/credentials. Keep client secrets server-side only.',\r\n },\r\n {\r\n id: 'SP-034',\r\n name: 'Firebase Web API Key',\r\n provider: 'firebase',\r\n severity: 'medium',\r\n pattern: /(?:NEXT_PUBLIC_FIREBASE_API_KEY|VITE_FIREBASE_API_KEY|firebase(?:Config)?\\.apiKey)['\":\\s=]+[\"']?(AIza[0-9A-Za-z\\-_]{35})/i,\r\n description: 'Firebase Web API Key in environment variable. The key itself is semi-public but should be paired with proper security rules.',\r\n recommendation: 'Restrict key usage in Firebase Console. Ensure Firestore and Storage rules are not open.',\r\n },\r\n {\r\n id: 'SP-035',\r\n name: 'Firebase Service Account',\r\n provider: 'firebase',\r\n severity: 'critical',\r\n pattern: /(?:FIREBASE_SERVICE_ACCOUNT|firebase[_-]admin[_-]sdk)['\":\\s=]+/i,\r\n description: 'Firebase Admin SDK service account credentials. Grants admin-level access bypassing all security rules.',\r\n recommendation: 'Never include Firebase Admin credentials in client bundles. Use client SDK on frontend, Admin SDK on backend only.',\r\n },\r\n {\r\n id: 'SP-036',\r\n name: 'Supabase Service Role Key',\r\n provider: 'supabase',\r\n severity: 'critical',\r\n pattern: /(?:SUPABASE_SERVICE_ROLE_KEY|SUPABASE_SERVICE_KEY|supabase[_-]service[_-]role)['\":\\s=]+[\"']?(eyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+)/i,\r\n description: 'Supabase service role key. Bypasses ALL Row Level Security policies — full database admin access.',\r\n recommendation: 'Immediately rotate in Supabase dashboard > Project Settings > API. This key MUST NEVER be in client code.',\r\n },\r\n {\r\n id: 'SP-037',\r\n name: 'Supabase Anon Key (Public)',\r\n provider: 'supabase',\r\n severity: 'low',\r\n pattern: /(?:NEXT_PUBLIC_SUPABASE_ANON_KEY|VITE_SUPABASE_ANON_KEY|supabase[_-]anon[_-]key)['\":\\s=]+[\"']?(eyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+)/i,\r\n description: 'Supabase anon/public key. This is designed to be public, but without proper RLS policies, it can expose your data.',\r\n recommendation: 'Safe to be public ONLY if RLS policies are correctly configured on all tables.',\r\n },\r\n {\r\n id: 'SP-038',\r\n name: 'Supabase URL',\r\n provider: 'supabase',\r\n severity: 'low',\r\n pattern: /https:\\/\\/[a-z0-9]{20}\\.supabase\\.co/,\r\n description: 'Supabase project URL. The unique identifier for your Supabase project.',\r\n recommendation: 'Exposure is low-risk, but combine with anon key check to assess full exposure surface.',\r\n },\r\n {\r\n id: 'SP-039',\r\n name: 'Azure Storage Connection String',\r\n provider: 'azure',\r\n severity: 'critical',\r\n pattern: /DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[A-Za-z0-9+/=]{88}/,\r\n description: 'Azure Storage Account connection string. Grants full read/write access to all blobs, queues, and tables.',\r\n recommendation: 'Rotate in Azure Portal > Storage Account > Access Keys. Use Managed Identity or SAS tokens instead.',\r\n },\r\n {\r\n id: 'SP-040',\r\n name: 'Azure Subscription Key',\r\n provider: 'azure',\r\n severity: 'high',\r\n pattern: /(?:Ocp-Apim-Subscription-Key|azure[_-]?subscription[_-]?key)['\":\\s=]+([A-Za-z0-9]{32})/i,\r\n description: 'Azure API Management subscription key.',\r\n recommendation: 'Rotate in Azure API Management portal.',\r\n },\r\n {\r\n id: 'SP-041',\r\n name: 'Azure AD Client Secret',\r\n provider: 'azure',\r\n severity: 'critical',\r\n pattern: /(?:AZURE_CLIENT_SECRET|azure[_-]?client[_-]?secret)['\":\\s=]+([A-Za-z0-9~.@-]{34,40})/i,\r\n description: 'Azure Active Directory application client secret. Grants OAuth access as the application.',\r\n recommendation: 'Rotate in Azure AD > App Registrations > Certificates & Secrets.',\r\n },\r\n {\r\n id: 'SP-042',\r\n name: 'Cloudflare API Token',\r\n provider: 'cloudflare',\r\n severity: 'critical',\r\n pattern: /(?:CLOUDFLARE_API_TOKEN|CF_API_TOKEN)['\":\\s=]+([A-Za-z0-9_-]{40})/i,\r\n description: 'Cloudflare API token. Grants access to DNS, Workers, Pages, and account settings.',\r\n recommendation: 'Rotate at dash.cloudflare.com/profile/api-tokens. Use scoped tokens with minimum permissions.',\r\n },\r\n {\r\n id: 'SP-043',\r\n name: 'Cloudflare API Key (Global)',\r\n provider: 'cloudflare',\r\n severity: 'critical',\r\n pattern: /(?:CLOUDFLARE_API_KEY|CF_API_KEY)['\":\\s=]+([a-f0-9]{37})/i,\r\n description: 'Cloudflare Global API Key. Grants full account access — more dangerous than API tokens.',\r\n recommendation: 'Rotate at dash.cloudflare.com/profile/api-tokens. Prefer scoped API tokens over global keys.',\r\n },\r\n {\r\n id: 'SP-044',\r\n name: 'Cloudflare Workers KV Namespace',\r\n provider: 'cloudflare',\r\n severity: 'low',\r\n pattern: /(?:KV_NAMESPACE_ID|CLOUDFLARE_KV_NAMESPACE)['\":\\s=]+([a-f0-9]{32})/i,\r\n description: 'Cloudflare Workers KV namespace identifier.',\r\n recommendation: 'Low risk alone, but combined with API credentials allows data access.',\r\n },\r\n {\r\n id: 'SP-045',\r\n name: 'Vercel API Token',\r\n provider: 'vercel',\r\n severity: 'critical',\r\n pattern: /(?:VERCEL_TOKEN|vercel[_-]token)['\":\\s=]+([A-Za-z0-9]{24})/i,\r\n description: 'Vercel deployment API token. Grants ability to deploy, manage domains, and access secrets.',\r\n recommendation: 'Rotate at vercel.com/account/tokens.',\r\n },\r\n {\r\n id: 'SP-046',\r\n name: 'Vercel Access Token',\r\n provider: 'vercel',\r\n severity: 'critical',\r\n pattern: /vercel_[A-Za-z0-9]{40,60}/i,\r\n description: 'Vercel access token for CLI or API authentication.',\r\n recommendation: 'Rotate at vercel.com/account/tokens.',\r\n },\r\n {\r\n id: 'SP-047',\r\n name: 'DigitalOcean Personal Access Token',\r\n provider: 'digitalocean',\r\n severity: 'critical',\r\n pattern: /dop_v1_[a-f0-9]{64}/,\r\n description: 'DigitalOcean personal access token. Grants full account access to droplets, databases, and storage.',\r\n recommendation: 'Rotate at cloud.digitalocean.com/account/api/tokens.',\r\n },\r\n {\r\n id: 'SP-048',\r\n name: 'Railway Token',\r\n provider: 'railway',\r\n severity: 'high',\r\n pattern: /(?:RAILWAY_TOKEN|railway[_-]token)['\":\\s=]+([A-Za-z0-9_-]{30,50})/i,\r\n description: 'Railway deployment platform API token.',\r\n recommendation: 'Rotate at railway.app/account/tokens.',\r\n },\r\n {\r\n id: 'SP-049',\r\n name: 'Fly.io API Token',\r\n provider: 'flyio',\r\n severity: 'high',\r\n pattern: /fo1_[A-Za-z0-9_-]{40,80}/,\r\n description: 'Fly.io API token for machine deployment and management.',\r\n recommendation: 'Rotate with: flyctl tokens revoke <token>',\r\n },\r\n\r\n // ── Payment Providers ──\r\n\r\n {\r\n id: 'SP-050',\r\n name: 'Stripe Secret Key (Live)',\r\n provider: 'stripe',\r\n severity: 'critical',\r\n pattern: /sk_live_[A-Za-z0-9]{24,100}/,\r\n description: 'Stripe live-mode secret key. Grants full access to create charges, issue refunds, and access customer data.',\r\n recommendation: 'Rotate immediately at dashboard.stripe.com/apikeys. Never expose in client code.',\r\n },\r\n {\r\n id: 'SP-051',\r\n name: 'Stripe Publishable Key (Live)',\r\n provider: 'stripe',\r\n severity: 'low',\r\n pattern: /pk_live_[A-Za-z0-9]{24,100}/,\r\n description: 'Stripe live publishable key. This is meant to be public for frontend use.',\r\n recommendation: 'This key is designed to be public. Ensure it is not confused with the secret key.',\r\n },\r\n {\r\n id: 'SP-052',\r\n name: 'Stripe Restricted Key (Live)',\r\n provider: 'stripe',\r\n severity: 'high',\r\n pattern: /rk_live_[A-Za-z0-9]{24,100}/,\r\n description: 'Stripe live restricted key with limited permissions. Scope depends on configuration.',\r\n recommendation: 'Rotate at dashboard.stripe.com/apikeys. Audit which permissions are granted.',\r\n },\r\n {\r\n id: 'SP-053',\r\n name: 'Stripe Webhook Secret',\r\n provider: 'stripe',\r\n severity: 'high',\r\n pattern: /whsec_[A-Za-z0-9]{32,60}/,\r\n description: 'Stripe webhook endpoint signing secret. Allows forging webhook events.',\r\n recommendation: 'Rotate webhook secret at dashboard.stripe.com/webhooks. Keep server-side only.',\r\n },\r\n {\r\n id: 'SP-054',\r\n name: 'PayPal Client Secret',\r\n provider: 'paypal',\r\n severity: 'critical',\r\n pattern: /(?:PAYPAL_CLIENT_SECRET|paypal[_-]secret)['\":\\s=]+([A-Za-z0-9_-]{60,80})/i,\r\n description: 'PayPal REST API client secret. Grants ability to process payments and access merchant data.',\r\n recommendation: 'Rotate at developer.paypal.com/developer/applications.',\r\n },\r\n {\r\n id: 'SP-055',\r\n name: 'PayPal Access Token',\r\n provider: 'paypal',\r\n severity: 'high',\r\n pattern: /(?:A21AA|Bearer\\s+)[A-Za-z0-9_-]{80,120}/,\r\n description: 'PayPal OAuth access token. Short-lived but grants API access to payment operations.',\r\n recommendation: 'Access tokens expire but should not be cached in client code.',\r\n },\r\n {\r\n id: 'SP-056',\r\n name: 'Plaid Secret Key',\r\n provider: 'plaid',\r\n severity: 'critical',\r\n pattern: /(?:PLAID_SECRET|plaid[_-]secret)['\":\\s=]+([a-f0-9]{30,40})/i,\r\n description: 'Plaid financial data API secret key. Grants access to bank account and transaction data.',\r\n recommendation: 'Rotate at dashboard.plaid.com/team/keys.',\r\n },\r\n {\r\n id: 'SP-057',\r\n name: 'Plaid Client ID',\r\n provider: 'plaid',\r\n severity: 'medium',\r\n pattern: /(?:PLAID_CLIENT_ID|plaid[_-]client[_-]id)['\":\\s=]+([a-f0-9]{24})/i,\r\n description: 'Plaid application client ID.',\r\n recommendation: 'Low risk alone, but combined with secret key grants bank data access.',\r\n },\r\n {\r\n id: 'SP-058',\r\n name: 'Square Access Token',\r\n provider: 'square',\r\n severity: 'critical',\r\n pattern: /(?:EAA|sq0atp-)[A-Za-z0-9_-]{22,44}/,\r\n description: 'Square payment processing access token.',\r\n recommendation: 'Rotate at developer.squareup.com/apps.',\r\n },\r\n {\r\n id: 'SP-059',\r\n name: 'Shopify Admin API Key',\r\n provider: 'shopify',\r\n severity: 'critical',\r\n pattern: /shpat_[a-fA-F0-9]{32}/,\r\n description: 'Shopify Admin API access token. Grants full store management capabilities.',\r\n recommendation: 'Revoke at Shopify Admin > Settings > Apps and Sales Channels > API.',\r\n },\r\n {\r\n id: 'SP-060',\r\n name: 'Shopify Storefront Token',\r\n provider: 'shopify',\r\n severity: 'medium',\r\n pattern: /shpss_[a-fA-F0-9]{32}/,\r\n description: 'Shopify Storefront API access token. More limited than admin token.',\r\n recommendation: 'Designed for client use but ensure scopes are minimum necessary.',\r\n },\r\n\r\n // ── Communication / Messaging ──\r\n\r\n {\r\n id: 'SP-061',\r\n name: 'Slack Bot Token',\r\n provider: 'slack',\r\n severity: 'high',\r\n pattern: /xoxb-[0-9]+-[0-9]+-[A-Za-z0-9]+/,\r\n description: 'Slack bot OAuth token. Grants access to post messages, read channels, and perform bot actions.',\r\n recommendation: 'Rotate at api.slack.com/apps. Scope tokens to minimum required permissions.',\r\n },\r\n {\r\n id: 'SP-062',\r\n name: 'Slack User Token',\r\n provider: 'slack',\r\n severity: 'critical',\r\n pattern: /xoxp-[0-9]+-[0-9]+-[0-9]+-[A-Za-z0-9]+/,\r\n description: 'Slack user OAuth token. Acts as the user — can read all accessible messages and DMs.',\r\n recommendation: 'Rotate at api.slack.com/apps. User tokens should never be in application code.',\r\n },\r\n {\r\n id: 'SP-063',\r\n name: 'Slack App-Level Token',\r\n provider: 'slack',\r\n severity: 'high',\r\n pattern: /xapp-[0-9]+-[A-Za-z0-9]+-[A-Za-z0-9]+/,\r\n description: 'Slack app-level token for socket mode and org-wide operations.',\r\n recommendation: 'Rotate at api.slack.com/apps.',\r\n },\r\n {\r\n id: 'SP-064',\r\n name: 'Slack Webhook URL',\r\n provider: 'slack',\r\n severity: 'medium',\r\n pattern: /https:\\/\\/hooks\\.slack\\.com\\/services\\/T[A-Z0-9]+\\/B[A-Z0-9]+\\/[A-Za-z0-9]+/,\r\n description: 'Slack incoming webhook URL. Allows posting messages to a specific channel.',\r\n recommendation: 'Rotate at api.slack.com/apps > Incoming Webhooks. Treat as sensitive.',\r\n },\r\n {\r\n id: 'SP-065',\r\n name: 'Discord Bot Token',\r\n provider: 'discord',\r\n severity: 'critical',\r\n pattern: /[MN][A-Za-z0-9_-]{23}\\.[\\w-]{6}\\.[\\w-]{27,38}/,\r\n description: 'Discord bot token. Grants full bot account access — can read all visible messages and take actions.',\r\n recommendation: 'Regenerate at discord.com/developers/applications. Immediately revoke if exposed.',\r\n },\r\n {\r\n id: 'SP-066',\r\n name: 'Discord Webhook URL',\r\n provider: 'discord',\r\n severity: 'medium',\r\n pattern: /https:\\/\\/discord(?:app)?\\.com\\/api\\/webhooks\\/[0-9]+\\/[A-Za-z0-9_-]+/,\r\n description: 'Discord webhook URL. Allows posting to a channel without a bot account.',\r\n recommendation: 'Delete and recreate the webhook at the Discord channel settings.',\r\n },\r\n {\r\n id: 'SP-067',\r\n name: 'Discord Client Secret',\r\n provider: 'discord',\r\n severity: 'high',\r\n pattern: /(?:DISCORD_CLIENT_SECRET|discord[_-]secret)['\":\\s=]+([A-Za-z0-9_-]{32})/i,\r\n description: 'Discord OAuth application client secret.',\r\n recommendation: 'Rotate at discord.com/developers/applications.',\r\n },\r\n {\r\n id: 'SP-068',\r\n name: 'Twilio Account SID',\r\n provider: 'twilio',\r\n severity: 'medium',\r\n pattern: /AC[a-fA-F0-9]{32}/,\r\n description: 'Twilio Account SID. Identifier needed with auth token to access SMS, voice, and messaging APIs.',\r\n recommendation: 'The SID alone is low risk — it requires the auth token to authenticate.',\r\n },\r\n {\r\n id: 'SP-069',\r\n name: 'Twilio Auth Token',\r\n provider: 'twilio',\r\n severity: 'critical',\r\n pattern: /(?:TWILIO_AUTH_TOKEN|twilio[_-]auth[_-]token)['\":\\s=]+([a-fA-F0-9]{32})/i,\r\n description: 'Twilio auth token. Combined with Account SID grants full access to messaging, phone numbers, and billing.',\r\n recommendation: 'Rotate at console.twilio.com. Enable API key authentication instead of master auth token.',\r\n },\r\n {\r\n id: 'SP-070',\r\n name: 'Twilio API Key Secret',\r\n provider: 'twilio',\r\n severity: 'high',\r\n pattern: /(?:SK[a-fA-F0-9]{32})/,\r\n description: 'Twilio API Key SID (more restricted than auth token, still sensitive).',\r\n recommendation: 'Rotate at console.twilio.com/user/api-keys.',\r\n },\r\n {\r\n id: 'SP-071',\r\n name: 'SendGrid API Key',\r\n provider: 'sendgrid',\r\n severity: 'high',\r\n pattern: /SG\\.[A-Za-z0-9_-]{22,30}\\.[A-Za-z0-9_-]{43,50}/,\r\n description: 'SendGrid email API key. Grants ability to send transactional email at your expense.',\r\n recommendation: 'Rotate at app.sendgrid.com/settings/api_keys.',\r\n },\r\n {\r\n id: 'SP-072',\r\n name: 'Resend API Key',\r\n provider: 'resend',\r\n severity: 'high',\r\n pattern: /re_[A-Za-z0-9_]{36,40}/,\r\n description: 'Resend transactional email API key.',\r\n recommendation: 'Rotate at resend.com/api-keys.',\r\n },\r\n {\r\n id: 'SP-073',\r\n name: 'Mailgun API Key',\r\n provider: 'mailgun',\r\n severity: 'high',\r\n pattern: /key-[a-fA-F0-9]{32}/,\r\n description: 'Mailgun private API key for sending and receiving email.',\r\n recommendation: 'Rotate at app.mailgun.com/app/account/security/api_keys.',\r\n },\r\n {\r\n id: 'SP-074',\r\n name: 'Mailchimp API Key',\r\n provider: 'mailchimp',\r\n severity: 'high',\r\n pattern: /[0-9a-f]{32}-us\\d{1,2}/,\r\n description: 'Mailchimp API key. Grants access to mailing lists, campaigns, and subscriber data.',\r\n recommendation: 'Rotate at account.mailchimp.com/settings/api-keys.',\r\n },\r\n\r\n // ── Developer Tools ──\r\n\r\n {\r\n id: 'SP-075',\r\n name: 'GitHub Personal Access Token (Classic)',\r\n provider: 'github',\r\n severity: 'critical',\r\n pattern: /ghp_[A-Za-z0-9]{36}/,\r\n description: 'GitHub classic personal access token. Scope depends on permissions granted when created.',\r\n recommendation: 'Revoke at github.com/settings/tokens. GitHub automatically detects and alerts on these.',\r\n },\r\n {\r\n id: 'SP-076',\r\n name: 'GitHub Fine-Grained Token',\r\n provider: 'github',\r\n severity: 'critical',\r\n pattern: /github_pat_[A-Za-z0-9_]{82}/,\r\n description: 'GitHub fine-grained personal access token with repository-level permissions.',\r\n recommendation: 'Revoke at github.com/settings/tokens.',\r\n },\r\n {\r\n id: 'SP-077',\r\n name: 'GitHub OAuth App Token',\r\n provider: 'github',\r\n severity: 'high',\r\n pattern: /gho_[A-Za-z0-9]{36}/,\r\n description: 'GitHub OAuth app token. Grants access as the user who authorized the app.',\r\n recommendation: 'Revoke at github.com/settings/applications.',\r\n },\r\n {\r\n id: 'SP-078',\r\n name: 'GitHub Actions Token',\r\n provider: 'github',\r\n severity: 'high',\r\n pattern: /ghs_[A-Za-z0-9]{36}/,\r\n description: 'GitHub Actions installation token. Short-lived but should not appear in artifacts.',\r\n recommendation: 'These expire within an hour but should not be logged or exposed in build artifacts.',\r\n },\r\n {\r\n id: 'SP-079',\r\n name: 'GitHub App Private Key',\r\n provider: 'github',\r\n severity: 'critical',\r\n pattern: /ghv_[A-Za-z0-9]{36}/,\r\n description: 'GitHub repository-scoped token from a GitHub App installation.',\r\n recommendation: 'Revoke via GitHub App settings.',\r\n },\r\n {\r\n id: 'SP-080',\r\n name: 'npm Publish Token',\r\n provider: 'npm',\r\n severity: 'critical',\r\n pattern: /npm_[A-Za-z0-9]{36}/,\r\n description: 'npm publish access token. Grants ability to publish packages under your account — supply chain attack vector.',\r\n recommendation: 'Revoke at npmjs.com/settings/tokens. This is the highest risk secret for package maintainers.',\r\n },\r\n {\r\n id: 'SP-081',\r\n name: 'PyPI API Token',\r\n provider: 'pypi',\r\n severity: 'critical',\r\n pattern: /pypi-[A-Za-z0-9_-]{32,80}/,\r\n description: 'PyPI package publish token. Grants ability to publish Python packages — supply chain attack vector.',\r\n recommendation: 'Revoke at pypi.org/manage/account/token. Use scoped tokens per-project.',\r\n },\r\n\r\n // ── Databases ──\r\n\r\n {\r\n id: 'SP-082',\r\n name: 'PostgreSQL Connection String',\r\n provider: 'database',\r\n severity: 'critical',\r\n pattern: /postgres(?:ql)?:\\/\\/[^:@\\s]+:[^@\\s]+@[^/\\s]+\\/[^\\s\"']+/i,\r\n description: 'PostgreSQL connection string with embedded credentials.',\r\n recommendation: 'Rotate database credentials. Use connection poolers (PgBouncer, Supabase Pooler) and secrets managers.',\r\n },\r\n {\r\n id: 'SP-083',\r\n name: 'MySQL Connection String',\r\n provider: 'database',\r\n severity: 'critical',\r\n pattern: /mysql:\\/\\/[^:@\\s]+:[^@\\s]+@[^/\\s]+\\/[^\\s\"']+/i,\r\n description: 'MySQL connection string with embedded credentials.',\r\n recommendation: 'Rotate credentials. Never include database URLs in client code.',\r\n },\r\n {\r\n id: 'SP-084',\r\n name: 'MongoDB Connection String',\r\n provider: 'database',\r\n severity: 'critical',\r\n pattern: /mongodb(?:\\+srv)?:\\/\\/[^:@\\s]+:[^@\\s]+@[^\\s\"']+/i,\r\n description: 'MongoDB connection string with embedded credentials.',\r\n recommendation: 'Rotate credentials. Enable IP allowlisting. Use MongoDB Atlas connection string with IAM auth.',\r\n },\r\n {\r\n id: 'SP-085',\r\n name: 'Redis Connection String',\r\n provider: 'database',\r\n severity: 'high',\r\n pattern: /redis(?:s)?:\\/\\/(?:[^:@\\s]+:[^@\\s]+@)?[^/\\s]+(?::\\d+)?/i,\r\n description: 'Redis connection string, potentially with authentication credentials.',\r\n recommendation: 'Enable Redis AUTH. Use TLS (rediss://). Do not expose Redis to the public internet.',\r\n },\r\n {\r\n id: 'SP-086',\r\n name: 'PlanetScale Service Token',\r\n provider: 'planetscale',\r\n severity: 'critical',\r\n pattern: /pscale_tkn_[A-Za-z0-9_]{32,50}/,\r\n description: 'PlanetScale database service token.',\r\n recommendation: 'Rotate at app.planetscale.com/settings/service-tokens.',\r\n },\r\n {\r\n id: 'SP-087',\r\n name: 'Neon Database URL',\r\n provider: 'neon',\r\n severity: 'critical',\r\n pattern: /postgresql:\\/\\/[^:]+:[^@]+@[a-z0-9-]+\\.(?:us-east-\\d|eu-central-\\d)\\.aws\\.neon\\.tech\\/[^\\s\"']+/,\r\n description: 'Neon serverless PostgreSQL connection URL with credentials.',\r\n recommendation: 'Rotate credentials at console.neon.tech. Use connection pooling endpoint.',\r\n },\r\n {\r\n id: 'SP-088',\r\n name: 'Turso Database URL',\r\n provider: 'turso',\r\n severity: 'high',\r\n pattern: /libsql:\\/\\/[a-z0-9-]+-[a-z0-9]+\\.turso\\.io/,\r\n description: 'Turso edge SQLite database connection URL.',\r\n recommendation: 'Combine with auth token check. Rotate at app.turso.tech.',\r\n },\r\n {\r\n id: 'SP-089',\r\n name: 'Turso Auth Token',\r\n provider: 'turso',\r\n severity: 'high',\r\n pattern: /(?:TURSO_AUTH_TOKEN|turso[_-]token)['\":\\s=]+([A-Za-z0-9_-]{100,200})/i,\r\n description: 'Turso database auth token.',\r\n recommendation: 'Rotate at app.turso.tech/databases.',\r\n },\r\n\r\n // ── Analytics / Monitoring ──\r\n\r\n {\r\n id: 'SP-090',\r\n name: 'Sentry DSN',\r\n provider: 'sentry',\r\n severity: 'low',\r\n pattern: /https:\\/\\/[a-fA-F0-9]{32}@[a-z0-9]+\\.ingest\\.sentry\\.io\\/[0-9]+/,\r\n description: 'Sentry Data Source Name. Can receive arbitrary error events and expose error data to unauthorized parties.',\r\n recommendation: 'Rate-limit the DSN. Consider using Sentry Security Headers to restrict event submission.',\r\n },\r\n {\r\n id: 'SP-091',\r\n name: 'Datadog API Key',\r\n provider: 'datadog',\r\n severity: 'high',\r\n pattern: /(?:DD_API_KEY|DATADOG_API_KEY)['\":\\s=]+([a-fA-F0-9]{32})/i,\r\n description: 'Datadog API key for submitting metrics, logs, and events.',\r\n recommendation: 'Rotate at app.datadoghq.com/organization-settings/api-keys.',\r\n },\r\n {\r\n id: 'SP-092',\r\n name: 'Datadog App Key',\r\n provider: 'datadog',\r\n severity: 'high',\r\n pattern: /(?:DD_APP_KEY|DATADOG_APP_KEY)['\":\\s=]+([a-fA-F0-9]{40})/i,\r\n description: 'Datadog application key for querying and managing Datadog resources.',\r\n recommendation: 'Rotate at app.datadoghq.com/organization-settings/application-keys.',\r\n },\r\n {\r\n id: 'SP-093',\r\n name: 'New Relic License Key',\r\n provider: 'newrelic',\r\n severity: 'high',\r\n pattern: /(?:NEW_RELIC_LICENSE_KEY|NRLIC)['\":\\s=]+([A-Za-z0-9]{40})/i,\r\n description: 'New Relic ingest license key for APM, logs, and infrastructure.',\r\n recommendation: 'Rotate at one.newrelic.com/api-keys.',\r\n },\r\n {\r\n id: 'SP-094',\r\n name: 'PostHog API Key',\r\n provider: 'posthog',\r\n severity: 'medium',\r\n pattern: /phc_[A-Za-z0-9]{43}/,\r\n description: 'PostHog project API key. Client-side analytics key — can be semi-public but controls data ingest.',\r\n recommendation: 'This key is typically safe on the client, but ensure project-level access controls are configured.',\r\n },\r\n {\r\n id: 'SP-095',\r\n name: 'Amplitude API Key',\r\n provider: 'amplitude',\r\n severity: 'medium',\r\n pattern: /(?:AMPLITUDE_API_KEY|amplitude[_-]api[_-]key)['\":\\s=]+([a-fA-F0-9]{32})/i,\r\n description: 'Amplitude analytics project API key.',\r\n recommendation: 'Low risk for the API key, but the secret key should be server-side only.',\r\n },\r\n {\r\n id: 'SP-096',\r\n name: 'Mixpanel Token',\r\n provider: 'mixpanel',\r\n severity: 'low',\r\n pattern: /(?:MIXPANEL_TOKEN|NEXT_PUBLIC_MIXPANEL)['\":\\s=]+([a-fA-F0-9]{32})/i,\r\n description: 'Mixpanel analytics project token. Designed to be public for event tracking.',\r\n recommendation: 'Project token is safe to be public. Keep the project secret server-side.',\r\n },\r\n {\r\n id: 'SP-097',\r\n name: 'Segment Write Key',\r\n provider: 'segment',\r\n severity: 'medium',\r\n pattern: /(?:SEGMENT_WRITE_KEY|analytics\\.load\\()['\":\\s(]+([A-Za-z0-9]{20,30})/i,\r\n description: 'Segment analytics write key. Allows sending arbitrary events to your workspace.',\r\n recommendation: 'Client-side write keys are semi-public, but server write keys should be kept private.',\r\n },\r\n\r\n // ── Search and Data ──\r\n\r\n {\r\n id: 'SP-098',\r\n name: 'Algolia App ID & API Key',\r\n provider: 'algolia',\r\n severity: 'medium',\r\n pattern: /(?:ALGOLIA_API_KEY|NEXT_PUBLIC_ALGOLIA_API_KEY)['\":\\s=]+([A-Za-z0-9]{32})/i,\r\n description: 'Algolia search API key. Admin key grants full index management; search-only key is safe to expose.',\r\n recommendation: 'Use search-only API keys on the client. Rotate admin keys at algolia.com/account/api-keys.',\r\n },\r\n {\r\n id: 'SP-099',\r\n name: 'Algolia Admin API Key',\r\n provider: 'algolia',\r\n severity: 'critical',\r\n pattern: /(?:ALGOLIA_ADMIN_API_KEY)['\":\\s=]+([A-Za-z0-9]{32})/i,\r\n description: 'Algolia admin API key. Grants full index management including delete operations.',\r\n recommendation: 'Never expose admin key on client. Rotate at algolia.com/account/api-keys.',\r\n },\r\n {\r\n id: 'SP-100',\r\n name: 'Mapbox Access Token',\r\n provider: 'mapbox',\r\n severity: 'medium',\r\n pattern: /pk\\.[a-zA-Z0-9.]+\\.[a-zA-Z0-9_-]+/,\r\n description: 'Mapbox public access token. Designed for client use but can be abused for quota theft.',\r\n recommendation: 'Restrict token by URL in Mapbox account settings.',\r\n },\r\n\r\n // ── Cryptographic / Generic ──\r\n\r\n {\r\n id: 'SP-101',\r\n name: 'RSA Private Key',\r\n provider: 'cryptographic',\r\n severity: 'critical',\r\n pattern: /-----BEGIN RSA PRIVATE KEY-----/,\r\n description: 'RSA private key embedded in source. Can be used for decryption or identity impersonation.',\r\n recommendation: 'Remove immediately. Use secrets managers (AWS Secrets Manager, Vault, Doppler).',\r\n },\r\n {\r\n id: 'SP-102',\r\n name: 'EC Private Key',\r\n provider: 'cryptographic',\r\n severity: 'critical',\r\n pattern: /-----BEGIN EC PRIVATE KEY-----/,\r\n description: 'Elliptic curve private key embedded in source.',\r\n recommendation: 'Remove immediately. Rotate the key pair and audit for any usage.',\r\n },\r\n {\r\n id: 'SP-103',\r\n name: 'PKCS8 Private Key',\r\n provider: 'cryptographic',\r\n severity: 'critical',\r\n pattern: /-----BEGIN PRIVATE KEY-----/,\r\n description: 'PKCS#8 private key in unencrypted PEM format.',\r\n recommendation: 'Remove immediately. Use environment variables with proper secret management.',\r\n },\r\n {\r\n id: 'SP-104',\r\n name: 'PGP Private Key',\r\n provider: 'cryptographic',\r\n severity: 'critical',\r\n pattern: /-----BEGIN PGP PRIVATE KEY BLOCK-----/,\r\n description: 'PGP/GPG private key embedded in source.',\r\n recommendation: 'Remove and revoke the key at your keyserver.',\r\n },\r\n {\r\n id: 'SP-105',\r\n name: 'Generic JWT Secret',\r\n provider: 'generic',\r\n severity: 'high',\r\n pattern: /(?:JWT_SECRET|jwt[_-]secret|JWT_SIGNING_KEY)['\":\\s=]+[\"']?([A-Za-z0-9_+/=-]{32,})/i,\r\n description: 'JWT signing secret. Anyone with this secret can forge valid authentication tokens for your application.',\r\n recommendation: 'Rotate the secret and invalidate all existing tokens. Use asymmetric keys (RS256/ES256) instead of symmetric.',\r\n },\r\n {\r\n id: 'SP-106',\r\n name: 'Generic API Secret',\r\n provider: 'generic',\r\n severity: 'high',\r\n pattern: /(?:API_SECRET|app[_-]secret|CLIENT_SECRET)['\":\\s=]+[\"']?([A-Za-z0-9_+/=.-]{32,})/i,\r\n description: 'Generic application secret or client secret.',\r\n recommendation: 'Audit the usage of this secret and rotate if exposed.',\r\n },\r\n {\r\n id: 'SP-107',\r\n name: 'Generic Webhook Secret',\r\n provider: 'generic',\r\n severity: 'high',\r\n pattern: /(?:WEBHOOK_SECRET|webhook[_-]signing[_-]key)['\":\\s=]+[\"']?([A-Za-z0-9_+/=-]{32,})/i,\r\n description: 'Webhook signing secret. Allows forging webhook payloads from third-party services.',\r\n recommendation: 'Rotate the webhook secret at the originating service.',\r\n },\r\n\r\n // ── Social Platforms ──\r\n\r\n {\r\n id: 'SP-108',\r\n name: 'Twitter/X Bearer Token',\r\n provider: 'twitter',\r\n severity: 'high',\r\n pattern: /AAAAAAAAAAAAAAAAAAAAAA[A-Za-z0-9%]+/,\r\n description: 'Twitter/X API v2 bearer token. Grants read access to public Twitter data.',\r\n recommendation: 'Rotate at developer.twitter.com/en/apps.',\r\n },\r\n {\r\n id: 'SP-109',\r\n name: 'Twitter API Key',\r\n provider: 'twitter',\r\n severity: 'high',\r\n pattern: /(?:TWITTER_API_KEY|TWITTER_CONSUMER_KEY)['\":\\s=]+([A-Za-z0-9]{25})/i,\r\n description: 'Twitter/X API consumer key.',\r\n recommendation: 'Rotate at developer.twitter.com/en/apps.',\r\n },\r\n {\r\n id: 'SP-110',\r\n name: 'Twitter API Secret',\r\n provider: 'twitter',\r\n severity: 'critical',\r\n pattern: /(?:TWITTER_API_SECRET|TWITTER_CONSUMER_SECRET)['\":\\s=]+([A-Za-z0-9]{50})/i,\r\n description: 'Twitter/X API consumer secret. Combined with key grants OAuth application access.',\r\n recommendation: 'Rotate at developer.twitter.com/en/apps. Required to regenerate if compromised.',\r\n },\r\n {\r\n id: 'SP-111',\r\n name: 'HubSpot Private App Token',\r\n provider: 'hubspot',\r\n severity: 'high',\r\n pattern: /pat-(?:na|eu)\\d-[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/,\r\n description: 'HubSpot private app access token. Grants CRM data access.',\r\n recommendation: 'Rotate at app.hubspot.com/private-apps.',\r\n },\r\n {\r\n id: 'SP-112',\r\n name: 'Notion Integration Token',\r\n provider: 'notion',\r\n severity: 'high',\r\n pattern: /secret_[A-Za-z0-9]{43}/,\r\n description: 'Notion internal integration token. Grants access to all pages shared with the integration.',\r\n recommendation: 'Rotate at notion.so/my-integrations.',\r\n },\r\n {\r\n id: 'SP-113',\r\n name: 'Airtable API Key',\r\n provider: 'airtable',\r\n severity: 'high',\r\n pattern: /(?:AIRTABLE_API_KEY)['\":\\s=]+([A-Za-z0-9]{17})/i,\r\n description: 'Airtable personal API key. Grants access to all bases the account can access.',\r\n recommendation: 'Rotate at airtable.com/account. Prefer scoped personal access tokens.',\r\n },\r\n {\r\n id: 'SP-114',\r\n name: 'Airtable Personal Access Token',\r\n provider: 'airtable',\r\n severity: 'high',\r\n pattern: /pat[A-Za-z0-9]{14}\\.[a-fA-F0-9]{64}/,\r\n description: 'Airtable scoped personal access token.',\r\n recommendation: 'Rotate at airtable.com/create/tokens.',\r\n },\r\n {\r\n id: 'SP-115',\r\n name: 'Linear API Key',\r\n provider: 'linear',\r\n severity: 'high',\r\n pattern: /lin_api_[A-Za-z0-9]{40}/,\r\n description: 'Linear project management API key. Grants access to issues, projects, and teams.',\r\n recommendation: 'Rotate at linear.app/settings/api.',\r\n },\r\n\r\n // ── Infrastructure / DevOps ──\r\n\r\n {\r\n id: 'SP-116',\r\n name: 'HashiCorp Vault Token',\r\n provider: 'hashicorp',\r\n severity: 'critical',\r\n pattern: /(?:VAULT_TOKEN|hvs\\.[A-Za-z0-9]+)/,\r\n description: 'HashiCorp Vault service token. Grants access to secrets stored in Vault.',\r\n recommendation: 'Revoke with vault token revoke. Enable token TTLs and audit logging.',\r\n },\r\n {\r\n id: 'SP-117',\r\n name: 'Kubernetes Service Account Token',\r\n provider: 'kubernetes',\r\n severity: 'critical',\r\n pattern: /eyJhbGciOiJSUzI1NiIsImtpZCI[A-Za-z0-9_-]+\\.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50/,\r\n description: 'Kubernetes service account JWT token. Grants API server access within the cluster.',\r\n recommendation: 'Rotate the service account token. Use short-lived projected tokens (TokenRequest API).',\r\n },\r\n {\r\n id: 'SP-118',\r\n name: 'Cloudinary URL',\r\n provider: 'cloudinary',\r\n severity: 'high',\r\n pattern: /cloudinary:\\/\\/[0-9]+:[A-Za-z0-9_-]+@[a-z0-9_]+/,\r\n description: 'Cloudinary media storage URL with API credentials.',\r\n recommendation: 'Rotate at cloudinary.com/console. This grants upload and transformation access.',\r\n },\r\n {\r\n id: 'SP-119',\r\n name: 'Upstash Redis Token',\r\n provider: 'upstash',\r\n severity: 'high',\r\n pattern: /(?:UPSTASH_REDIS_REST_TOKEN|upstash[_-]token)['\":\\s=]+([A-Za-z0-9_-]{50,100})/i,\r\n description: 'Upstash serverless Redis REST API token.',\r\n recommendation: 'Rotate at console.upstash.com.',\r\n },\r\n {\r\n id: 'SP-120',\r\n name: 'Upstash QStash Token',\r\n provider: 'upstash',\r\n severity: 'high',\r\n pattern: /(?:QSTASH_TOKEN)['\":\\s=]+([A-Za-z0-9_-]{100,200})/i,\r\n description: 'Upstash QStash serverless message queue token.',\r\n recommendation: 'Rotate at console.upstash.com/qstash.',\r\n },\r\n\r\n // ── Additional AI/ML Platforms ──\r\n\r\n {\r\n id: 'SP-121',\r\n name: 'Portkey API Key',\r\n provider: 'portkey',\r\n severity: 'medium',\r\n pattern: /(?:PORTKEY_API_KEY)['\":\\s=]+([A-Za-z0-9_-]{40,60})/i,\r\n description: 'Portkey AI gateway API key.',\r\n recommendation: 'Rotate at app.portkey.ai/settings.',\r\n },\r\n {\r\n id: 'SP-122',\r\n name: 'Langfuse Secret Key',\r\n provider: 'langfuse',\r\n severity: 'medium',\r\n pattern: /sk-lf-[A-Za-z0-9_-]{36,50}/,\r\n description: 'Langfuse LLM observability secret key.',\r\n recommendation: 'Rotate at cloud.langfuse.com/project/settings.',\r\n },\r\n {\r\n id: 'SP-123',\r\n name: 'Weaviate API Key',\r\n provider: 'weaviate',\r\n severity: 'high',\r\n pattern: /(?:WEAVIATE_API_KEY)['\":\\s=]+([A-Za-z0-9_-]{32,50})/i,\r\n description: 'Weaviate vector database API key.',\r\n recommendation: 'Rotate at the Weaviate Cloud Services console.',\r\n },\r\n {\r\n id: 'SP-124',\r\n name: 'Pinecone API Key',\r\n provider: 'pinecone',\r\n severity: 'high',\r\n pattern: /(?:PINECONE_API_KEY)['\":\\s=]+([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})/i,\r\n description: 'Pinecone vector database API key. Grants access to vector indices and stored embeddings.',\r\n recommendation: 'Rotate at app.pinecone.io/organization/api-keys.',\r\n },\r\n {\r\n id: 'SP-125',\r\n name: 'Browserbase API Key',\r\n provider: 'browserbase',\r\n severity: 'high',\r\n pattern: /(?:BROWSERBASE_API_KEY)['\":\\s=]+([A-Za-z0-9_-]{32,50})/i,\r\n description: 'Browserbase cloud browser API key for web automation.',\r\n recommendation: 'Rotate at browserbase.com/settings.',\r\n },\r\n\r\n // ── Payments (extended) ──\r\n\r\n {\r\n id: 'SP-126',\r\n name: 'Braintree API Key',\r\n provider: 'braintree',\r\n severity: 'critical',\r\n pattern: /(?:BRAINTREE_PRIVATE_KEY|braintree[_-]private[_-]key)['\":\\s=]+([A-Za-z0-9]{32})/i,\r\n description: 'Braintree payment processing private key.',\r\n recommendation: 'Rotate at sandbox.braintreegateway.com or braintreegateway.com.',\r\n },\r\n\r\n // ── Communication (extended) ──\r\n\r\n {\r\n id: 'SP-127',\r\n name: 'Pusher App Secret',\r\n provider: 'pusher',\r\n severity: 'high',\r\n pattern: /(?:PUSHER_APP_SECRET)['\":\\s=]+([a-fA-F0-9]{20})/i,\r\n description: 'Pusher Channels app secret for server-side event authentication.',\r\n recommendation: 'Rotate at dashboard.pusher.com.',\r\n },\r\n {\r\n id: 'SP-128',\r\n name: 'Ably API Key',\r\n provider: 'ably',\r\n severity: 'high',\r\n pattern: /[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+:[A-Za-z0-9_+/=]{32,50}/,\r\n description: 'Ably realtime messaging API key.',\r\n recommendation: 'Rotate at ably.com/accounts.',\r\n },\r\n {\r\n id: 'SP-129',\r\n name: 'Zoom JWT Token',\r\n provider: 'zoom',\r\n severity: 'high',\r\n pattern: /(?:ZOOM_JWT_TOKEN|zoom[_-]jwt)['\":\\s=]+([A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+)/i,\r\n description: 'Zoom JWT application token for Server-to-Server OAuth.',\r\n recommendation: 'Rotate at marketplace.zoom.us/develop/apps.',\r\n },\r\n\r\n // ── Data Platforms ──\r\n\r\n {\r\n id: 'SP-130',\r\n name: 'Elastic Search API Key',\r\n provider: 'elastic',\r\n severity: 'high',\r\n pattern: /(?:ELASTICSEARCH_API_KEY|elastic[_-]api[_-]key)['\":\\s=]+([A-Za-z0-9_=]+:{[^}]+})/i,\r\n description: 'Elasticsearch / Elastic Cloud API key.',\r\n recommendation: 'Rotate in Kibana > Stack Management > Security > API Keys.',\r\n },\r\n {\r\n id: 'SP-131',\r\n name: 'Kafka SASL Password',\r\n provider: 'kafka',\r\n severity: 'critical',\r\n pattern: /(?:KAFKA_SASL_PASSWORD|kafka[_-]password)['\":\\s=]+([^\\s\"']{8,})/i,\r\n description: 'Apache Kafka SASL authentication password.',\r\n recommendation: 'Rotate and enable SSL/TLS for Kafka connections.',\r\n },\r\n\r\n // ── Observability (extended) ──\r\n\r\n {\r\n id: 'SP-132',\r\n name: 'Axiom API Token',\r\n provider: 'axiom',\r\n severity: 'medium',\r\n pattern: /xaat-[A-Za-z0-9_-]{36,50}/,\r\n description: 'Axiom log analytics API token.',\r\n recommendation: 'Rotate at app.axiom.co/settings/api-tokens.',\r\n },\r\n {\r\n id: 'SP-133',\r\n name: 'Grafana API Key',\r\n provider: 'grafana',\r\n severity: 'high',\r\n pattern: /(?:GRAFANA_API_KEY|grafana[_-]token)['\":\\s=]+([A-Za-z0-9=]{40,100})/i,\r\n description: 'Grafana Cloud API key for metrics, logs, and traces.',\r\n recommendation: 'Rotate at grafana.com/profile/api-keys.',\r\n },\r\n\r\n // ── Environment Variable Leaks ──\r\n\r\n {\r\n id: 'SP-134',\r\n name: 'Exposed NEXT_PUBLIC Secret Variable',\r\n provider: 'nextjs',\r\n severity: 'high',\r\n pattern: /NEXT_PUBLIC_(?:SECRET|KEY|TOKEN|PASSWORD|PRIVATE)[A-Z_]*['\":\\s=]+[\"']?[A-Za-z0-9_+/=-]{20,}/i,\r\n description: 'Variable prefixed NEXT_PUBLIC_ (client-bundle exposure) with a name suggesting it is a secret. NEXT_PUBLIC_ variables are always bundled into the client.',\r\n recommendation: 'Remove NEXT_PUBLIC_ prefix. Move to server-side environment variables or use Next.js server actions.',\r\n },\r\n {\r\n id: 'SP-135',\r\n name: 'Exposed VITE Secret Variable',\r\n provider: 'vite',\r\n severity: 'high',\r\n pattern: /VITE_(?:SECRET|KEY|TOKEN|PASSWORD|PRIVATE)[A-Z_]*['\":\\s=]+[\"']?[A-Za-z0-9_+/=-]{20,}/i,\r\n description: 'Variable prefixed VITE_ (client-bundle exposure) with a name suggesting it is a secret. VITE_ variables are always bundled into the client.',\r\n recommendation: 'Remove VITE_ prefix and handle server-side. Use API routes for sensitive operations.',\r\n },\r\n {\r\n id: 'SP-136',\r\n name: 'Hardcoded Password Pattern',\r\n provider: 'generic',\r\n severity: 'high',\r\n pattern: /(?:password|passwd|db_pass|db_password)['\":\\s=]+[\"'](?!.*\\${)[A-Za-z0-9!@#$%^&*_+=]{8,}/i,\r\n description: 'Hardcoded password string that does not appear to be a template variable.',\r\n recommendation: 'Move credentials to environment variables and use a secrets manager.',\r\n },\r\n\r\n // ── AI Agent Ecosystem ──\r\n\r\n {\r\n id: 'SP-137',\r\n name: 'Smithery Registry Token',\r\n provider: 'smithery',\r\n severity: 'high',\r\n pattern: /(?:SMITHERY_API_KEY|smithery[_-]token)['\":\\s=]+([A-Za-z0-9_-]{30,60})/i,\r\n description: 'Smithery MCP registry API token. Grants publish access to MCP server packages.',\r\n recommendation: 'Rotate at smithery.ai/settings. Supply chain risk — protect publish access.',\r\n },\r\n {\r\n id: 'SP-138',\r\n name: 'Cursor API Key',\r\n provider: 'cursor',\r\n severity: 'medium',\r\n pattern: /(?:CURSOR_API_KEY)['\":\\s=]+([A-Za-z0-9_-]{30,60})/i,\r\n description: 'Cursor IDE API integration key.',\r\n recommendation: 'Rotate at cursor.com/settings.',\r\n },\r\n {\r\n id: 'SP-139',\r\n name: 'Vigile API Key',\r\n provider: 'vigile',\r\n severity: 'medium',\r\n pattern: /vgl_[A-Za-z0-9_-]{40,60}/,\r\n description: 'Vigile AI API key. Grants access to Vigile scan API and trust registry.',\r\n recommendation: 'Rotate at vigile.dev/account.',\r\n },\r\n {\r\n id: 'SP-140',\r\n name: 'OpenAI API Key (via env)',\r\n provider: 'openai',\r\n severity: 'critical',\r\n pattern: /(?:OPENAI_API_KEY)['\":\\s=]+[\"']?(sk-[A-Za-z0-9_-]{48,})/i,\r\n description: 'OpenAI API key referenced in an environment variable assignment.',\r\n recommendation: 'Rotate at platform.openai.com/api-keys. Move to server-side proxy.',\r\n },\r\n {\r\n id: 'SP-141',\r\n name: 'Anthropic API Key (via env)',\r\n provider: 'anthropic',\r\n severity: 'critical',\r\n pattern: /(?:ANTHROPIC_API_KEY)['\":\\s=]+[\"']?(sk-ant-api[A-Za-z0-9_-]{90,})/i,\r\n description: 'Anthropic API key in an environment variable assignment.',\r\n recommendation: 'Rotate at console.anthropic.com. Never include in client bundles.',\r\n },\r\n\r\n // ── Extended Coverage ──\r\n\r\n {\r\n id: 'SP-142',\r\n name: 'Stripe Secret Key (Test)',\r\n provider: 'stripe',\r\n severity: 'medium',\r\n pattern: /sk_test_[A-Za-z0-9]{24,100}/,\r\n description: 'Stripe test-mode secret key. Should not be in production bundles.',\r\n recommendation: 'Test keys should not appear in client code even in development builds.',\r\n },\r\n {\r\n id: 'SP-143',\r\n name: 'GitHub App Installation Token',\r\n provider: 'github',\r\n severity: 'high',\r\n pattern: /ghi_[A-Za-z0-9]{36}/,\r\n description: 'GitHub App installation token (internal use).',\r\n recommendation: 'These are short-lived but should not appear in artifacts.',\r\n },\r\n {\r\n id: 'SP-144',\r\n name: 'Supabase JWT Secret',\r\n provider: 'supabase',\r\n severity: 'critical',\r\n pattern: /(?:SUPABASE_JWT_SECRET|JWT_SECRET)['\":\\s=]+[\"']?[A-Za-z0-9_+/=-]{32,}/i,\r\n description: 'JWT signing secret for Supabase Auth. Anyone with this secret can forge valid session tokens.',\r\n recommendation: 'This secret is set at project creation and cannot be rotated without breaking all sessions.',\r\n },\r\n {\r\n id: 'SP-145',\r\n name: 'Firebase Admin Private Key',\r\n provider: 'firebase',\r\n severity: 'critical',\r\n pattern: /(?:FIREBASE_PRIVATE_KEY)['\":\\s=]+[\"']?-----BEGIN (RSA )?PRIVATE KEY/i,\r\n description: 'Firebase Admin SDK private key. Grants admin-level database and Auth access.',\r\n recommendation: 'Remove from client code immediately. Firebase Admin SDK must only run server-side.',\r\n },\r\n {\r\n id: 'SP-146',\r\n name: 'Google Analytics Measurement ID',\r\n provider: 'google',\r\n severity: 'low',\r\n pattern: /G-[A-Z0-9]{10}/,\r\n description: 'Google Analytics 4 Measurement ID. Designed to be public — included for inventory purposes.',\r\n recommendation: 'This is public by design but note it for compliance/privacy documentation.',\r\n },\r\n {\r\n id: 'SP-147',\r\n name: 'Imagekit Private Key',\r\n provider: 'imagekit',\r\n severity: 'high',\r\n pattern: /(?:IMAGEKIT_PRIVATE_KEY)['\":\\s=]+([A-Za-z0-9_-]{30,60})/i,\r\n description: 'Imagekit.io private API key for image upload and management.',\r\n recommendation: 'Rotate at imagekit.io/dashboard/developer/api-keys.',\r\n },\r\n {\r\n id: 'SP-148',\r\n name: 'ScraperAPI Key',\r\n provider: 'scraperapi',\r\n severity: 'medium',\r\n pattern: /(?:SCRAPERAPI_KEY|scraperapi[_-]key)['\":\\s=]+([a-fA-F0-9]{32})/i,\r\n description: 'ScraperAPI proxy key for web scraping requests.',\r\n recommendation: 'Rotate at scraperapi.com/dashboard.',\r\n },\r\n {\r\n id: 'SP-149',\r\n name: 'Salesforce Connected App Secret',\r\n provider: 'salesforce',\r\n severity: 'critical',\r\n pattern: /(?:SF_CLIENT_SECRET|SALESFORCE_SECRET)['\":\\s=]+([A-Za-z0-9_-]{64})/i,\r\n description: 'Salesforce Connected App client secret. Grants CRM access as the connected application.',\r\n recommendation: 'Rotate in Salesforce Setup > App Manager.',\r\n },\r\n {\r\n id: 'SP-150',\r\n name: 'Generic Bearer Token',\r\n provider: 'generic',\r\n severity: 'medium',\r\n pattern: /Authorization:\\s*Bearer\\s+([A-Za-z0-9_-]{40,200})/i,\r\n description: 'Bearer token hardcoded in Authorization header — likely a live credential.',\r\n recommendation: 'Remove hardcoded tokens. Generate tokens at runtime from securely stored credentials.',\r\n },\r\n];\r\n\r\n// ============================================================\r\n// Core Detection Function\r\n// ============================================================\r\n\r\n/**\r\n * Scan text content for exposed secrets.\r\n * Uses `String.prototype.matchAll` with global regex to find all occurrences.\r\n *\r\n * @param text - JavaScript bundle content or any text to scan\r\n * @returns Array of SecretMatch objects (empty if no secrets found)\r\n */\r\nexport function matchSecrets(text: string): SecretMatch[] {\r\n const results: SecretMatch[] = [];\r\n\r\n for (const sp of SECRET_PATTERNS) {\r\n // Build global version of the pattern to find all occurrences\r\n const flags = sp.pattern.flags.includes('i') ? 'gi' : 'g';\r\n const globalPattern = new RegExp(sp.pattern.source, flags);\r\n\r\n for (const m of text.matchAll(globalPattern)) {\r\n const matched = m[0];\r\n const idx = m.index ?? 0;\r\n\r\n // Extract surrounding context for the report\r\n const start = Math.max(0, idx - 25);\r\n const end = Math.min(text.length, idx + matched.length + 25);\r\n const context = text.slice(start, end).replace(/\\s+/g, ' ').trim();\r\n\r\n results.push({\r\n pattern: sp,\r\n match: mask(matched),\r\n context,\r\n });\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n","// ============================================================\r\n// Vigile CLI — BaaS Bundle Analyzer\r\n// Downloads JS bundles from a deployed web app and scans them\r\n// for exposed secrets using the secret-patterns library.\r\n// ============================================================\r\n//\r\n// Limits (chosen to mirror browser devtools behavior):\r\n// MAX_BUNDLE_SIZE 5 MB — realistic for any production bundle\r\n// MAX_BUNDLES 10 — covers most SPAs; prevents runaway\r\n// FETCH_TIMEOUT 15 s — generous enough for slow edge CDNs\r\n\r\nimport { matchSecrets } from './secret-patterns.js';\r\nimport type { Finding } from '../../types/index.js';\r\n\r\nconst MAX_BUNDLE_SIZE = 5 * 1024 * 1024; // 5 MB\r\nconst MAX_BUNDLES = 10;\r\nconst FETCH_TIMEOUT_MS = 15_000;\r\n\r\nexport interface BundleAnalysisResult {\r\n /** The app URL that was scanned */\r\n url: string;\r\n /** Number of JS bundles successfully analyzed */\r\n bundlesAnalyzed: number;\r\n /** Security findings from all bundles */\r\n findings: Finding[];\r\n /** Non-fatal errors (e.g. one bundle 404'd but others succeeded) */\r\n errors: string[];\r\n}\r\n\r\n// ── HTML script-source extraction ──────────────────────────\r\n\r\n/**\r\n * Parses raw HTML and returns all script src values that look\r\n * like JS bundle paths (relative or absolute).\r\n */\r\nfunction extractScriptSrcs(html: string, baseUrl: string): string[] {\r\n const srcs: string[] = [];\r\n // Match <script src=\"...\"> and <script src='...'>\r\n const scriptRe = /<script[^>]+src=[\"']([^\"']+)[\"'][^>]*>/gi;\r\n for (const m of html.matchAll(scriptRe)) {\r\n const src = m[1];\r\n if (!src) continue;\r\n // Skip obvious non-bundle srcs (inline polyfills, analytics tags)\r\n if (src.startsWith('data:')) continue;\r\n try {\r\n const resolved = new URL(src, baseUrl).toString();\r\n srcs.push(resolved);\r\n } catch {\r\n // Malformed src — skip\r\n }\r\n }\r\n return srcs;\r\n}\r\n\r\n// ── Fetch helpers ───────────────────────────────────────────\r\n\r\nasync function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), timeoutMs);\r\n try {\r\n const response = await fetch(url, { signal: controller.signal });\r\n return response;\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n}\r\n\r\nasync function fetchText(url: string): Promise<{ text: string; error?: string }> {\r\n try {\r\n const res = await fetchWithTimeout(url, FETCH_TIMEOUT_MS);\r\n if (!res.ok) {\r\n return { text: '', error: `HTTP ${res.status} for ${url}` };\r\n }\r\n const contentLength = parseInt(res.headers.get('content-length') ?? '0', 10);\r\n if (contentLength > MAX_BUNDLE_SIZE) {\r\n return { text: '', error: `Bundle too large (${contentLength} bytes): ${url}` };\r\n }\r\n const buffer = await res.arrayBuffer();\r\n if (buffer.byteLength > MAX_BUNDLE_SIZE) {\r\n return { text: '', error: `Bundle exceeds 5 MB limit: ${url}` };\r\n }\r\n return { text: new TextDecoder().decode(buffer) };\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n return { text: '', error: `Fetch failed for ${url}: ${msg}` };\r\n }\r\n}\r\n\r\n// ── Finding construction ────────────────────────────────────\r\n\r\nfunction makeSecretFinding(\r\n bundleUrl: string,\r\n matchedId: string,\r\n matchedName: string,\r\n severity: 'critical' | 'high' | 'medium' | 'low',\r\n maskedValue: string,\r\n context: string,\r\n index: number,\r\n): Finding {\r\n return {\r\n id: `BU-${String(index + 1).padStart(3, '0')}`,\r\n category: 'exposed-secret',\r\n severity,\r\n title: `Exposed ${matchedName} secret in JS bundle`,\r\n description:\r\n `A ${matchedName} credential was found in a compiled JavaScript bundle ` +\r\n `at ${bundleUrl}. Secrets baked into frontend bundles are readable by ` +\r\n `anyone who inspects your app's network traffic or source code.`,\r\n evidence: `Pattern: ${matchedId} | Masked value: ${maskedValue} | Context: ${context}`,\r\n recommendation:\r\n `Move this secret to a server-side environment variable. Never include ` +\r\n `API keys or tokens in frontend JavaScript. Use a backend proxy or BFF ` +\r\n `(Backend for Frontend) pattern to make authenticated API calls.`,\r\n };\r\n}\r\n\r\n// ── Main export ─────────────────────────────────────────────\r\n\r\n/**\r\n * Downloads JS bundles from a deployed web app URL, then scans\r\n * each bundle for exposed secrets using secret-patterns.ts.\r\n *\r\n * @param appUrl The root URL of the deployed app (e.g. https://myapp.com)\r\n */\r\nexport async function analyzeBundles(appUrl: string): Promise<BundleAnalysisResult> {\r\n const errors: string[] = [];\r\n const findings: Finding[] = [];\r\n let bundlesAnalyzed = 0;\r\n\r\n // Normalise URL\r\n const baseUrl = appUrl.endsWith('/') ? appUrl : `${appUrl}/`;\r\n\r\n // Step 1: Fetch the root HTML page\r\n const { text: html, error: htmlError } = await fetchText(baseUrl);\r\n if (htmlError || !html) {\r\n errors.push(htmlError ?? `Could not fetch root HTML for ${baseUrl}`);\r\n return { url: appUrl, bundlesAnalyzed: 0, findings, errors };\r\n }\r\n\r\n // Step 2: Extract script sources\r\n const scriptUrls = extractScriptSrcs(html, baseUrl).slice(0, MAX_BUNDLES);\r\n if (scriptUrls.length === 0) {\r\n errors.push(`No <script src> tags found in HTML at ${baseUrl}`);\r\n return { url: appUrl, bundlesAnalyzed: 0, findings, errors };\r\n }\r\n\r\n // Step 3: Fetch and scan each bundle\r\n let findingIndex = 0;\r\n for (const scriptUrl of scriptUrls) {\r\n const { text: bundleText, error: fetchError } = await fetchText(scriptUrl);\r\n if (fetchError || !bundleText) {\r\n errors.push(fetchError ?? `Empty bundle at ${scriptUrl}`);\r\n continue;\r\n }\r\n\r\n bundlesAnalyzed++;\r\n\r\n const matches = matchSecrets(bundleText);\r\n for (const secretMatch of matches) {\r\n findings.push(\r\n makeSecretFinding(\r\n scriptUrl,\r\n secretMatch.pattern.id,\r\n secretMatch.pattern.name,\r\n secretMatch.pattern.severity,\r\n secretMatch.match,\r\n secretMatch.context,\r\n findingIndex++,\r\n ),\r\n );\r\n }\r\n }\r\n\r\n return { url: appUrl, bundlesAnalyzed, findings, errors };\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Supabase Scanner\r\n// Actively probes a Supabase project for RLS misconfigurations,\r\n// exposed credentials, auth settings, and CORS policy issues.\r\n// ============================================================\r\n//\r\n// Finding IDs:\r\n// SB-001 RLS disabled (anon read allowed) — critical\r\n// SB-002 Anon key exposed in frontend bundle — high\r\n// SB-003 Email confirmation disabled — medium\r\n// SB-004 CORS wildcard policy — medium\r\n// SB-005 Service role key exposed in bundle — critical\r\n// SB-006 Anon write allowed (RLS disabled) — critical\r\n// SB-007 Open signup enabled — low\r\n// ============================================================\r\n\r\nimport type { Finding } from '../../types/index.js';\r\nimport { analyzeBundles } from './bundle-analyzer.js';\r\n\r\nexport interface SupabaseScanOptions {\r\n /** Supabase project URL (e.g. https://abcdefgh.supabase.co) */\r\n projectUrl: string;\r\n /** Anon/public key — usually safe to provide; used to test anon-level access */\r\n anonKey?: string;\r\n}\r\n\r\nexport interface SupabaseScanResult {\r\n projectUrl: string;\r\n findings: Finding[];\r\n /** Tables discovered via anon REST API */\r\n tablesFound: string[];\r\n /** Whether anon read was allowed on any table */\r\n anonReadExposed: boolean;\r\n /** Whether the project URL resolves (basic reachability) */\r\n reachable: boolean;\r\n errors: string[];\r\n}\r\n\r\nconst FETCH_TIMEOUT_MS = 10_000;\r\n\r\n// ── Fetch with timeout (same pattern as bundle-analyzer) ────\r\n\r\nasync function fetchWithTimeout(\r\n url: string,\r\n init?: RequestInit,\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\r\n try {\r\n return await fetch(url, { ...init, signal: controller.signal });\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n}\r\n\r\n// ── URL normalisation ───────────────────────────────────────\r\n\r\nfunction normaliseSupabaseUrl(url: string): string {\r\n let u = url.replace(/\\/+$/, '');\r\n if (!u.startsWith('http')) {\r\n u = `https://${u}`;\r\n }\r\n return u;\r\n}\r\n\r\n// ── Anon key extraction from bundle findings ────────────────\r\n\r\n/**\r\n * Look through bundle analysis findings for a Supabase anon key.\r\n * SP-022/SP-023 patterns match the JWT format Supabase uses for\r\n * anon and service_role keys (eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...).\r\n */\r\nfunction extractAnonKeyFromFindings(findings: Finding[]): string | null {\r\n for (const f of findings) {\r\n const ev = f.evidence ?? '';\r\n if (\r\n (ev.includes('supabase') || ev.includes('SP-022') || ev.includes('SP-023')) &&\r\n ev.includes('eyJ')\r\n ) {\r\n const jwtMatch = ev.match(/eyJ[A-Za-z0-9_-]+\\.eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+/);\r\n if (jwtMatch) return jwtMatch[0];\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n// ── Main export ─────────────────────────────────────────────\r\n\r\n/**\r\n * Scans a Supabase project for security misconfigurations.\r\n *\r\n * Scan flow:\r\n * 1. Reachability check — does the REST API respond?\r\n * 2. Bundle analysis — scan the hosting URL for exposed keys\r\n * 3. Anon key resolution — use provided key, or extract from bundle\r\n * 4. Table enumeration — GET /rest/v1/ with anon key\r\n * 5. RLS testing — probe each table for anon read/write\r\n * 6. Auth settings — GET /auth/v1/settings\r\n * 7. CORS check — inspect Access-Control-Allow-Origin header\r\n */\r\nexport async function scanSupabase(\r\n opts: SupabaseScanOptions,\r\n): Promise<SupabaseScanResult> {\r\n const findings: Finding[] = [];\r\n const errors: string[] = [];\r\n const tablesFound: string[] = [];\r\n let anonReadExposed = false;\r\n let reachable = false;\r\n\r\n const baseUrl = normaliseSupabaseUrl(opts.projectUrl);\r\n\r\n // ── Step 1: Reachability ────────────────────────────────\r\n try {\r\n const res = await fetchWithTimeout(`${baseUrl}/rest/v1/`, {\r\n method: 'HEAD',\r\n });\r\n // Supabase returns 401 without apikey — that's still reachable\r\n reachable = res.status === 401 || res.status === 200 || res.status === 403;\r\n } catch {\r\n errors.push(`Supabase project not reachable at ${baseUrl}`);\r\n return { projectUrl: baseUrl, findings, tablesFound, anonReadExposed, reachable, errors };\r\n }\r\n\r\n if (!reachable) {\r\n errors.push(`Supabase project returned unexpected status at ${baseUrl}/rest/v1/`);\r\n return { projectUrl: baseUrl, findings, tablesFound, anonReadExposed, reachable, errors };\r\n }\r\n\r\n // ── Step 2: Bundle analysis (for key exposure) ──────────\r\n const bundleResult = await analyzeBundles(baseUrl);\r\n findings.push(...bundleResult.findings);\r\n if (bundleResult.errors.length > 0) {\r\n errors.push(...bundleResult.errors.map((e) => `[bundle] ${e}`));\r\n }\r\n\r\n // Check for service_role key specifically\r\n const hasServiceRoleKey = bundleResult.findings.some(\r\n (f) =>\r\n f.evidence?.includes('service_role') ||\r\n f.evidence?.includes('SP-023'),\r\n );\r\n if (hasServiceRoleKey) {\r\n findings.push({\r\n id: 'SB-005',\r\n category: 'exposed-secret',\r\n severity: 'critical',\r\n title: 'Supabase service_role key exposed in frontend bundle',\r\n description:\r\n 'The Supabase service_role key was found in a JavaScript bundle. ' +\r\n 'This key bypasses all Row Level Security policies and grants full ' +\r\n 'read/write/delete access to every table and storage bucket. ' +\r\n 'This is equivalent to database superuser access.',\r\n evidence: 'Detected via bundle analysis — service_role JWT in compiled JS',\r\n recommendation:\r\n 'Rotate the service_role key immediately in Supabase Dashboard > ' +\r\n 'Settings > API. This key must NEVER appear in frontend code. ' +\r\n 'Use it only in server-side functions (Edge Functions, API routes).',\r\n });\r\n }\r\n\r\n // ── Step 3: Resolve anon key ────────────────────────────\r\n let anonKey = opts.anonKey ?? null;\r\n if (!anonKey) {\r\n anonKey = extractAnonKeyFromFindings(bundleResult.findings);\r\n }\r\n\r\n if (!anonKey) {\r\n errors.push(\r\n 'No anon key provided or detected in bundles — skipping RLS and table enumeration. ' +\r\n 'Pass --supabase-key or ensure the app URL serves JS bundles with the anon key.',\r\n );\r\n }\r\n\r\n // ── Step 4: Table enumeration ───────────────────────────\r\n if (anonKey) {\r\n try {\r\n const tablesRes = await fetchWithTimeout(`${baseUrl}/rest/v1/`, {\r\n headers: {\r\n apikey: anonKey,\r\n Authorization: `Bearer ${anonKey}`,\r\n },\r\n });\r\n\r\n if (tablesRes.ok) {\r\n try {\r\n const schema = (await tablesRes.json()) as Record<string, unknown>;\r\n // The /rest/v1/ endpoint returns an OpenAPI spec.\r\n // Table names appear as keys under \"paths\" (e.g. \"/users\").\r\n const paths = (schema.paths ?? {}) as Record<string, unknown>;\r\n for (const path of Object.keys(paths)) {\r\n const tableName = path.replace(/^\\//, '').split('?')[0];\r\n if (tableName && !tableName.includes('/')) {\r\n tablesFound.push(tableName);\r\n }\r\n }\r\n } catch {\r\n errors.push('Could not parse Supabase REST schema response');\r\n }\r\n }\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`Table enumeration failed: ${msg}`);\r\n }\r\n\r\n // ── Step 5: RLS testing per table ───────────────────────\r\n for (const table of tablesFound) {\r\n // Test anon READ\r\n try {\r\n const readRes = await fetchWithTimeout(\r\n `${baseUrl}/rest/v1/${table}?select=*&limit=1`,\r\n {\r\n headers: {\r\n apikey: anonKey,\r\n Authorization: `Bearer ${anonKey}`,\r\n },\r\n },\r\n );\r\n\r\n if (readRes.ok) {\r\n const body = await readRes.text();\r\n // Non-empty array means data was returned — RLS is off for reads\r\n if (body.startsWith('[') && body !== '[]') {\r\n anonReadExposed = true;\r\n findings.push({\r\n id: 'SB-001',\r\n category: 'rls-misconfiguration',\r\n severity: 'critical',\r\n title: `RLS disabled: anonymous read on \"${table}\"`,\r\n description:\r\n `Table \"${table}\" returned data via the anon key without any Row Level ` +\r\n `Security policies. Any user with the anon key (which is public) can ` +\r\n `read all rows in this table. This is the #1 Supabase security mistake.`,\r\n evidence: `GET /rest/v1/${table}?select=*&limit=1 returned 200 with data`,\r\n recommendation:\r\n `Enable RLS on table \"${table}\" in Supabase Dashboard > Database > ` +\r\n `Tables. Add a policy like: CREATE POLICY \"auth read\" ON \"${table}\" ` +\r\n `FOR SELECT USING (auth.uid() = user_id);`,\r\n });\r\n }\r\n }\r\n } catch {\r\n // Individual table probe failure — non-fatal\r\n }\r\n\r\n // Test anon WRITE\r\n // POST with empty object — 400 means RLS let it through to DB layer\r\n // (schema constraints caught it), 401/403 means RLS blocked it\r\n try {\r\n const writeRes = await fetchWithTimeout(\r\n `${baseUrl}/rest/v1/${table}`,\r\n {\r\n method: 'POST',\r\n headers: {\r\n apikey: anonKey,\r\n Authorization: `Bearer ${anonKey}`,\r\n 'Content-Type': 'application/json',\r\n Prefer: 'return=minimal',\r\n },\r\n body: JSON.stringify({}),\r\n },\r\n );\r\n\r\n // 400 = RLS didn't block, schema constraints did (RLS is OFF)\r\n // 201/200 = insert succeeded (RLS is OFF and no constraints)\r\n // 401/403 = RLS blocked it (safe)\r\n if (\r\n writeRes.status === 400 ||\r\n writeRes.status === 201 ||\r\n writeRes.status === 200\r\n ) {\r\n findings.push({\r\n id: 'SB-006',\r\n category: 'rls-misconfiguration',\r\n severity: 'critical',\r\n title: `RLS disabled: anonymous write on \"${table}\"`,\r\n description:\r\n `Table \"${table}\" allows INSERT operations via the anon key. The ` +\r\n `request reached the database layer (RLS did not block it). Even if ` +\r\n `it failed on a constraint, the lack of RLS means anyone can attempt ` +\r\n `to write data to this table.`,\r\n evidence: `POST /rest/v1/${table} returned ${writeRes.status} (not 401/403)`,\r\n recommendation:\r\n `Enable RLS on table \"${table}\" and add INSERT policies. For example: ` +\r\n `CREATE POLICY \"auth insert\" ON \"${table}\" FOR INSERT WITH CHECK ` +\r\n `(auth.uid() = user_id);`,\r\n });\r\n }\r\n } catch {\r\n // Individual write probe failure — non-fatal\r\n }\r\n }\r\n }\r\n\r\n // ── Step 6: Auth configuration ──────────────────────────\r\n if (anonKey) {\r\n try {\r\n const authRes = await fetchWithTimeout(`${baseUrl}/auth/v1/settings`, {\r\n headers: {\r\n apikey: anonKey,\r\n Authorization: `Bearer ${anonKey}`,\r\n },\r\n });\r\n\r\n if (authRes.ok) {\r\n try {\r\n const settings = (await authRes.json()) as Record<string, unknown>;\r\n\r\n const autoconfirm = settings.mailer_autoconfirm ?? settings.autoconfirm;\r\n if (autoconfirm === true) {\r\n findings.push({\r\n id: 'SB-003',\r\n category: 'auth-misconfiguration',\r\n severity: 'medium',\r\n title: 'Email confirmation disabled (autoconfirm enabled)',\r\n description:\r\n 'Supabase auth is configured to automatically confirm email addresses ' +\r\n 'without requiring the user to click a verification link. This allows ' +\r\n 'attackers to create accounts with any email address, including ' +\r\n 'impersonating legitimate users.',\r\n evidence: 'GET /auth/v1/settings returned mailer_autoconfirm: true',\r\n recommendation:\r\n 'Disable autoconfirm in Supabase Dashboard > Authentication > ' +\r\n 'Settings > Email Auth. Require email verification for all new signups.',\r\n });\r\n }\r\n\r\n const disableSignup = settings.disable_signup;\r\n if (disableSignup === false) {\r\n findings.push({\r\n id: 'SB-007',\r\n category: 'auth-misconfiguration',\r\n severity: 'low',\r\n title: 'Open signup enabled',\r\n description:\r\n 'Public signup is enabled on this Supabase project. If this is an ' +\r\n 'internal tool or admin panel, open signup allows anyone to create ' +\r\n 'an account.',\r\n evidence: 'GET /auth/v1/settings returned disable_signup: false',\r\n recommendation:\r\n 'If this project is not meant for public registration, disable ' +\r\n 'signup in Supabase Dashboard > Authentication > Settings.',\r\n });\r\n }\r\n } catch {\r\n errors.push('Could not parse auth settings response');\r\n }\r\n }\r\n } catch {\r\n errors.push('Auth settings endpoint not reachable');\r\n }\r\n }\r\n\r\n // ── Step 7: CORS policy check ───────────────────────────\r\n try {\r\n const corsRes = await fetchWithTimeout(`${baseUrl}/rest/v1/`, {\r\n method: 'OPTIONS',\r\n headers: {\r\n Origin: 'https://evil-attacker-site.com',\r\n 'Access-Control-Request-Method': 'GET',\r\n },\r\n });\r\n\r\n const allowOrigin = corsRes.headers.get('access-control-allow-origin');\r\n if (allowOrigin === '*' || allowOrigin === 'https://evil-attacker-site.com') {\r\n const isReflected = allowOrigin === 'https://evil-attacker-site.com';\r\n findings.push({\r\n id: 'SB-004',\r\n category: 'cors-misconfiguration',\r\n severity: 'medium',\r\n title: isReflected\r\n ? 'CORS reflects arbitrary origins on REST API'\r\n : 'CORS wildcard policy on REST API',\r\n description: isReflected\r\n ? 'The Supabase REST API reflects any Origin header back in ' +\r\n 'Access-Control-Allow-Origin, which is functionally equivalent ' +\r\n 'to a wildcard policy but harder to detect.'\r\n : 'The Supabase REST API returns Access-Control-Allow-Origin: * which ' +\r\n 'allows any website to make authenticated requests to your API. ' +\r\n 'Combined with an exposed anon key, this enables cross-origin ' +\r\n 'data access from malicious sites.',\r\n evidence: isReflected\r\n ? 'OPTIONS /rest/v1/ reflected origin: https://evil-attacker-site.com'\r\n : 'OPTIONS /rest/v1/ returned Access-Control-Allow-Origin: *',\r\n recommendation:\r\n 'Configure allowed origins in Supabase Dashboard > Settings > API. ' +\r\n 'Restrict to your app domain(s) only.',\r\n });\r\n }\r\n } catch {\r\n // CORS check is non-critical\r\n }\r\n\r\n return {\r\n projectUrl: baseUrl,\r\n findings,\r\n tablesFound,\r\n anonReadExposed,\r\n reachable,\r\n errors,\r\n };\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Firebase Scanner\r\n// Probes a Firebase project for Firestore/RTDB public access,\r\n// Storage bucket exposure, config leaks, and missing App Check.\r\n// ============================================================\r\n//\r\n// Finding IDs:\r\n// FB-001 Firestore public read/write — critical\r\n// FB-002 RTDB public read/write — critical\r\n// FB-003 Firebase config object in bundle — high\r\n// FB-004 Storage bucket publicly listable — high\r\n// FB-005 Missing security headers on hosting — medium\r\n// FB-006 RTDB .indexOn missing (data exposure) — medium\r\n// ============================================================\r\n\r\nimport type { Finding } from '../../types/index.js';\r\nimport { analyzeBundles } from './bundle-analyzer.js';\r\n\r\nexport interface FirebaseScanOptions {\r\n /** Firebase project URL (e.g. https://my-project.web.app or https://my-project.firebaseapp.com) */\r\n projectUrl: string;\r\n /** Firebase project ID — extracted from projectUrl if not provided */\r\n projectId?: string;\r\n}\r\n\r\nexport interface FirebaseScanResult {\r\n projectUrl: string;\r\n projectId: string | null;\r\n findings: Finding[];\r\n /** Whether Firestore rules allow unauthenticated read/write */\r\n firestorePublicAccess: boolean;\r\n /** Whether RTDB rules allow unauthenticated read/write */\r\n rtdbPublicAccess: boolean;\r\n /** Whether Firebase config object was found exposed in bundles */\r\n configExposed: boolean;\r\n reachable: boolean;\r\n errors: string[];\r\n}\r\n\r\nconst FETCH_TIMEOUT_MS = 10_000;\r\n\r\n// ── Fetch with timeout ──────────────────────────────────────\r\n\r\nasync function fetchWithTimeout(\r\n url: string,\r\n init?: RequestInit,\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\r\n try {\r\n return await fetch(url, { ...init, signal: controller.signal });\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n}\r\n\r\n// ── URL normalisation ───────────────────────────────────────\r\n\r\nfunction normaliseUrl(url: string): string {\r\n let u = url.replace(/\\/+$/, '');\r\n if (!u.startsWith('http')) {\r\n u = `https://${u}`;\r\n }\r\n return u;\r\n}\r\n\r\n// ── Project ID extraction ───────────────────────────────────\r\n\r\n/** Best-effort project ID extraction from Firebase hosting URLs. */\r\nfunction extractProjectId(url: string): string | null {\r\n try {\r\n const hostname = new URL(url).hostname;\r\n // my-project.web.app -> my-project\r\n // my-project.firebaseapp.com -> my-project\r\n const match = hostname.match(/^([^.]+)\\.(web\\.app|firebaseapp\\.com)$/);\r\n return match?.[1] ?? null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n// ── Firestore config extraction from bundle findings ────────\r\n\r\ninterface FirebaseConfigFromBundle {\r\n apiKey?: string;\r\n projectId?: string;\r\n storageBucket?: string;\r\n}\r\n\r\n/**\r\n * Attempt to extract Firebase config values from bundle analysis\r\n * findings. The SP-015/SP-016/SP-017 patterns match Firebase\r\n * apiKey, projectId, and storageBucket in compiled JS.\r\n */\r\nfunction extractConfigFromFindings(findings: Finding[]): FirebaseConfigFromBundle {\r\n const config: FirebaseConfigFromBundle = {};\r\n for (const f of findings) {\r\n const ev = f.evidence ?? '';\r\n // Firebase API key pattern (AIzaSy...)\r\n if (ev.includes('AIzaSy')) {\r\n const keyMatch = ev.match(/AIzaSy[A-Za-z0-9_-]{33}/);\r\n if (keyMatch) config.apiKey = keyMatch[0];\r\n }\r\n // Project ID from evidence\r\n if (ev.includes('projectId') || ev.includes('firebase')) {\r\n const projMatch = ev.match(/[\"']([a-z0-9-]+)\\.firebaseapp\\.com[\"']/);\r\n if (projMatch) config.projectId = projMatch[1];\r\n }\r\n // Storage bucket\r\n if (ev.includes('storageBucket') || ev.includes('.appspot.com')) {\r\n const bucketMatch = ev.match(/([a-z0-9-]+)\\.appspot\\.com/);\r\n if (bucketMatch) config.storageBucket = bucketMatch[0];\r\n }\r\n }\r\n return config;\r\n}\r\n\r\n// ── Main export ─────────────────────────────────────────────\r\n\r\n/**\r\n * Scans a Firebase-backed app for security misconfigurations.\r\n *\r\n * Scan flow:\r\n * 1. Extract project ID from URL\r\n * 2. Reachability check\r\n * 3. Bundle analysis — look for Firebase config exposure\r\n * 4. Firestore REST API — probe for unauthenticated read\r\n * 5. RTDB — probe /.json for unauthenticated read\r\n * 6. Storage — probe bucket for public file listing\r\n * 7. Hosting headers — check for security headers\r\n */\r\nexport async function scanFirebase(\r\n opts: FirebaseScanOptions,\r\n): Promise<FirebaseScanResult> {\r\n const findings: Finding[] = [];\r\n const errors: string[] = [];\r\n let firestorePublicAccess = false;\r\n let rtdbPublicAccess = false;\r\n let configExposed = false;\r\n let reachable = false;\r\n\r\n const baseUrl = normaliseUrl(opts.projectUrl);\r\n const projectId = opts.projectId ?? extractProjectId(baseUrl);\r\n\r\n // ── Step 1: Reachability ────────────────────────────────\r\n try {\r\n const res = await fetchWithTimeout(baseUrl, { method: 'HEAD' });\r\n reachable = res.status >= 200 && res.status < 500;\r\n } catch {\r\n errors.push(`Firebase project not reachable at ${baseUrl}`);\r\n return {\r\n projectUrl: baseUrl, projectId, findings,\r\n firestorePublicAccess, rtdbPublicAccess, configExposed,\r\n reachable, errors,\r\n };\r\n }\r\n\r\n if (!reachable) {\r\n errors.push(`Firebase project returned unexpected status at ${baseUrl}`);\r\n return {\r\n projectUrl: baseUrl, projectId, findings,\r\n firestorePublicAccess, rtdbPublicAccess, configExposed,\r\n reachable, errors,\r\n };\r\n }\r\n\r\n // ── Step 2: Bundle analysis ─────────────────────────────\r\n const bundleResult = await analyzeBundles(baseUrl);\r\n findings.push(...bundleResult.findings);\r\n if (bundleResult.errors.length > 0) {\r\n errors.push(...bundleResult.errors.map((e) => `[bundle] ${e}`));\r\n }\r\n\r\n // Check if Firebase config was exposed in bundles\r\n const bundleConfig = extractConfigFromFindings(bundleResult.findings);\r\n const detectedProjectId = projectId ?? bundleConfig.projectId ?? null;\r\n\r\n if (bundleConfig.apiKey) {\r\n configExposed = true;\r\n findings.push({\r\n id: 'FB-003',\r\n category: 'exposed-secret',\r\n severity: 'high',\r\n title: 'Firebase config object exposed in frontend bundle',\r\n description:\r\n 'The Firebase client configuration (apiKey, projectId, etc.) was found ' +\r\n 'in a JavaScript bundle. While Firebase API keys are designed to be ' +\r\n 'public for client-side use, exposure without App Check enforcement ' +\r\n 'allows abuse: automated account creation, Firestore/RTDB enumeration, ' +\r\n 'and quota exhaustion attacks.',\r\n evidence: `Firebase apiKey: ${bundleConfig.apiKey.slice(0, 8)}*** detected in bundle`,\r\n recommendation:\r\n 'Enable Firebase App Check in the Firebase Console to restrict API ' +\r\n 'access to your legitimate app. Configure reCAPTCHA Enterprise or ' +\r\n 'DeviceCheck attestation. Without App Check, anyone with the config ' +\r\n 'can call your Firebase APIs.',\r\n });\r\n }\r\n\r\n // ── Step 3: Firestore public access probe ───────────────\r\n if (detectedProjectId) {\r\n const firestoreUrl =\r\n `https://firestore.googleapis.com/v1/projects/${detectedProjectId}/databases/(default)/documents`;\r\n\r\n try {\r\n const fsRes = await fetchWithTimeout(firestoreUrl);\r\n\r\n if (fsRes.ok) {\r\n // 200 without auth = Firestore rules allow public read\r\n firestorePublicAccess = true;\r\n\r\n let docCount = 0;\r\n try {\r\n const body = (await fsRes.json()) as { documents?: unknown[] };\r\n docCount = body.documents?.length ?? 0;\r\n } catch {\r\n // JSON parse failure is non-fatal\r\n }\r\n\r\n findings.push({\r\n id: 'FB-001',\r\n category: 'firebase-rules-issue',\r\n severity: 'critical',\r\n title: 'Firestore allows unauthenticated read access',\r\n description:\r\n 'The Firestore REST API returned documents without any authentication ' +\r\n 'token. This means Firestore Security Rules are configured with ' +\r\n '`allow read: if true` or similar permissive rules at the database or ' +\r\n 'collection level. Any data in Firestore is publicly accessible.',\r\n evidence:\r\n `GET ${firestoreUrl} returned 200` +\r\n (docCount > 0 ? ` (${docCount} documents visible)` : ''),\r\n recommendation:\r\n 'Update Firestore Security Rules to require authentication: ' +\r\n '`allow read: if request.auth != null;` at minimum. Ideally, add ' +\r\n 'per-user rules: `allow read: if request.auth.uid == resource.data.userId;`',\r\n });\r\n }\r\n // 403/401 = rules are blocking unauthenticated access (good)\r\n // 404 = no default database or project not found\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`Firestore probe failed: ${msg}`);\r\n }\r\n\r\n // Also test write access\r\n try {\r\n const writeUrl =\r\n `https://firestore.googleapis.com/v1/projects/${detectedProjectId}/databases/(default)/documents/vigile_probe_test`;\r\n const writeRes = await fetchWithTimeout(writeUrl, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n fields: {\r\n _vigile_probe: { stringValue: 'security_test' },\r\n },\r\n }),\r\n });\r\n\r\n // 200 = write succeeded without auth (very bad)\r\n // 400 = request reached Firestore (rules didn't block, bad format)\r\n if (writeRes.status === 200) {\r\n firestorePublicAccess = true;\r\n findings.push({\r\n id: 'FB-001',\r\n category: 'firebase-rules-issue',\r\n severity: 'critical',\r\n title: 'Firestore allows unauthenticated WRITE access',\r\n description:\r\n 'A document was successfully written to Firestore without any ' +\r\n 'authentication. This means anyone on the internet can create, ' +\r\n 'modify, or delete data in your database.',\r\n evidence: `POST to Firestore documents endpoint returned 200`,\r\n recommendation:\r\n 'Immediately update Firestore Security Rules to block unauthenticated ' +\r\n 'writes: `allow write: if request.auth != null;` — and review all ' +\r\n 'existing data for tampering.',\r\n });\r\n }\r\n } catch {\r\n // Write probe failure is non-critical\r\n }\r\n } else {\r\n errors.push(\r\n 'Could not determine Firebase project ID — skipping Firestore/RTDB probes. ' +\r\n 'Provide --firebase-project-id or use a *.web.app / *.firebaseapp.com URL.',\r\n );\r\n }\r\n\r\n // ── Step 4: RTDB public access probe ────────────────────\r\n if (detectedProjectId) {\r\n // Firebase RTDB URLs follow: https://<project-id>-default-rtdb.firebaseio.com/\r\n // or https://<project-id>.firebaseio.com/ for older projects\r\n const rtdbUrls = [\r\n `https://${detectedProjectId}-default-rtdb.firebaseio.com/.json?shallow=true`,\r\n `https://${detectedProjectId}.firebaseio.com/.json?shallow=true`,\r\n ];\r\n\r\n for (const rtdbUrl of rtdbUrls) {\r\n try {\r\n const rtdbRes = await fetchWithTimeout(rtdbUrl);\r\n\r\n if (rtdbRes.ok) {\r\n rtdbPublicAccess = true;\r\n let keyCount = 0;\r\n try {\r\n const body = (await rtdbRes.json()) as Record<string, unknown> | null;\r\n if (body && typeof body === 'object') {\r\n keyCount = Object.keys(body).length;\r\n }\r\n } catch {\r\n // JSON parse failure is non-fatal\r\n }\r\n\r\n findings.push({\r\n id: 'FB-002',\r\n category: 'firebase-rules-issue',\r\n severity: 'critical',\r\n title: 'Realtime Database allows unauthenticated read access',\r\n description:\r\n 'The Firebase Realtime Database returned data at the root path ' +\r\n 'without any authentication. RTDB rules are configured with ' +\r\n '`\".read\": true` or `\".read\": \"auth == null\"` or similar. ' +\r\n 'All data stored in RTDB is publicly accessible.',\r\n evidence:\r\n `GET ${rtdbUrl.replace(/\\?.*/, '')} returned 200` +\r\n (keyCount > 0 ? ` (${keyCount} top-level keys)` : ''),\r\n recommendation:\r\n 'Update RTDB Security Rules to require authentication: ' +\r\n '`\".read\": \"auth != null\"` at minimum. Review what data is stored ' +\r\n 'in RTDB and assume it has been scraped if this rule was open.',\r\n });\r\n\r\n // Only need one RTDB URL to confirm — break\r\n break;\r\n }\r\n // 401 = \"Permission denied\" (good, rules are working)\r\n // 404 = RTDB not provisioned at this URL pattern\r\n } catch {\r\n // Timeout or network error — try the next URL pattern\r\n continue;\r\n }\r\n }\r\n\r\n // Also test RTDB write access\r\n try {\r\n const rtdbWriteUrl =\r\n `https://${detectedProjectId}-default-rtdb.firebaseio.com/vigile_probe_test.json`;\r\n const rtdbWriteRes = await fetchWithTimeout(rtdbWriteUrl, {\r\n method: 'PUT',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ _vigile_probe: 'security_test' }),\r\n });\r\n\r\n if (rtdbWriteRes.ok) {\r\n rtdbPublicAccess = true;\r\n findings.push({\r\n id: 'FB-002',\r\n category: 'firebase-rules-issue',\r\n severity: 'critical',\r\n title: 'Realtime Database allows unauthenticated WRITE access',\r\n description:\r\n 'Data was successfully written to Firebase Realtime Database without ' +\r\n 'any authentication. Anyone can create, modify, or delete data.',\r\n evidence: `PUT to RTDB vigile_probe_test.json returned 200`,\r\n recommendation:\r\n 'Immediately update RTDB Security Rules: `\".write\": \"auth != null\"`. ' +\r\n 'Audit all existing data for tampering. Consider migrating sensitive ' +\r\n 'data to Firestore with stricter per-document rules.',\r\n });\r\n }\r\n } catch {\r\n // Write probe failure is non-critical\r\n }\r\n }\r\n\r\n // ── Step 5: Firebase Storage bucket probe ───────────────\r\n if (detectedProjectId) {\r\n const storageBucket =\r\n bundleConfig.storageBucket ?? `${detectedProjectId}.appspot.com`;\r\n\r\n // Try listing objects in the default bucket\r\n const storageUrl =\r\n `https://firebasestorage.googleapis.com/v0/b/${storageBucket}/o`;\r\n\r\n try {\r\n const storageRes = await fetchWithTimeout(storageUrl);\r\n\r\n if (storageRes.ok) {\r\n let itemCount = 0;\r\n try {\r\n const body = (await storageRes.json()) as { items?: unknown[] };\r\n itemCount = body.items?.length ?? 0;\r\n } catch {\r\n // non-fatal\r\n }\r\n\r\n findings.push({\r\n id: 'FB-004',\r\n category: 'firebase-rules-issue',\r\n severity: 'high',\r\n title: 'Firebase Storage bucket is publicly listable',\r\n description:\r\n 'The Firebase Storage bucket returned a file listing without ' +\r\n 'authentication. Storage Security Rules allow public read access. ' +\r\n 'All files in the bucket can be enumerated and downloaded by anyone.',\r\n evidence:\r\n `GET ${storageUrl} returned 200` +\r\n (itemCount > 0 ? ` (${itemCount} files visible)` : ''),\r\n recommendation:\r\n 'Update Firebase Storage Security Rules to require authentication: ' +\r\n '`allow read: if request.auth != null;` — Review uploaded files for ' +\r\n 'sensitive content (user uploads, profile pictures, documents).',\r\n });\r\n }\r\n } catch {\r\n // Storage probe failure is non-critical\r\n }\r\n }\r\n\r\n // ── Step 6: Hosting security headers ────────────────────\r\n try {\r\n const headRes = await fetchWithTimeout(baseUrl);\r\n const headers = headRes.headers;\r\n\r\n const missingHeaders: string[] = [];\r\n if (!headers.get('x-content-type-options')) {\r\n missingHeaders.push('X-Content-Type-Options');\r\n }\r\n if (!headers.get('x-frame-options') && !headers.get('content-security-policy')?.includes('frame-ancestors')) {\r\n missingHeaders.push('X-Frame-Options or CSP frame-ancestors');\r\n }\r\n if (!headers.get('strict-transport-security')) {\r\n missingHeaders.push('Strict-Transport-Security');\r\n }\r\n\r\n if (missingHeaders.length >= 2) {\r\n findings.push({\r\n id: 'FB-005',\r\n category: 'cors-misconfiguration',\r\n severity: 'medium',\r\n title: 'Firebase Hosting missing security headers',\r\n description:\r\n `The Firebase Hosting response is missing ${missingHeaders.length} ` +\r\n `recommended security headers: ${missingHeaders.join(', ')}. ` +\r\n 'These headers protect against clickjacking, MIME sniffing, and ' +\r\n 'protocol downgrade attacks.',\r\n evidence: `Missing: ${missingHeaders.join(', ')}`,\r\n recommendation:\r\n 'Add security headers in firebase.json under hosting.headers: ' +\r\n '{\"source\": \"**\", \"headers\": [{\"key\": \"X-Content-Type-Options\", ' +\r\n '\"value\": \"nosniff\"}, {\"key\": \"X-Frame-Options\", \"value\": \"DENY\"}]}',\r\n });\r\n }\r\n } catch {\r\n // Header check is non-critical\r\n }\r\n\r\n return {\r\n projectUrl: baseUrl,\r\n projectId: detectedProjectId,\r\n findings,\r\n firestorePublicAccess,\r\n rtdbPublicAccess,\r\n configExposed,\r\n reachable,\r\n errors,\r\n };\r\n}\r\n","// ============================================================\r\n// Vigile CLI — CVE Detector\r\n// Checks detected npm/pypi/cargo packages against the OSV.dev\r\n// vulnerability database (free, no API key required).\r\n// ============================================================\r\n//\r\n// Finding IDs:\r\n// CVE-* Direct CVE from OSV.dev database — severity varies\r\n// GHSA-* GitHub Security Advisory — severity varies\r\n//\r\n// API reference: https://osv.dev/docs/#tag/api/post/v1/querybatch\r\n// ============================================================\r\n\r\nimport type { Finding } from '../../types/index.js';\r\n\r\nconst FETCH_TIMEOUT_MS = 15_000;\r\nconst OSV_BATCH_URL = 'https://api.osv.dev/v1/querybatch';\r\nconst MAX_BATCH_SIZE = 100; // OSV recommends batches of ≤1000, we cap lower\r\n\r\nexport interface DetectedPackage {\r\n /** Package name (npm, PyPI, etc.) */\r\n name: string;\r\n /** Detected version string */\r\n version: string;\r\n /** Ecosystem: npm, pypi, cargo, etc. */\r\n ecosystem: 'npm' | 'pypi' | 'cargo' | 'unknown';\r\n}\r\n\r\nexport interface CveMatch {\r\n pkg: DetectedPackage;\r\n cveId: string;\r\n cvssScore: number;\r\n summary: string;\r\n patchedVersion: string | null;\r\n}\r\n\r\nexport interface CveDetectionResult {\r\n packagesChecked: number;\r\n matches: CveMatch[];\r\n findings: Finding[];\r\n errors: string[];\r\n}\r\n\r\n// ── Fetch with timeout ──────────────────────────────────────\r\n\r\nasync function fetchWithTimeout(\r\n url: string,\r\n init?: RequestInit,\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\r\n try {\r\n return await fetch(url, { ...init, signal: controller.signal });\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n}\r\n\r\n// ── OSV.dev response types ──────────────────────────────────\r\n\r\ninterface OsvSeverity {\r\n type: string;\r\n score: string;\r\n}\r\n\r\ninterface OsvAffectedRange {\r\n type: string;\r\n events: Array<{ introduced?: string; fixed?: string; last_affected?: string }>;\r\n}\r\n\r\ninterface OsvAffectedPackage {\r\n package: { name: string; ecosystem: string };\r\n ranges?: OsvAffectedRange[];\r\n versions?: string[];\r\n}\r\n\r\ninterface OsvVuln {\r\n id: string;\r\n summary?: string;\r\n details?: string;\r\n severity?: OsvSeverity[];\r\n affected?: OsvAffectedPackage[];\r\n aliases?: string[];\r\n}\r\n\r\ninterface OsvBatchResponse {\r\n results: Array<{ vulns?: OsvVuln[] }>;\r\n}\r\n\r\n// ── CVSS score extraction ───────────────────────────────────\r\n\r\n/**\r\n * Extract a numeric CVSS score from OSV severity data.\r\n * OSV provides severity as CVSS_V3 or CVSS_V2 vector strings\r\n * (e.g. \"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\").\r\n * We parse the base score from the vector or fall back to a\r\n * heuristic based on the severity label.\r\n */\r\nfunction extractCvssScore(vuln: OsvVuln): number {\r\n if (vuln.severity && vuln.severity.length > 0) {\r\n for (const sev of vuln.severity) {\r\n // Try to extract numeric score from CVSS vector\r\n const scoreMatch = sev.score.match(/CVSS:[^/]+\\/.*?/);\r\n if (scoreMatch && sev.type === 'CVSS_V3') {\r\n // Parse AV/AC/PR/UI/S/C/I/A components for rough score estimation\r\n // For simplicity, use the ecosystem_specific database_specific severity\r\n return estimateCvssFromVector(sev.score);\r\n }\r\n }\r\n }\r\n // Default: unknown severity\r\n return 5.0;\r\n}\r\n\r\n/**\r\n * Rough CVSS 3.x score estimation from a vector string.\r\n * Full CVSS calculation is complex — we approximate from key metrics.\r\n */\r\nfunction estimateCvssFromVector(vector: string): number {\r\n const v = vector.toUpperCase();\r\n\r\n // Attack vector\r\n const avNetwork = v.includes('/AV:N');\r\n // Privileges required\r\n const prNone = v.includes('/PR:N');\r\n // Confidentiality / Integrity / Availability impact\r\n const cHigh = v.includes('/C:H');\r\n const iHigh = v.includes('/I:H');\r\n const aHigh = v.includes('/A:H');\r\n const cLow = v.includes('/C:L');\r\n const iLow = v.includes('/I:L');\r\n const aLow = v.includes('/A:L');\r\n\r\n let score = 5.0; // base\r\n if (avNetwork) score += 1.5;\r\n if (prNone) score += 1.0;\r\n if (cHigh) score += 0.8;\r\n if (iHigh) score += 0.8;\r\n if (aHigh) score += 0.8;\r\n if (cLow) score += 0.3;\r\n if (iLow) score += 0.3;\r\n if (aLow) score += 0.3;\r\n\r\n return Math.min(10.0, Math.round(score * 10) / 10);\r\n}\r\n\r\n/**\r\n * Extract the earliest fixed version from affected range events.\r\n */\r\nfunction extractPatchedVersion(vuln: OsvVuln, pkgName: string): string | null {\r\n if (!vuln.affected) return null;\r\n\r\n for (const affected of vuln.affected) {\r\n if (affected.package.name !== pkgName) continue;\r\n if (!affected.ranges) continue;\r\n\r\n for (const range of affected.ranges) {\r\n for (const event of range.events) {\r\n if (event.fixed) return event.fixed;\r\n }\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Get a preferred vulnerability ID — prefer CVE IDs over GHSA/OSV IDs.\r\n */\r\nfunction getPreferredId(vuln: OsvVuln): string {\r\n // Check aliases for a CVE ID first\r\n if (vuln.aliases) {\r\n const cve = vuln.aliases.find((a) => a.startsWith('CVE-'));\r\n if (cve) return cve;\r\n }\r\n // Fall back to the primary ID (GHSA-xxx or OSV-xxx)\r\n return vuln.id;\r\n}\r\n\r\n// ── Severity mapping ────────────────────────────────────────\r\n\r\nfunction cvssToSeverity(score: number): 'critical' | 'high' | 'medium' | 'low' {\r\n if (score >= 9.0) return 'critical';\r\n if (score >= 7.0) return 'high';\r\n if (score >= 4.0) return 'medium';\r\n return 'low';\r\n}\r\n\r\n// ── Main export ─────────────────────────────────────────────\r\n\r\n/**\r\n * Checks a list of detected packages against the OSV.dev\r\n * vulnerability database.\r\n *\r\n * Scan flow:\r\n * 1. Build batch query from package list\r\n * 2. POST to OSV.dev /v1/querybatch\r\n * 3. Parse vulnerability records into CveMatch objects\r\n * 4. Build Finding objects for each match\r\n *\r\n * @param packages List of packages detected in the scanned app\r\n */\r\nexport async function detectCves(packages: DetectedPackage[]): Promise<CveDetectionResult> {\r\n const matches: CveMatch[] = [];\r\n const findings: Finding[] = [];\r\n const errors: string[] = [];\r\n\r\n if (packages.length === 0) {\r\n return { packagesChecked: 0, matches, findings, errors };\r\n }\r\n\r\n // Filter out packages with non-parseable versions or 'unknown' ecosystem\r\n const queryablePackages = packages.filter(\r\n (p) => p.ecosystem !== 'unknown' && p.version && !p.version.includes('*'),\r\n );\r\n\r\n if (queryablePackages.length === 0) {\r\n errors.push('No packages with valid version + ecosystem for CVE lookup');\r\n return { packagesChecked: packages.length, matches, findings, errors };\r\n }\r\n\r\n // Batch into chunks of MAX_BATCH_SIZE\r\n const chunks: DetectedPackage[][] = [];\r\n for (let i = 0; i < queryablePackages.length; i += MAX_BATCH_SIZE) {\r\n chunks.push(queryablePackages.slice(i, i + MAX_BATCH_SIZE));\r\n }\r\n\r\n for (const chunk of chunks) {\r\n // Build OSV batch query\r\n const queries = chunk.map((pkg) => ({\r\n package: {\r\n name: pkg.name,\r\n ecosystem: pkg.ecosystem === 'npm' ? 'npm' : pkg.ecosystem === 'pypi' ? 'PyPI' : pkg.ecosystem,\r\n },\r\n version: pkg.version,\r\n }));\r\n\r\n try {\r\n const res = await fetchWithTimeout(OSV_BATCH_URL, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ queries }),\r\n });\r\n\r\n if (!res.ok) {\r\n errors.push(`OSV.dev API returned HTTP ${res.status}`);\r\n continue;\r\n }\r\n\r\n const body = (await res.json()) as OsvBatchResponse;\r\n\r\n // Each result corresponds to the query at the same index\r\n for (let i = 0; i < body.results.length; i++) {\r\n const result = body.results[i];\r\n const pkg = chunk[i];\r\n if (!result?.vulns || result.vulns.length === 0) continue;\r\n\r\n // Deduplicate vulns by preferred ID (CVE > GHSA > OSV)\r\n const seenIds = new Set<string>();\r\n\r\n for (const vuln of result.vulns) {\r\n const vulnId = getPreferredId(vuln);\r\n if (seenIds.has(vulnId)) continue;\r\n seenIds.add(vulnId);\r\n\r\n const cvssScore = extractCvssScore(vuln);\r\n const patchedVersion = extractPatchedVersion(vuln, pkg.name);\r\n const summary = vuln.summary ?? vuln.details?.slice(0, 200) ?? 'No description available';\r\n\r\n const match: CveMatch = {\r\n pkg,\r\n cveId: vulnId,\r\n cvssScore,\r\n summary,\r\n patchedVersion,\r\n };\r\n matches.push(match);\r\n\r\n const severity = cvssToSeverity(cvssScore);\r\n findings.push({\r\n id: vulnId,\r\n category: 'cve-detected',\r\n severity,\r\n title: `${vulnId}: ${pkg.name}@${pkg.version}`,\r\n description:\r\n `Known vulnerability in ${pkg.name} version ${pkg.version}. ` +\r\n `${summary.slice(0, 300)}`,\r\n evidence:\r\n `Package: ${pkg.name}@${pkg.version} (${pkg.ecosystem}) | ` +\r\n `CVSS: ${cvssScore} | ` +\r\n (patchedVersion ? `Fixed in: ${patchedVersion}` : 'No patch available'),\r\n recommendation: patchedVersion\r\n ? `Upgrade ${pkg.name} to version ${patchedVersion} or later. ` +\r\n `Run: npm install ${pkg.name}@${patchedVersion}`\r\n : `No patched version available. Consider removing or replacing ` +\r\n `${pkg.name} with an alternative package. Monitor ${vulnId} for updates.`,\r\n });\r\n }\r\n }\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`OSV.dev query failed: ${msg}`);\r\n }\r\n }\r\n\r\n return {\r\n packagesChecked: packages.length,\r\n matches,\r\n findings,\r\n errors,\r\n };\r\n}\r\n\r\n/**\r\n * Parses package.json text and returns detected npm packages.\r\n * Used by vibe-app-scanner to extract dependencies from fetched\r\n * package.json files or bundle metadata.\r\n */\r\nexport function parseNpmPackages(packageJsonText: string): DetectedPackage[] {\r\n try {\r\n const parsed = JSON.parse(packageJsonText) as Record<string, unknown>;\r\n const deps = {\r\n ...((parsed.dependencies as Record<string, string>) ?? {}),\r\n ...((parsed.devDependencies as Record<string, string>) ?? {}),\r\n };\r\n return Object.entries(deps).map(([name, version]) => ({\r\n name,\r\n version: String(version).replace(/^[\\^~>=<]+/, ''), // strip semver range prefixes\r\n ecosystem: 'npm' as const,\r\n }));\r\n } catch {\r\n return [];\r\n }\r\n}\r\n","// ============================================================\r\n// Vigile CLI — Vibe App Scanner (orchestrator)\r\n// The top-level BaaS scan orchestrator. Given a URL, detects\r\n// which BaaS platform is in use, then routes to the appropriate\r\n// sub-scanner, bundle-analyzer, and CVE detector.\r\n// ============================================================\r\n//\r\n// Scan flow:\r\n// 1. Bundle analysis — download JS bundles, scan for secrets\r\n// 2. Platform detect — identify Supabase or Firebase signals\r\n// 3. Platform scan — run supabase-scanner or firebase-scanner\r\n// 4. Package detection — try fetching package.json for CVE scan\r\n// 5. CVE detection — check detected packages against OSV.dev\r\n// 6. Aggregate — merge and deduplicate all findings\r\n// ============================================================\r\n\r\nimport { analyzeBundles } from './bundle-analyzer.js';\r\nimport { scanSupabase } from './supabase-scanner.js';\r\nimport { scanFirebase } from './firebase-scanner.js';\r\nimport { detectCves, parseNpmPackages } from './cve-detector.js';\r\nimport type { DetectedPackage } from './cve-detector.js';\r\nimport type { Finding } from '../../types/index.js';\r\n\r\nconst FETCH_TIMEOUT_MS = 10_000;\r\n\r\nexport type BaaSPlatform = 'supabase' | 'firebase' | 'unknown';\r\n\r\nexport interface VibeAppScanOptions {\r\n /** The deployed app URL (e.g. https://myapp.vercel.app) */\r\n appUrl: string;\r\n /** Override BaaS platform detection */\r\n platform?: BaaSPlatform;\r\n /** Extra Supabase project URL if different from appUrl */\r\n supabaseUrl?: string;\r\n /** Extra Firebase project URL if different from appUrl */\r\n firebaseUrl?: string;\r\n}\r\n\r\nexport interface VibeAppScanResult {\r\n appUrl: string;\r\n detectedPlatform: BaaSPlatform;\r\n findings: Finding[];\r\n bundlesAnalyzed: number;\r\n packagesChecked: number;\r\n cveMatches: number;\r\n errors: string[];\r\n}\r\n\r\n// ── Fetch with timeout ──────────────────────────────────────\r\n\r\nasync function fetchWithTimeout(\r\n url: string,\r\n init?: RequestInit,\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\r\n try {\r\n return await fetch(url, { ...init, signal: controller.signal });\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n}\r\n\r\n// ── Platform detection ──────────────────────────────────────\r\n\r\n/**\r\n * Detects which BaaS platform is used by examining:\r\n * 1. Secret finding evidence from bundle analysis\r\n * 2. URL patterns (*.supabase.co, *.web.app, *.firebaseapp.com)\r\n * 3. Bundle content signals (import paths, SDK references)\r\n */\r\nfunction detectPlatform(findings: Finding[], appUrl: string): BaaSPlatform {\r\n // Signal 1: Evidence from secret pattern matches\r\n for (const f of findings) {\r\n const ev = (f.evidence ?? '').toLowerCase();\r\n const title = f.title.toLowerCase();\r\n\r\n // Supabase signals — anon key JWTs, supabase SDK references\r\n if (\r\n ev.includes('supabase') ||\r\n title.includes('supabase') ||\r\n ev.includes('sb-') ||\r\n ev.includes('.supabase.co')\r\n ) {\r\n return 'supabase';\r\n }\r\n\r\n // Firebase signals — API keys, firebase SDK references\r\n if (\r\n ev.includes('firebase') ||\r\n title.includes('firebase') ||\r\n ev.includes('aizasy') || // Firebase API key prefix (lowercased)\r\n ev.includes('.firebaseapp.com') ||\r\n ev.includes('.firebaseio.com')\r\n ) {\r\n return 'firebase';\r\n }\r\n }\r\n\r\n // Signal 2: URL patterns\r\n try {\r\n const hostname = new URL(appUrl).hostname;\r\n if (hostname.endsWith('.supabase.co')) return 'supabase';\r\n if (hostname.endsWith('.web.app') || hostname.endsWith('.firebaseapp.com')) {\r\n return 'firebase';\r\n }\r\n } catch {\r\n // Invalid URL — fall through\r\n }\r\n\r\n return 'unknown';\r\n}\r\n\r\n// ── Package.json detection ──────────────────────────────────\r\n\r\n/**\r\n * Attempts to fetch package.json from common paths on the deployed app.\r\n * Many frameworks (Next.js on Vercel, CRA) don't serve package.json,\r\n * but some setups expose it. This is a best-effort heuristic.\r\n */\r\nasync function tryFetchPackageJson(appUrl: string): Promise<DetectedPackage[]> {\r\n const baseUrl = appUrl.endsWith('/') ? appUrl : `${appUrl}/`;\r\n\r\n // Common paths where package.json might be accessible\r\n const paths = [\r\n 'package.json',\r\n 'assets/package.json',\r\n ];\r\n\r\n for (const path of paths) {\r\n try {\r\n const res = await fetchWithTimeout(`${baseUrl}${path}`);\r\n if (res.ok) {\r\n const text = await res.text();\r\n // Validate it looks like a real package.json (not an HTML 404 page)\r\n if (text.trimStart().startsWith('{') && text.includes('\"dependencies\"')) {\r\n return parseNpmPackages(text);\r\n }\r\n }\r\n } catch {\r\n // Path not available — try next\r\n continue;\r\n }\r\n }\r\n\r\n return [];\r\n}\r\n\r\n/**\r\n * Extract npm package references from bundle content.\r\n * Bundles often contain package version comments or source map references\r\n * like \"node_modules/react/index.js\" that reveal dependency names.\r\n * We can't get exact versions from minified bundles, but we can detect\r\n * which packages are in use for informational purposes.\r\n */\r\nfunction extractPackagesFromBundleFindings(findings: Finding[]): string[] {\r\n const packageNames = new Set<string>();\r\n\r\n for (const f of findings) {\r\n const ev = f.evidence ?? '';\r\n // Look for node_modules references in evidence\r\n const nodeModuleMatches = ev.matchAll(/node_modules\\/(@[^/]+\\/[^/]+|[^/]+)/g);\r\n for (const m of nodeModuleMatches) {\r\n if (m[1]) packageNames.add(m[1]);\r\n }\r\n }\r\n\r\n return Array.from(packageNames);\r\n}\r\n\r\n// ── Finding deduplication ───────────────────────────────────\r\n\r\n/**\r\n * Deduplicates findings by their id + evidence combination.\r\n * The bundle analyzer and platform scanners may both find the\r\n * same secret, so we remove exact duplicates.\r\n */\r\nfunction deduplicateFindings(findings: Finding[]): Finding[] {\r\n const seen = new Set<string>();\r\n const unique: Finding[] = [];\r\n\r\n for (const f of findings) {\r\n const key = `${f.id}::${f.evidence ?? ''}`;\r\n if (seen.has(key)) continue;\r\n seen.add(key);\r\n unique.push(f);\r\n }\r\n\r\n return unique;\r\n}\r\n\r\n// ── Main export ─────────────────────────────────────────────\r\n\r\n/**\r\n * Orchestrates a full BaaS security scan for a deployed web app.\r\n *\r\n * @param opts App URL and optional platform overrides\r\n */\r\nexport async function scanVibeApp(opts: VibeAppScanOptions): Promise<VibeAppScanResult> {\r\n const allFindings: Finding[] = [];\r\n const errors: string[] = [];\r\n let packagesChecked = 0;\r\n let cveMatches = 0;\r\n\r\n // ── Step 1: Bundle analysis (always runs) ─────────────────\r\n const bundleResult = await analyzeBundles(opts.appUrl);\r\n allFindings.push(...bundleResult.findings);\r\n errors.push(...bundleResult.errors);\r\n\r\n // ── Step 2: Platform detection ────────────────────────────\r\n const detectedPlatform: BaaSPlatform =\r\n opts.platform ?? detectPlatform(bundleResult.findings, opts.appUrl);\r\n\r\n // ── Step 3: Platform-specific scan ────────────────────────\r\n if (detectedPlatform === 'supabase' || opts.supabaseUrl) {\r\n const supabaseUrl = opts.supabaseUrl ?? opts.appUrl;\r\n try {\r\n const supabaseResult = await scanSupabase({ projectUrl: supabaseUrl });\r\n // Only add non-duplicate findings (bundle analyzer already ran inside scanSupabase)\r\n allFindings.push(...supabaseResult.findings);\r\n errors.push(...supabaseResult.errors);\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`Supabase scan failed: ${msg}`);\r\n }\r\n }\r\n\r\n if (detectedPlatform === 'firebase' || opts.firebaseUrl) {\r\n const firebaseUrl = opts.firebaseUrl ?? opts.appUrl;\r\n try {\r\n const firebaseResult = await scanFirebase({ projectUrl: firebaseUrl });\r\n allFindings.push(...firebaseResult.findings);\r\n errors.push(...firebaseResult.errors);\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`Firebase scan failed: ${msg}`);\r\n }\r\n }\r\n\r\n if (detectedPlatform === 'unknown' && !opts.supabaseUrl && !opts.firebaseUrl) {\r\n errors.push(\r\n 'Could not detect BaaS platform (Supabase or Firebase) from bundle analysis or URL. ' +\r\n 'Provide --supabase <url> or --firebase <url> explicitly for deeper platform scanning.',\r\n );\r\n }\r\n\r\n // ── Step 4: Package detection for CVE scan ────────────────\r\n let packages: DetectedPackage[] = [];\r\n\r\n // Try fetching package.json from the deployed app\r\n try {\r\n packages = await tryFetchPackageJson(opts.appUrl);\r\n } catch {\r\n // Non-fatal — package.json fetch is best-effort\r\n }\r\n\r\n // If no package.json found, note the bundle-detected packages\r\n if (packages.length === 0) {\r\n const bundlePackageNames = extractPackagesFromBundleFindings(bundleResult.findings);\r\n if (bundlePackageNames.length > 0) {\r\n errors.push(\r\n `Detected ${bundlePackageNames.length} package(s) in bundles but could not ` +\r\n `determine versions for CVE lookup: ${bundlePackageNames.slice(0, 5).join(', ')}` +\r\n (bundlePackageNames.length > 5 ? '...' : ''),\r\n );\r\n }\r\n }\r\n\r\n // ── Step 5: CVE detection ─────────────────────────────────\r\n if (packages.length > 0) {\r\n try {\r\n const cveResult = await detectCves(packages);\r\n allFindings.push(...cveResult.findings);\r\n errors.push(...cveResult.errors);\r\n packagesChecked = cveResult.packagesChecked;\r\n cveMatches = cveResult.matches.length;\r\n } catch (err: unknown) {\r\n const msg = err instanceof Error ? err.message : String(err);\r\n errors.push(`CVE detection failed: ${msg}`);\r\n }\r\n }\r\n\r\n // ── Step 6: Deduplicate and return ────────────────────────\r\n const findings = deduplicateFindings(allFindings);\r\n\r\n return {\r\n appUrl: opts.appUrl,\r\n detectedPlatform,\r\n findings,\r\n bundlesAnalyzed: bundleResult.bundlesAnalyzed,\r\n packagesChecked,\r\n cveMatches,\r\n errors,\r\n };\r\n}\r\n"],"mappings":"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","names":["exports","module","import_chalk","import_promises","import_path","import_path","import_path","import_fs","import_path","import_path","import_path","import_promises","import_fs","import_promises","import_fs","import_path","_","SEVERITY_DEDUCTIONS","deduplicateFindings","dnsPromises","chalk","import_promises","import_path","import_os","FETCH_TIMEOUT_MS","fetchWithTimeout","FETCH_TIMEOUT_MS","fetchWithTimeout","FETCH_TIMEOUT_MS","fetchWithTimeout","FETCH_TIMEOUT_MS","fetchWithTimeout","deduplicateFindings","chalk","ora"]}
|