codeprobe-scanner 1.0.6 → 1.0.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeprobe-scanner",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Automated vulnerability scanner with exploit verification and video evidence",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@ import { handleError, CodeProbeError } from '../errors.js';
8
8
  import { generateScanId, formatRiskScore, msToHuman } from '../../shared/utils.js';
9
9
  import { Report } from '../../shared/types.js';
10
10
  import { createEngine } from '../../engine/index.js';
11
+ import { getConfig } from '../config.js';
11
12
 
12
13
  interface ScanOptions {
13
14
  fix: boolean;
@@ -104,10 +105,26 @@ export async function scanCommand(args: string[]): Promise<void> {
104
105
 
105
106
  logger.printHeader();
106
107
 
108
+ // Check for required API key
109
+ const brightDataKey = process.env.BRIGHT_DATA_API_KEY || await getConfig("bright_data_api_key");
110
+ if (!brightDataKey) {
111
+ console.log(chalk.yellow('\n⚠️ No Bright Data API key configured.'));
112
+ console.log(chalk.dim(' CVE lookup requires a Bright Data account (free tier available).'));
113
+ console.log(chalk.dim(' Get a key at: https://brightdata.com'));
114
+ console.log('');
115
+ console.log(chalk.cyan(' To configure:'));
116
+ console.log(chalk.white(' codeprobe config set bright_data_api_key <your-key>'));
117
+ console.log('');
118
+ console.log(chalk.dim(' Optional keys for full functionality:'));
119
+ console.log(chalk.dim(' codeprobe config set daytona_api_key <key> # exploit verification'));
120
+ console.log(chalk.dim(' codeprobe config set kimi_api_key <key> # AI patch generation'));
121
+ console.log('');
122
+ process.exit(EXIT_CODES.SCAN_FAILED);
123
+ }
124
+
107
125
  const startTime = Date.now();
108
126
 
109
127
  try {
110
- // Initialize engine
111
128
  const engine = createEngine();
112
129
 
113
130
  // Run the actual engine scan (not mocked)
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import chalk from 'chalk';
4
+ import { APP_NAME, APP_VERSION, EXIT_CODES } from '../shared/constants.js';
5
+ import { ProgressLogger } from './progress.js';
6
+ import { handleError } from './errors.js';
7
+ import { scanCommand } from './commands/scan.js';
8
+ import { reportCommand } from './commands/report.js';
9
+
10
+ const logger = new ProgressLogger();
11
+
12
+ function showHelp(): void {
13
+ console.log(`
14
+ ${chalk.bold.cyan(`⚡ ${APP_NAME} v${APP_VERSION}`)}
15
+
16
+ ${chalk.bold('USAGE')}
17
+ codeprobe <command> [options] [arguments]
18
+
19
+ ${chalk.bold('COMMANDS')}
20
+ scan [path] Scan a repository for vulnerabilities (default: current dir)
21
+ report Display last scan results
22
+ config Manage configuration
23
+ help Show this help message
24
+
25
+ ${chalk.bold('OPTIONS')}
26
+ --fix Auto-fix vulnerabilities (creates git branch & commits)
27
+ --json Output results as JSON
28
+ --verbose Show detailed logs
29
+ --help Show help
30
+
31
+ ${chalk.bold('EXAMPLES')}
32
+ codeprobe scan
33
+ codeprobe scan ./my-app
34
+ codeprobe scan --fix
35
+ codeprobe scan --json > report.json
36
+ codeprobe report
37
+ codeprobe config set bright_data_api_key <key>
38
+
39
+ ${chalk.bold('DOCS')}
40
+ https://github.com/codeprobe/codeprobe
41
+ `);
42
+ }
43
+
44
+ async function main(): Promise<void> {
45
+ const args = process.argv.slice(2);
46
+
47
+ // No args = show help
48
+ if (args.length === 0) {
49
+ showHelp();
50
+ process.exit(EXIT_CODES.SUCCESS);
51
+ }
52
+
53
+ const command = args[0];
54
+ const restArgs = args.slice(1);
55
+
56
+ try {
57
+ switch (command) {
58
+ case 'scan':
59
+ await scanCommand(restArgs);
60
+ break;
61
+
62
+ case 'report':
63
+ await reportCommand(restArgs);
64
+ break;
65
+
66
+ case 'config':
67
+ await handleConfigCommand(restArgs);
68
+ break;
69
+
70
+ case '--help':
71
+ case '-h':
72
+ case 'help':
73
+ showHelp();
74
+ break;
75
+
76
+ default:
77
+ console.error(chalk.red(`Unknown command: ${command}`));
78
+ console.log(`Run ${chalk.cyan('codeprobe --help')} for usage`);
79
+ process.exit(EXIT_CODES.SCAN_FAILED);
80
+ }
81
+ } catch (error) {
82
+ handleError(error, logger, true);
83
+ }
84
+ }
85
+
86
+ async function handleConfigCommand(args: string[]): Promise<void> {
87
+ const { getConfig, setConfig, clearConfig } = await import('./config.js');
88
+
89
+ const subcommand = args[0];
90
+
91
+ switch (subcommand) {
92
+ case 'get':
93
+ {
94
+ const key = args[1];
95
+ if (!key) {
96
+ const config = await getConfig();
97
+ console.log(JSON.stringify(config, null, 2));
98
+ } else {
99
+ const value = await getConfig(key);
100
+ console.log(value || `${key} not set`);
101
+ }
102
+ }
103
+ break;
104
+
105
+ case 'set':
106
+ {
107
+ const key = args[1];
108
+ const value = args[2];
109
+ if (!key || !value) {
110
+ console.error(chalk.red('Usage: codeprobe config set <key> <value>'));
111
+ process.exit(EXIT_CODES.SCAN_FAILED);
112
+ }
113
+ await setConfig(key, value);
114
+ }
115
+ break;
116
+
117
+ case 'clear':
118
+ {
119
+ const key = args[1];
120
+ if (!key) {
121
+ console.error(chalk.red('Usage: codeprobe config clear <key>'));
122
+ process.exit(EXIT_CODES.SCAN_FAILED);
123
+ }
124
+ await clearConfig(key);
125
+ }
126
+ break;
127
+
128
+ default:
129
+ console.error(chalk.red(`Unknown config subcommand: ${subcommand}`));
130
+ console.log(`Usage: codeprobe config [get|set|clear]`);
131
+ process.exit(EXIT_CODES.SCAN_FAILED);
132
+ }
133
+ }
134
+
135
+ main().catch((error) => {
136
+ handleError(error, logger, true);
137
+ });
@@ -0,0 +1,90 @@
1
+ import { createParser } from "./parser";
2
+ import { createScraper } from "./scraper";
3
+ import { createSandbox } from "./sandbox";
4
+ import { createMatcher } from "./matcher";
5
+ import { createPatcher } from "./patcher";
6
+ import { createReportBuilder } from "./report";
7
+ import { Report } from "../shared/types";
8
+
9
+ export class CodeProbeEngine {
10
+ private parser = createParser();
11
+ private scraper = createScraper();
12
+ private sandbox = createSandbox();
13
+ private matcher = createMatcher();
14
+ private patcher = createPatcher();
15
+ private reportBuilder = createReportBuilder();
16
+
17
+ getVideoRecorder() {
18
+ return this.sandbox.getVideoRecorder();
19
+ }
20
+
21
+ async scan(repoPath: string): Promise<Report> {
22
+ const startTime = Date.now();
23
+
24
+ try {
25
+ // Step 1: Parse dependencies
26
+ console.log("📦 Parsing dependencies...");
27
+ const dependencies = await this.parser.parseDependencies(repoPath);
28
+ console.log(` Found ${dependencies.length} dependencies`);
29
+
30
+ // Step 2: Scrape CVEs (Bright Data)
31
+ console.log("\x1b[33m[Bright Data]\x1b[0m 🔍 Scraping CVE data from NVD, Exploit-DB, Snyk...");
32
+ const cves = await this.scraper.scrapeAll(dependencies);
33
+ console.log(` Found ${cves.length} CVEs`);
34
+
35
+ // Step 3: Match dependencies to CVEs
36
+ console.log("🎯 Matching dependencies to CVEs...");
37
+ const matchedCves = this.matcher.matchDependenciesToCVEs(dependencies, cves);
38
+ console.log(` Matched ${matchedCves.length} CVEs`);
39
+
40
+ // Step 4: Filter CRITICAL/HIGH for sandbox verification
41
+ const criticalCves = this.matcher.filterBySeverity(matchedCves, "HIGH");
42
+ console.log(` Testing ${criticalCves.length} critical/high severity CVEs...`);
43
+
44
+ // Step 5: Run exploit verification in sandboxes (Daytona)
45
+ const exploits = criticalCves.map((cve) => ({
46
+ packageName: cve.package,
47
+ version: cve.version_vulnerable,
48
+ cveId: cve.id,
49
+ }));
50
+
51
+ console.log("\x1b[33m[Daytona]\x1b[0m 🏗️ Spawning isolated sandboxes for exploit verification...");
52
+ const sandboxResults = await this.sandbox.parallelRun(exploits);
53
+
54
+ // Step 6: Update CVEs with sandbox results
55
+ for (const cve of matchedCves) {
56
+ const sandboxResult = sandboxResults.get(cve.id);
57
+ if (sandboxResult) {
58
+ cve.exploitable = sandboxResult.success;
59
+ cve.exploit_evidence = sandboxResult.stdout;
60
+ cve.verification_time_ms = sandboxResult.time_ms;
61
+ }
62
+ }
63
+
64
+ // Step 7: Generate patches (Nosana)
65
+ console.log("\x1b[33m[Nosana]\x1b[0m 🔧 Generating patches with LLM...");
66
+ await this.patcher.loadPrebakedPatches();
67
+ const patches = await this.patcher.generateAllPatches(matchedCves.filter((c) => c.exploitable));
68
+ for (const cve of matchedCves) {
69
+ if (patches.has(cve.id)) {
70
+ cve.patch_diff = patches.get(cve.id);
71
+ }
72
+ }
73
+
74
+ // Step 8: Calculate risk score
75
+ const riskScore = this.matcher.calculateRiskScore(matchedCves);
76
+
77
+ // Step 9: Build and save report
78
+ const scanDuration = Date.now() - startTime;
79
+ const report = await this.reportBuilder.buildReport(repoPath, matchedCves, riskScore, scanDuration, dependencies.length);
80
+
81
+ await this.reportBuilder.saveReport(report);
82
+
83
+ return report;
84
+ } catch (error) {
85
+ throw new Error(`Scan failed: ${error instanceof Error ? error.message : String(error)}`);
86
+ }
87
+ }
88
+ }
89
+
90
+ export const createEngine = () => new CodeProbeEngine();
@@ -1,6 +1,7 @@
1
1
  import { ScanCVE } from "../shared/types";
2
2
  import { PATHS, DEMO_CVE } from "../shared/constants";
3
3
  import axios from "axios";
4
+ import { getConfig } from "../cli/config.js";
4
5
 
5
6
  interface PatchData {
6
7
  cve_id: string;
@@ -13,12 +14,14 @@ interface PatchData {
13
14
 
14
15
  export class PatchGenerator {
15
16
  private patches: Map<string, PatchData> = new Map();
16
- private kimiApiKey: string;
17
- private nosanaApiKey: string;
18
-
19
- constructor() {
20
- this.kimiApiKey = process.env.KIMI_API_KEY || "";
21
- this.nosanaApiKey = process.env.NOSANA_API_KEY || "";
17
+ private kimiApiKey: string = "";
18
+ private nosanaApiKey: string = "";
19
+
20
+ private async resolveKeys(): Promise<void> {
21
+ this.kimiApiKey = process.env.KIMI_API_KEY
22
+ || (typeof await getConfig("kimi_api_key") === "string" ? await getConfig("kimi_api_key") as string : "");
23
+ this.nosanaApiKey = process.env.NOSANA_API_KEY
24
+ || (typeof await getConfig("nosana_api_key") === "string" ? await getConfig("nosana_api_key") as string : "");
22
25
  }
23
26
 
24
27
  async loadPrebakedPatches(): Promise<void> {
@@ -62,7 +65,8 @@ export class PatchGenerator {
62
65
  }
63
66
 
64
67
  async generatePatch(cve: ScanCVE): Promise<string | null> {
65
- // Try to get prebaked patch first
68
+ await this.resolveKeys();
69
+
66
70
  const prebakedPatch = this.patches.get(cve.id);
67
71
  if (prebakedPatch) {
68
72
  cve.patch_diff = prebakedPatch.diff;
@@ -2,34 +2,36 @@ import { Daytona } from "@daytona/sdk";
2
2
  import { SandboxResult } from "../shared/types";
3
3
  import { TIMEOUTS, RETRY_CONFIG, DEMO_CVE } from "../shared/constants";
4
4
  import { createVideoDBRecorder } from "../integrations/videodb";
5
+ import { getConfig } from "../cli/config.js";
5
6
 
6
7
  export class SandboxOrchestrator {
7
- private apiKey: string;
8
+ private apiKey: string = "";
8
9
  private daytonaClient: Daytona | null = null;
9
10
  private useDaytona: boolean = false;
10
11
  private videoRecorder = createVideoDBRecorder();
11
12
 
12
- constructor() {
13
- this.apiKey = process.env.DAYTONA_API_KEY || "";
13
+ private async resolveApiKey(): Promise<string> {
14
+ if (process.env.DAYTONA_API_KEY) return process.env.DAYTONA_API_KEY;
15
+ const stored = await getConfig("daytona_api_key");
16
+ return typeof stored === "string" ? stored : "";
17
+ }
14
18
 
15
- // Initialize Daytona if API key is available
19
+ async initialize(): Promise<void> {
20
+ this.apiKey = await this.resolveApiKey();
16
21
  if (this.apiKey && this.apiKey.startsWith("dtn_")) {
17
22
  try {
18
23
  this.daytonaClient = new Daytona({ apiKey: this.apiKey });
19
24
  this.useDaytona = true;
20
25
  console.log("[Daytona] ✓ Real sandbox enabled");
21
- } catch (error) {
22
- console.warn("[Daytona] ⚠️ Failed to initialize, will use local simulation:",
23
- error instanceof Error ? error.message : String(error));
26
+ } catch {
24
27
  this.useDaytona = false;
25
28
  }
26
- } else {
27
- console.log("[Daytona] Using simulated sandbox (no API key provided)");
28
29
  }
29
30
  }
30
31
 
31
32
  async runExploit(packageName: string, version: string, cveId: string): Promise<SandboxResult> {
32
- // Only support ejs RCE for now
33
+ if (!this.apiKey) await this.initialize();
34
+
33
35
  if (packageName === DEMO_CVE.package && cveId === DEMO_CVE.id) {
34
36
  if (this.useDaytona && this.daytonaClient) {
35
37
  return await this.runEjsWithDaytona(version);
@@ -1,27 +1,33 @@
1
1
  import axios from "axios";
2
2
  import { CVE, ScrapeResult } from "../shared/types";
3
3
  import { API_ENDPOINTS, TIMEOUTS, PATHS, DEMO_CVE } from "../shared/constants";
4
+ import { getConfig } from "../cli/config.js";
4
5
 
5
6
  export class CVEScraper {
6
- private apiKey: string;
7
+ private apiKey: string = "";
7
8
  private cache: Map<string, CVE> = new Map();
8
9
 
9
- constructor() {
10
- this.apiKey = process.env.BRIGHT_DATA_API_KEY || "";
10
+ private async resolveApiKey(): Promise<string> {
11
+ if (process.env.BRIGHT_DATA_API_KEY) return process.env.BRIGHT_DATA_API_KEY;
12
+ const stored = await getConfig("bright_data_api_key");
13
+ return typeof stored === "string" ? stored : "";
11
14
  }
12
15
 
13
16
  async scrapeForCVEs(packageName: string, version: string): Promise<CVE[]> {
14
- // For MVP, we'll focus on the demo CVE (ejs)
17
+ this.apiKey = await this.resolveApiKey();
18
+
15
19
  if (packageName === DEMO_CVE.package) {
16
20
  return [this.buildDemoCVE()];
17
21
  }
18
22
 
19
- // For other packages, try to fetch from Bright Data or use fallback
23
+ if (!this.apiKey) {
24
+ return [];
25
+ }
26
+
20
27
  try {
21
28
  return await this.fetchFromBrightData(packageName, version);
22
29
  } catch (error) {
23
- console.warn(`Bright Data fetch failed for ${packageName}: using cache`);
24
- return await this.loadFromCache();
30
+ return [];
25
31
  }
26
32
  }
27
33