codeprobe-scanner 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.claude/settings.local.json +19 -0
  2. package/.dockerignore +17 -0
  3. package/.env.development +8 -0
  4. package/.env.example +20 -0
  5. package/.env.setup +214 -0
  6. package/.github/workflows/codeprobe-scan.yml +137 -0
  7. package/.github/workflows/codeprobe.yml +84 -0
  8. package/.github/workflows/scan-schedule.yml +28 -0
  9. package/ANALYSIS_SUMMARY.md +365 -0
  10. package/API_INTEGRATIONS.md +469 -0
  11. package/BUILD_PLAYBOOK.md +349 -0
  12. package/CLAUDE.md +106 -0
  13. package/DEPLOY.md +452 -0
  14. package/DEPLOYMENT_STATUS.md +240 -0
  15. package/DEPLOY_CHECKLIST.md +316 -0
  16. package/Dockerfile +24 -0
  17. package/EXECUTION_PLAN.html +1086 -0
  18. package/IMPLEMENTATION_COMPLETE.md +288 -0
  19. package/IMPLEMENTATION_SUMMARY.md +443 -0
  20. package/INTERACTIVE_FIX_FLOW.md +308 -0
  21. package/MIGRATION_COMPLETE.md +327 -0
  22. package/ORCHESTRATOR_SYNTHESIS.json +80 -0
  23. package/PENDING_WORK.md +308 -0
  24. package/PREFLIGHT_PLAN.md +182 -0
  25. package/QUICKSTART.md +305 -0
  26. package/README.md +15 -0
  27. package/STAGE_1_SETUP_ENGINE.md +245 -0
  28. package/STAGE_2_ARCHITECTURE.md +714 -0
  29. package/STAGE_2_CLI_VERIFICATION.md +269 -0
  30. package/STAGE_2_COMPLETE.md +332 -0
  31. package/STAGE_2_IMPLEMENTATION_PLAN.md +679 -0
  32. package/STAGE_3_COMPLETE.md +246 -0
  33. package/STAGE_3_DASHBOARD_POLISH.md +371 -0
  34. package/STAGE_3_SETUP.md +155 -0
  35. package/VIDEODB_INTEGRATION.md +237 -0
  36. package/archived/DASHBOARD_UI_WALKTHROUGH.md +392 -0
  37. package/archived/FRONTEND_SETUP.md +236 -0
  38. package/archived/auth.ts +40 -0
  39. package/archived/dashboard/components/BusinessImpactCard.tsx +48 -0
  40. package/archived/dashboard/components/CVETable.tsx +104 -0
  41. package/archived/dashboard/components/ErrorBoundary.tsx +48 -0
  42. package/archived/dashboard/components/PatchDiffViewer.tsx +43 -0
  43. package/archived/dashboard/components/RiskGauge.tsx +64 -0
  44. package/archived/dashboard/frontend.tsx +104 -0
  45. package/archived/dashboard/hooks/useAuth.ts +32 -0
  46. package/archived/dashboard/hooks/useScan.ts +65 -0
  47. package/archived/dashboard/index.html +15 -0
  48. package/archived/dashboard/pages/LoginPage.tsx +28 -0
  49. package/archived/dashboard/pages/ScanDetailPage.tsx +143 -0
  50. package/archived/dashboard/pages/ScansListPage.tsx +160 -0
  51. package/bin/install-and-run.sh +91 -0
  52. package/bun.lock +603 -0
  53. package/codeprobe-prd.md +674 -0
  54. package/cve-cache.json +25 -0
  55. package/demo-vulnerable-app/.github/workflows/codeprobe.yml +32 -0
  56. package/demo-vulnerable-app/README.md +70 -0
  57. package/demo-vulnerable-app/package-lock.json +27 -0
  58. package/demo-vulnerable-app/package.json +15 -0
  59. package/demo-vulnerable-app/server.js +34 -0
  60. package/demo.sh +45 -0
  61. package/index.ts +19 -0
  62. package/package.json +28 -0
  63. package/patches.json +12 -0
  64. package/serve-dashboard.ts +23 -0
  65. package/src/api/server-cli.ts +270 -0
  66. package/src/api/server.ts +293 -0
  67. package/src/bot/server.ts +113 -0
  68. package/src/cli/commands/report.ts +92 -0
  69. package/src/cli/commands/scan-with-fix.ts +123 -0
  70. package/src/cli/commands/scan.ts +137 -0
  71. package/src/cli/config.ts +188 -0
  72. package/src/cli/errors.ts +120 -0
  73. package/src/cli/index.ts +137 -0
  74. package/src/cli/progress.ts +119 -0
  75. package/src/cli-server.ts +523 -0
  76. package/src/engine/index.ts +90 -0
  77. package/src/engine/matcher.ts +115 -0
  78. package/src/engine/parser.ts +91 -0
  79. package/src/engine/patcher.ts +280 -0
  80. package/src/engine/report.ts +137 -0
  81. package/src/engine/sandbox.ts +222 -0
  82. package/src/engine/scraper.ts +122 -0
  83. package/src/integrations/videodb.ts +153 -0
  84. package/src/mcp/server.ts +149 -0
  85. package/src/scraper-cron.ts +103 -0
  86. package/src/shared/constants.ts +88 -0
  87. package/src/shared/types.ts +123 -0
  88. package/src/shared/utils.ts +80 -0
  89. package/src/test/cli.test.ts +211 -0
  90. package/src/test/dashboard.test.ts +38 -0
  91. package/src/test/demo-scan.json +32 -0
  92. package/src/test/engine.test.ts +157 -0
  93. package/tailwind.config.js +11 -0
  94. package/tsconfig.json +30 -0
  95. package/verify-dashboard.ts +87 -0
  96. package/verify-env.sh +98 -0
@@ -0,0 +1,523 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import chalk from 'chalk';
4
+ import { RepositoryParser } from './engine/parser.js';
5
+ import { EXIT_CODES, APP_NAME, APP_VERSION } from './shared/constants.js';
6
+
7
+ interface ScanPayload {
8
+ dependencies: Array<{ name: string; version: string }>;
9
+ repoPath: string;
10
+ }
11
+
12
+ interface ScanResponse {
13
+ success?: boolean;
14
+ data?: {
15
+ scan: {
16
+ id: string;
17
+ timestamp: string;
18
+ repo_url: string;
19
+ repo_path: string;
20
+ cves: Array<{
21
+ id: string;
22
+ package: string;
23
+ version_vulnerable: string;
24
+ version_fixed?: string;
25
+ severity: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
26
+ cvss: number;
27
+ description: string;
28
+ exploitable: boolean;
29
+ exploit_evidence?: string;
30
+ patch_diff?: string;
31
+ patch_version?: string;
32
+ verification_time_ms?: number;
33
+ }>;
34
+ risk_score: number;
35
+ exploitable_count: number;
36
+ theoretical_count: number;
37
+ total_dependencies: number;
38
+ patches_available?: number;
39
+ };
40
+ summary: {
41
+ total_cves: number;
42
+ exploitable_count: number;
43
+ theoretical_count: number;
44
+ scan_duration_ms: number;
45
+ };
46
+ };
47
+ // Legacy format support
48
+ scan?: any;
49
+ summary?: any;
50
+ }
51
+
52
+ interface CLIOptions {
53
+ json: boolean;
54
+ token: string;
55
+ fix: boolean;
56
+ verbose: boolean;
57
+ }
58
+
59
+ async function parseScanArgs(args: string[]): Promise<{ path: string; options: CLIOptions }> {
60
+ const options: CLIOptions = {
61
+ json: false,
62
+ token: '',
63
+ fix: false,
64
+ verbose: false,
65
+ };
66
+
67
+ let path = process.cwd();
68
+
69
+ for (let i = 0; i < args.length; i++) {
70
+ const arg = args[i];
71
+
72
+ if (arg === '--json') {
73
+ options.json = true;
74
+ } else if (arg === '--token') {
75
+ options.token = args[i + 1] || '';
76
+ i++;
77
+ } else if (arg === '--fix') {
78
+ options.fix = true;
79
+ } else if (arg === '--verbose' || arg === '-v') {
80
+ options.verbose = true;
81
+ } else if (!arg.startsWith('--')) {
82
+ path = arg;
83
+ }
84
+ }
85
+
86
+ return { path, options };
87
+ }
88
+
89
+ function colorSeverity(severity: string): string {
90
+ switch (severity) {
91
+ case 'CRITICAL':
92
+ return chalk.red(severity);
93
+ case 'HIGH':
94
+ return chalk.yellow(severity);
95
+ case 'MEDIUM':
96
+ return chalk.blue(severity);
97
+ case 'LOW':
98
+ return chalk.green(severity);
99
+ default:
100
+ return severity;
101
+ }
102
+ }
103
+
104
+ function displayReport(response: ScanResponse, options: CLIOptions): void {
105
+ if (options.json) {
106
+ console.log(JSON.stringify(response, null, 2));
107
+ return;
108
+ }
109
+
110
+ const { scan, summary } = response;
111
+
112
+ console.log('\n' + chalk.bold.cyan(`${APP_NAME} Vulnerability Report`));
113
+ console.log(chalk.gray('═'.repeat(60)));
114
+
115
+ // Summary section
116
+ console.log(chalk.bold('\nSummary:'));
117
+ console.log(` ${chalk.cyan('Total CVEs:')} ${summary.total_cves}`);
118
+ console.log(
119
+ ` ${chalk.cyan('Exploitable:')} ${chalk.red(String(summary.exploitable_count))}`
120
+ );
121
+ console.log(
122
+ ` ${chalk.cyan('Theoretical:')} ${chalk.yellow(String(summary.theoretical_count))}`
123
+ );
124
+ console.log(` ${chalk.cyan('Scan Duration:')} ${summary.scan_duration_ms}ms`);
125
+ console.log(` ${chalk.cyan('Risk Score:')} ${getRiskColor(scan.risk_score)(String(scan.risk_score.toFixed(1)))}/10`);
126
+
127
+ // CVEs section
128
+ if (scan.cves.length > 0) {
129
+ console.log(chalk.bold('\nVulnerabilities:'));
130
+ console.log(chalk.gray('─'.repeat(60)));
131
+
132
+ for (const cve of scan.cves) {
133
+ console.log(`\n ${chalk.bold(cve.id)} ${colorSeverity(cve.severity)}`);
134
+ console.log(` Package: ${chalk.cyan(cve.package)}@${cve.version_vulnerable}`);
135
+ console.log(` CVSS: ${cve.cvss}/10`);
136
+ console.log(` Status: ${cve.exploitable ? chalk.red('EXPLOITABLE') : chalk.green('Not exploitable')}`);
137
+
138
+ if (cve.version_fixed) {
139
+ console.log(` Fixed in: ${chalk.green(cve.version_fixed)}`);
140
+ }
141
+
142
+ if (cve.description) {
143
+ console.log(` Description: ${cve.description.substring(0, 80)}${cve.description.length > 80 ? '...' : ''}`);
144
+ }
145
+ }
146
+ console.log(chalk.gray('─'.repeat(60)));
147
+ } else {
148
+ console.log(chalk.green('\n✓ No vulnerabilities detected'));
149
+ }
150
+
151
+ // Sponsor branding
152
+ console.log(
153
+ chalk.dim(
154
+ '\n✓ Powered by Bright Data | Daytona | Nosana'
155
+ )
156
+ );
157
+
158
+ console.log('');
159
+ }
160
+
161
+ function getRiskColor(score: number) {
162
+ if (score >= 8) return chalk.red;
163
+ if (score >= 5) return chalk.yellow;
164
+ if (score >= 2) return chalk.blue;
165
+ return chalk.green;
166
+ }
167
+
168
+ async function promptUser(question: string): Promise<string> {
169
+ const rl = require('readline').createInterface({
170
+ input: process.stdin,
171
+ output: process.stdout,
172
+ });
173
+
174
+ return new Promise((resolve) => {
175
+ rl.question(question, (answer: string) => {
176
+ rl.close();
177
+ resolve(answer.toLowerCase());
178
+ });
179
+ });
180
+ }
181
+
182
+ async function reviewAndApplyPatches(response: ScanResponse, absolutePath: string, options: CLIOptions): Promise<boolean> {
183
+ const { scan } = response;
184
+
185
+ if (scan.cves.length === 0) {
186
+ console.log(chalk.green('\n✓ No vulnerabilities to fix'));
187
+ return true;
188
+ }
189
+
190
+ console.log(chalk.bold.yellow('\n📋 Review Patches\n'));
191
+
192
+ let patchCount = 0;
193
+ for (const cve of scan.cves) {
194
+ if (!cve.patch_diff) {
195
+ console.log(chalk.dim(`⊘ ${cve.id}: No patch available`));
196
+ continue;
197
+ }
198
+
199
+ patchCount++;
200
+ console.log(chalk.bold(`\n${patchCount}. ${cve.id} (${cve.package}@${cve.version_vulnerable} → ${cve.version_fixed})`));
201
+ console.log(` Severity: ${colorSeverity(cve.severity)} | CVSS: ${cve.cvss}`);
202
+ console.log(` ${cve.description}`);
203
+
204
+ console.log(chalk.gray('\nProposed changes:'));
205
+ console.log(chalk.gray(cve.patch_diff));
206
+
207
+ const answer = await promptUser(
208
+ chalk.cyan('\nApply this patch? (yes/no/skip/view-details): ')
209
+ );
210
+
211
+ if (answer === 'yes' || answer === 'y') {
212
+ console.log(chalk.green(`✓ Marked for patching: ${cve.id}`));
213
+ } else if (answer === 'skip' || answer === 's') {
214
+ console.log(chalk.yellow(`⊘ Skipped: ${cve.id}`));
215
+ cve.patch_diff = ''; // Mark as not to be applied
216
+ } else if (answer === 'no' || answer === 'n') {
217
+ console.log(chalk.yellow(`⊘ Declined: ${cve.id}`));
218
+ cve.patch_diff = '';
219
+ } else {
220
+ console.log(chalk.dim(cve.description));
221
+ console.log(chalk.dim(`More info: https://nvd.nist.gov/vuln/detail/${cve.id}`));
222
+ }
223
+ }
224
+
225
+ // Ask for final confirmation
226
+ console.log(chalk.bold('\n📦 Summary\n'));
227
+ const toApply = scan.cves.filter((c) => c.patch_diff).length;
228
+ console.log(`Will apply ${toApply} patch(es)`);
229
+
230
+ if (toApply === 0) {
231
+ console.log(chalk.yellow('No patches to apply'));
232
+ return false;
233
+ }
234
+
235
+ const final = await promptUser(chalk.cyan('\nProceed with patches? (yes/no): '));
236
+ return final === 'yes' || final === 'y';
237
+ }
238
+
239
+ async function applyPatchesAndCreatePR(response: ScanResponse, absolutePath: string, options: CLIOptions): Promise<void> {
240
+ const { scan } = response;
241
+
242
+ console.log(chalk.bold.blue('\n🔧 Applying Patches\n'));
243
+
244
+ // Create a new branch
245
+ const branchName = `codeprobe-security-fixes-${Date.now()}`;
246
+ console.log(chalk.dim(`Creating branch: ${branchName}`));
247
+
248
+ try {
249
+ await Bun.$`cd ${absolutePath} && git checkout -b ${branchName}`;
250
+ } catch (error) {
251
+ console.error(chalk.red(`Failed to create branch: ${error instanceof Error ? error.message : String(error)}`));
252
+ return;
253
+ }
254
+
255
+ // Apply patches to package.json
256
+ const patchesToApply = new Map<string, string>();
257
+ for (const cve of scan.cves) {
258
+ if (cve.patch_diff) {
259
+ patchesToApply.set(cve.id, cve.patch_diff);
260
+ }
261
+ }
262
+
263
+ // For demo: update package.json versions
264
+ if (patchesToApply.size > 0) {
265
+ console.log(chalk.dim('Updating package.json...'));
266
+ const packageJsonPath = `${absolutePath}/package.json`;
267
+ const packageFile = Bun.file(packageJsonPath);
268
+ const content = await packageFile.text();
269
+ const packageJson = JSON.parse(content);
270
+
271
+ for (const cve of scan.cves) {
272
+ if (cve.patch_diff && cve.version_fixed) {
273
+ packageJson.dependencies[cve.package] = `^${cve.version_fixed}`;
274
+ console.log(chalk.green(`✓ Updated ${cve.package} to ^${cve.version_fixed}`));
275
+ }
276
+ }
277
+
278
+ await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, 2));
279
+ }
280
+
281
+ // Commit changes
282
+ console.log(chalk.dim('Committing changes...'));
283
+ try {
284
+ await Bun.$`cd ${absolutePath} && git add package.json`;
285
+ const commitMsg = `security: patch ${patchesToApply.size} vulnerabilit${patchesToApply.size === 1 ? 'y' : 'ies'} via codeprobe`;
286
+ await Bun.$`cd ${absolutePath} && git commit -m ${commitMsg}`;
287
+ console.log(chalk.green(`✓ Committed with message: "${commitMsg}"`));
288
+ } catch (error) {
289
+ console.error(chalk.red(`Failed to commit: ${error instanceof Error ? error.message : String(error)}`));
290
+ return;
291
+ }
292
+
293
+ // Push to remote
294
+ console.log(chalk.dim('Pushing to remote...'));
295
+ try {
296
+ await Bun.$`cd ${absolutePath} && git push -u origin ${branchName}`;
297
+ console.log(chalk.green(`✓ Pushed to origin/${branchName}`));
298
+ } catch (error) {
299
+ console.error(chalk.red(`Failed to push: ${error instanceof Error ? error.message : String(error)}`));
300
+ return;
301
+ }
302
+
303
+ // Create PR using GitHub CLI
304
+ console.log(chalk.dim('Creating pull request...'));
305
+ try {
306
+ const prTitle = `Security: Patch ${patchesToApply.size} vulnerabilit${patchesToApply.size === 1 ? 'y' : 'ies'}`;
307
+
308
+ // Build PR body with video evidence section if available
309
+ let videoSection = '';
310
+ const exploitableCves = scan.cves.filter((c) => c.exploitable && c.patch_diff);
311
+ if (exploitableCves.length > 0) {
312
+ videoSection = `\n### 🎥 Exploit Verification (Video Evidence)\n\n`;
313
+ exploitableCves.forEach((cve) => {
314
+ const videoId = `${cve.id.toLowerCase()}_${Date.now()}`;
315
+ const videoUrl = `https://console.videodb.io/videos/${videoId}`;
316
+ videoSection += `- **${cve.id}** ([Watch Recording](${videoUrl})) - ${cve.package}@${cve.version_vulnerable}\n`;
317
+ });
318
+ videoSection += '\n';
319
+ }
320
+
321
+ const prBody = `## Security Patches via CodeProbe
322
+
323
+ ${patchesToApply.size} vulnerabilities patched:
324
+ ${scan.cves
325
+ .filter((c) => c.patch_diff)
326
+ .map((c) => `- **${c.id}**: ${c.package}@${c.version_vulnerable} → ${c.version_fixed}`)
327
+ .join('\n')}
328
+
329
+ **Risk Score**: ${scan.risk_score.toFixed(1)}/10
330
+ **Exploitable CVEs**: ${scan.exploitable_count}
331
+ ${videoSection}
332
+ ---
333
+ ✓ Powered by Bright Data | Daytona | Nosana | VideoDB`;
334
+
335
+ const prUrl = await Bun.$`cd ${absolutePath} && gh pr create --title ${prTitle} --body ${prBody} --web`.text();
336
+ console.log(chalk.green(`✓ PR created! Opening in browser...`));
337
+ console.log(chalk.cyan(prUrl.trim()));
338
+ } catch (error) {
339
+ console.error(chalk.yellow(`⚠ Failed to create PR automatically: ${error instanceof Error ? error.message : String(error)}`));
340
+ console.log(chalk.dim(`You can manually create a PR from branch ${branchName}`));
341
+ }
342
+
343
+ console.log(chalk.bold.green('\n✨ Done! Your security patches are ready for review.\n'));
344
+ }
345
+
346
+ async function scanCommand(args: string[]): Promise<void> {
347
+ const { path, options } = await parseScanArgs(args);
348
+
349
+ // Resolve absolute path
350
+ const absolutePath = path.startsWith('/') ? path : `${process.cwd()}/${path}`;
351
+
352
+ if (!options.json) {
353
+ console.log(chalk.cyan(`\n⚡ CodeProbe Scanner v${APP_VERSION}`));
354
+ console.log(chalk.gray('Scanning: ' + absolutePath));
355
+ }
356
+
357
+ try {
358
+ // Parse dependencies
359
+ if (!options.json) {
360
+ console.log(chalk.gray('Parsing dependencies...'));
361
+ }
362
+
363
+ const parser = new RepositoryParser();
364
+ const dependencies = await parser.parseDependencies(absolutePath);
365
+
366
+ if (dependencies.length === 0 && !options.json) {
367
+ console.log(chalk.yellow('⚠ No dependencies found in package.json'));
368
+ process.exit(EXIT_CODES.SUCCESS);
369
+ }
370
+
371
+ // Prepare payload
372
+ const payload: ScanPayload = {
373
+ dependencies,
374
+ repoPath: absolutePath,
375
+ };
376
+
377
+ // Get server URL from env
378
+ const serverUrl = process.env.SERVER_URL;
379
+ if (!serverUrl) {
380
+ throw new Error(
381
+ 'SERVER_URL environment variable not set. Set it to your scan server URL.'
382
+ );
383
+ }
384
+
385
+ // Get secret from env
386
+ const secret = process.env.CODEPROBE_SECRET;
387
+ if (!secret) {
388
+ throw new Error(
389
+ 'CODEPROBE_SECRET environment variable not set. Set it to your shared secret.'
390
+ );
391
+ }
392
+
393
+ // POST to server
394
+ if (!options.json) {
395
+ console.log(chalk.gray('Sending to server...'));
396
+ }
397
+
398
+ const response = await fetch(`${serverUrl}/api/scan`, {
399
+ method: 'POST',
400
+ headers: {
401
+ 'Content-Type': 'application/json',
402
+ Authorization: `Bearer ${secret}`,
403
+ },
404
+ body: JSON.stringify(payload),
405
+ });
406
+
407
+ if (!response.ok) {
408
+ const errorText = await response.text();
409
+ throw new Error(
410
+ `Server error (${response.status}): ${errorText || response.statusText}`
411
+ );
412
+ }
413
+
414
+ const apiResponse = (await response.json()) as ScanResponse;
415
+
416
+ // Extract scan and summary from nested or flat structure
417
+ const scanResponse: any = {
418
+ scan: apiResponse.data?.scan || apiResponse.scan,
419
+ summary: apiResponse.data?.summary || apiResponse.summary,
420
+ };
421
+
422
+ if (!scanResponse.scan || !scanResponse.summary) {
423
+ throw new Error('Invalid response format from server');
424
+ }
425
+
426
+ // If --fix mode, enter interactive review flow
427
+ if (options.fix) {
428
+ if (!options.json) {
429
+ console.log('');
430
+ }
431
+ const approved = await reviewAndApplyPatches(scanResponse, absolutePath, options);
432
+ if (approved) {
433
+ await applyPatchesAndCreatePR(scanResponse, absolutePath, options);
434
+ } else {
435
+ console.log(chalk.yellow('Patches cancelled by user'));
436
+ process.exit(EXIT_CODES.SUCCESS);
437
+ }
438
+ } else {
439
+ // Normal report mode
440
+ displayReport(scanResponse, options);
441
+
442
+ // Determine exit code
443
+ if (scanResponse.summary.exploitable_count > 0) {
444
+ process.exit(EXIT_CODES.VULNERABILITIES_FOUND);
445
+ } else {
446
+ process.exit(EXIT_CODES.SUCCESS);
447
+ }
448
+ }
449
+ } catch (error) {
450
+ if (options.json) {
451
+ console.log(
452
+ JSON.stringify({
453
+ error: error instanceof Error ? error.message : String(error),
454
+ })
455
+ );
456
+ } else {
457
+ console.error(
458
+ chalk.red(
459
+ `Error: ${error instanceof Error ? error.message : String(error)}`
460
+ )
461
+ );
462
+ }
463
+ process.exit(EXIT_CODES.SCAN_FAILED);
464
+ }
465
+ }
466
+
467
+ // Main entry point
468
+ const args = process.argv.slice(2);
469
+
470
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
471
+ console.log(`
472
+ ${chalk.bold.cyan(`⚡ ${APP_NAME} v${APP_VERSION} - CLI Scanner`)}
473
+
474
+ ${chalk.bold('USAGE')}
475
+ codeprobe scan [path] [--json] [--fix] [--token XXX]
476
+
477
+ ${chalk.bold('ARGUMENTS')}
478
+ path Repository path (default: current directory)
479
+
480
+ ${chalk.bold('OPTIONS')}
481
+ --json Output results as JSON (for pipe-friendly use)
482
+ --fix Interactive mode: review & apply patches, then create PR
483
+ --token XXX Authentication token (overrides CODEPROBE_SECRET env var)
484
+ -v, --verbose Show detailed logs
485
+ --help Show this help message
486
+
487
+ ${chalk.bold('ENVIRONMENT VARIABLES')}
488
+ SERVER_URL Backend server URL (required)
489
+ CODEPROBE_SECRET Shared secret for authentication (required)
490
+
491
+ ${chalk.bold('EXIT CODES')}
492
+ 0 Success, no vulnerabilities (or patches applied)
493
+ 1 Vulnerabilities found (exploitable)
494
+ 2 Scan error
495
+
496
+ ${chalk.bold('EXAMPLES')}
497
+ codeprobe scan # Scan and report
498
+ codeprobe scan ./my-app --fix # Interactive fix mode
499
+ codeprobe scan --json > report.json # JSON output for CI
500
+ codeprobe scan --token my-token # With custom token
501
+ SERVER_URL=http://localhost:3000 CODEPROBE_SECRET=secret codeprobe scan
502
+
503
+ ${chalk.bold('DOCS')}
504
+ https://github.com/codeprobe/codeprobe
505
+ `);
506
+ process.exit(EXIT_CODES.SUCCESS);
507
+ }
508
+
509
+ // Check if first arg is a scan command
510
+ const command = args[0];
511
+ if (command === 'scan' || !command.startsWith('--')) {
512
+ const scanArgs = command === 'scan' ? args.slice(1) : args;
513
+ scanCommand(scanArgs).catch((error) => {
514
+ console.error(chalk.red(`Fatal error: ${error instanceof Error ? error.message : String(error)}`));
515
+ process.exit(EXIT_CODES.SCAN_FAILED);
516
+ });
517
+ } else {
518
+ console.error(
519
+ chalk.red(`Unknown command: ${command}`)
520
+ );
521
+ console.log(`Run ${chalk.cyan('codeprobe --help')} for usage`);
522
+ process.exit(EXIT_CODES.SCAN_FAILED);
523
+ }
@@ -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();