codeprobe-scanner 1.0.9 → 1.0.11

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/README.md CHANGED
@@ -1,15 +1,209 @@
1
- # codeprobe
1
+ # CodeProbe
2
2
 
3
- To install dependencies:
3
+ Automated vulnerability scanner for Node.js / npm projects. Scans dependencies against OSV.dev and the GitHub Advisory Database, verifies exploits in isolated sandboxes, and optionally auto-patches vulnerable packages.
4
4
 
5
- ```bash
6
- bun install
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install -g codeprobe-scanner
9
+ ```
10
+
11
+ Or run without installing:
12
+
13
+ ```sh
14
+ npx codeprobe-scanner scan
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```sh
20
+ # Scan the current directory
21
+ codeprobe scan
22
+
23
+ # Scan a specific project
24
+ codeprobe scan ./my-app
25
+
26
+ # Scan and auto-fix vulnerabilities
27
+ codeprobe scan --fix
28
+
29
+ # Output results as JSON
30
+ codeprobe scan --json > report.json
31
+
32
+ # Show the last scan report
33
+ codeprobe report
7
34
  ```
8
35
 
9
- To run:
36
+ ## Commands
37
+
38
+ ### `codeprobe scan [path]`
39
+
40
+ Scans a repository for known CVEs in its npm dependencies.
41
+
42
+ | Flag | Description |
43
+ |------|-------------|
44
+ | `--fix` | Auto-fix: upgrades vulnerable packages, creates a git branch and commit |
45
+ | `--json` | Print results as JSON (pipe-friendly) |
46
+ | `--verbose` | Show detailed phase-by-phase logs |
47
+
48
+ **What it does:**
49
+
50
+ 1. Parses `package.json` / `package-lock.json` / `bun.lock` for installed packages and versions
51
+ 2. Queries **OSV.dev** for CVEs matching each package+version
52
+ 3. Cross-references the **GitHub Advisory Database** for additional intelligence
53
+ 4. Runs exploit verification in an isolated **Daytona sandbox** (when configured)
54
+ 5. Saves the report to `~/.codeprobe/scans/<scan-id>.json`
55
+ 6. Displays a risk score, CVE table, and recent npm threat feed
10
56
 
11
- ```bash
12
- bun run index.ts
57
+ **Exit codes:**
58
+
59
+ | Code | Meaning |
60
+ |------|---------|
61
+ | `0` | No vulnerabilities found |
62
+ | `1` | Vulnerabilities found |
63
+ | `2` | Scan failed |
64
+
65
+ ### `codeprobe report`
66
+
67
+ Displays the most recent scan results from `~/.codeprobe/scans/latest.json`.
68
+
69
+ ### `codeprobe config set <key> <value>`
70
+
71
+ Saves a configuration value to `~/.codeprobe/config.json`.
72
+
73
+ ```sh
74
+ codeprobe config set bright_data_api_key YOUR_KEY
75
+ codeprobe config set daytona_api_key YOUR_KEY
76
+ ```
77
+
78
+ ## Configuration
79
+
80
+ CodeProbe works out of the box with zero configuration using public APIs. Optional integrations unlock deeper exploit verification.
81
+
82
+ ### Environment Variables
83
+
84
+ | Variable | Description |
85
+ |----------|-------------|
86
+ | `BRIGHT_DATA_API_KEY` | Bright Data scraping proxy (optional) |
87
+ | `DAYTONA_API_KEY` | Daytona sandbox for exploit verification (optional) |
88
+ | `NOSANA_API_KEY` | Nosana LLM for AI-assisted analysis (optional) |
89
+ | `GITHUB_TOKEN` | GitHub token for higher Advisory API rate limits |
90
+ | `PORT` | API server port (default: `3000`) |
91
+
92
+ Create a `.env` file in your project root or export variables in your shell.
93
+
94
+ ## How It Works
95
+
96
+ ```
97
+ package.json / lockfile
98
+
99
+ Dependency Parser
100
+
101
+ OSV.dev + GitHub Advisory DB ──→ CVE list
102
+
103
+ Sandbox Exploit Verification ──→ Confirmed / Theoretical
104
+
105
+ Risk Score + Report
13
106
  ```
14
107
 
15
- This project was created using `bun init` in bun v1.3.14. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
108
+ - **Risk score** = (confirmed exploitable × 10) + (theoretical × 3)
109
+ - Scans are saved locally at `~/.codeprobe/scans/`
110
+ - `latest.json` always points to the most recent scan
111
+
112
+ ## Auto-Fix (`--fix`)
113
+
114
+ When `--fix` is passed, CodeProbe:
115
+
116
+ 1. Identifies vulnerable packages that have a patched version available
117
+ 2. Updates `package.json` to the safe version
118
+ 3. Runs `bun install` (or `npm install`) to apply the change
119
+ 4. Creates a git commit on a new branch: `codeprobe-fix/<scan-id>`
120
+
121
+ Review the branch and open a PR — no changes are pushed automatically.
122
+
123
+ ## API Server
124
+
125
+ Start the REST API server:
126
+
127
+ ```sh
128
+ bun run src/api/server.ts
129
+ ```
130
+
131
+ | Endpoint | Method | Description |
132
+ |----------|--------|-------------|
133
+ | `POST /api/scan` | POST | Trigger a scan (`{ "repoPath": "./path" }`) |
134
+ | `GET /api/scans` | GET | List all past scans (requires auth) |
135
+ | `GET /api/scans/:id` | GET | Get a specific scan (requires auth) |
136
+ | `GET /api/auth/github` | GET | GitHub OAuth callback |
137
+ | `GET /api/auth/logout` | GET | Logout |
138
+
139
+ Authentication: pass a `Bearer <token>` header. In development mode any non-empty token is accepted.
140
+
141
+ ## Docker
142
+
143
+ ```sh
144
+ docker build -t codeprobe .
145
+ docker run -e PORT=8080 -p 8080:8080 codeprobe
146
+ ```
147
+
148
+ ## GitHub Actions
149
+
150
+ Add CodeProbe to your CI pipeline:
151
+
152
+ ```yaml
153
+ # .github/workflows/codeprobe-scan.yml
154
+ name: Security Scan
155
+ on: [push, pull_request]
156
+ jobs:
157
+ scan:
158
+ runs-on: ubuntu-latest
159
+ steps:
160
+ - uses: actions/checkout@v3
161
+ - run: npx codeprobe-scanner scan --json > report.json
162
+ - uses: actions/upload-artifact@v3
163
+ with:
164
+ name: security-report
165
+ path: report.json
166
+ ```
167
+
168
+ ## Output Example
169
+
170
+ ```
171
+ ╔══════════════════════════════════════════╗
172
+ ║ CodeProbe Scanner ║
173
+ ╚══════════════════════════════════════════╝
174
+
175
+ SCAN COMPLETE
176
+ Risk Score: 🔴 HIGH (43)
177
+ Confirmed Exploitable: 2 | Theoretical Risk: 5
178
+ Patches Available: 3
179
+ Duration: 8.3s
180
+
181
+ CVE Details:
182
+ CVE-2022-29078: ejs 3.1.6 [CRITICAL] ✓ CONFIRMED EXPLOITABLE
183
+ → Patch available: 3.1.7
184
+ CVE-2023-44487: http2-server 1.0.0 [HIGH] ✓ CONFIRMED EXPLOITABLE
185
+ → Patch available: 1.0.1
186
+
187
+ 🌐 Recent npm Security Threats (GitHub Advisory Database):
188
+ CRITICAL lodash - Prototype Pollution
189
+ HIGH axios - SSRF via redirect
190
+ ```
191
+
192
+ ## Project Structure
193
+
194
+ ```
195
+ src/
196
+ ├── cli/ # CLI entry point and commands (scan, report, config)
197
+ ├── engine/ # Core scanner: parser, scraper, sandbox, patcher
198
+ ├── api/ # REST API server
199
+ ├── shared/ # Types, constants, utilities
200
+ ├── integrations/ # VideoDB, Daytona, Nosana integrations
201
+ ├── bot/ # Bot server
202
+ └── mcp/ # MCP server
203
+ bin/
204
+ └── codeprobe.cjs # CLI binary entry point
205
+ ```
206
+
207
+ ## License
208
+
209
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeprobe-scanner",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Automated vulnerability scanner with exploit verification and video evidence",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@daytona/sdk": "^0.187.0",
17
- "axios": "^1.6.5",
17
+ "axios": "^1.16.0",
18
18
  "chalk": "^5.3.0",
19
19
  "cli-table3": "^0.6.3",
20
20
  "dayjs": "^1.11.10",
@@ -1,22 +1,16 @@
1
1
  import chalk from 'chalk';
2
2
  import { execSync } from 'child_process';
3
+ import { readFileSync, writeFileSync } from 'fs';
4
+ import path from 'path';
3
5
  import dayjs from 'dayjs';
4
6
  import { Report } from '../../shared/types.js';
5
7
  import { ProgressLogger } from '../progress.js';
6
8
  import { GitError, handleError } from '../errors.js';
7
9
  import { EXIT_CODES } from '../../shared/constants.js';
8
10
 
9
- function getGitStatus(): string {
10
- try {
11
- return execSync('git status --porcelain', { encoding: 'utf-8' });
12
- } catch {
13
- throw new GitError('Not a git repository');
14
- }
15
- }
16
-
17
11
  function checkGitRepo(): boolean {
18
12
  try {
19
- execSync('git rev-parse --git-dir', { encoding: 'utf-8' });
13
+ execSync('git rev-parse --git-dir', { encoding: 'utf-8', stdio: 'pipe' });
20
14
  return true;
21
15
  } catch {
22
16
  return false;
@@ -24,25 +18,48 @@ function checkGitRepo(): boolean {
24
18
  }
25
19
 
26
20
  function isGitDirty(): boolean {
27
- const status = getGitStatus();
28
- return status.trim().length > 0;
21
+ try {
22
+ const status = execSync('git status --porcelain', { encoding: 'utf-8', stdio: 'pipe' });
23
+ return status.trim().length > 0;
24
+ } catch {
25
+ throw new GitError('Not a git repository');
26
+ }
29
27
  }
30
28
 
31
29
  function createBranch(name: string): void {
32
30
  try {
33
- execSync(`git checkout -b ${name}`, { encoding: 'utf-8' });
34
- } catch (error) {
31
+ execSync(`git checkout -b ${name}`, { encoding: 'utf-8', stdio: 'pipe' });
32
+ } catch {
35
33
  throw new GitError(`Failed to create branch: ${name}`);
36
34
  }
37
35
  }
38
36
 
39
- function commitChanges(message: string): void {
37
+ function commitAndPush(message: string, branchName: string): void {
38
+ execSync('git add package.json', { encoding: 'utf-8', stdio: 'pipe' });
39
+ execSync(`git commit -m "${message}"`, { encoding: 'utf-8', stdio: 'pipe' });
40
40
  try {
41
- execSync('git add .', { encoding: 'utf-8' });
42
- execSync(`git commit -m "${message}"`, { encoding: 'utf-8' });
43
- } catch (error) {
44
- throw new GitError('Failed to commit changes');
41
+ execSync(`git push -u origin ${branchName}`, { encoding: 'utf-8', stdio: 'pipe' });
42
+ } catch {
43
+ // Push failed — not fatal, user can push manually
44
+ }
45
+ }
46
+
47
+ function applyVersionBumps(
48
+ repoPath: string,
49
+ bumps: Array<{ package: string; from: string; to: string }>
50
+ ): void {
51
+ const pkgPath = path.join(repoPath, 'package.json');
52
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
53
+
54
+ for (const bump of bumps) {
55
+ if (pkg.dependencies?.[bump.package]) {
56
+ pkg.dependencies[bump.package] = `^${bump.to}`;
57
+ } else if (pkg.devDependencies?.[bump.package]) {
58
+ pkg.devDependencies[bump.package] = `^${bump.to}`;
59
+ }
45
60
  }
61
+
62
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
46
63
  }
47
64
 
48
65
  export async function scanWithFixCommand(
@@ -52,72 +69,81 @@ export async function scanWithFixCommand(
52
69
  ): Promise<void> {
53
70
  console.log('');
54
71
  logger.printSeparator();
55
- logger.logPhaseStart('git', 'Preparing to apply patches');
56
72
 
57
- try {
58
- // Check git repo exists
59
- if (!checkGitRepo()) {
60
- throw new GitError('Not a git repository. Run: git init');
61
- }
73
+ const repoPath = path.resolve(args[0] || '.');
62
74
 
63
- // Check git status
64
- if (isGitDirty()) {
65
- logger.logWarning('Git repository has uncommitted changes', 'Commit or stash first');
66
- throw new GitError('Repository is dirty. Commit changes before applying patches.');
67
- }
75
+ // Collect CVEs that have a known fixed version
76
+ const fixable = report.scan.cves.filter(
77
+ (cve) => cve.version_fixed && cve.version_fixed.trim() !== ''
78
+ );
68
79
 
69
- // Filter for exploitable CVEs only
70
- const exploitableCVEs = report.scan.cves.filter((cve) => cve.exploitable);
80
+ if (fixable.length === 0) {
81
+ console.log(chalk.yellow('⚠️ No CVEs with known fixes found.'));
82
+ console.log(chalk.dim(' All vulnerabilities are either unpatched or already on the latest version.'));
83
+ process.exit(EXIT_CODES.SUCCESS);
84
+ }
71
85
 
72
- if (exploitableCVEs.length === 0) {
73
- logger.logWarning('No exploitable CVEs found', 'Nothing to patch');
74
- process.exit(EXIT_CODES.SUCCESS);
86
+ // Deduplicate: one bump per package (use highest fixed version)
87
+ const bumpMap = new Map<string, { from: string; to: string; cves: string[] }>();
88
+ for (const cve of fixable) {
89
+ const existing = bumpMap.get(cve.package);
90
+ if (!existing) {
91
+ bumpMap.set(cve.package, {
92
+ from: cve.version_vulnerable,
93
+ to: cve.version_fixed!,
94
+ cves: [cve.id],
95
+ });
96
+ } else {
97
+ existing.cves.push(cve.id);
75
98
  }
99
+ }
76
100
 
77
- // Create feature branch
78
- const timestamp = dayjs().format('YYYY-MM-DD-HHmmss');
79
- const branchName = `codeprobe-fix-${timestamp}`;
101
+ const bumps = Array.from(bumpMap.entries()).map(([pkg, info]) => ({
102
+ package: pkg,
103
+ ...info,
104
+ }));
105
+
106
+ // Show what will be changed
107
+ console.log(chalk.bold(`\n📦 ${bumps.length} package(s) can be updated:\n`));
108
+ for (const b of bumps) {
109
+ console.log(
110
+ ` ${chalk.cyan(b.package)}: ${chalk.red(b.from)} → ${chalk.green(b.to)}`
111
+ );
112
+ console.log(chalk.dim(` Fixes: ${b.cves.slice(0, 3).join(', ')}${b.cves.length > 3 ? ` +${b.cves.length - 3} more` : ''}`));
113
+ }
80
114
 
81
- logger.logPhaseStart('git', `Creating branch: ${branchName}`);
82
- createBranch(branchName);
83
- logger.logPhaseComplete('git', `Branch created: ${branchName}`);
115
+ // Check git repo
116
+ if (!checkGitRepo()) {
117
+ console.log(chalk.yellow('\n⚠️ Not a git repository applying fixes without committing.'));
118
+ applyVersionBumps(repoPath, bumps);
119
+ console.log(chalk.green('\n✓ package.json updated. Run your package manager to install.'));
120
+ process.exit(EXIT_CODES.SUCCESS);
121
+ }
84
122
 
85
- // Apply patches
86
- for (const cve of exploitableCVEs) {
87
- if (!cve.patch_diff) continue;
123
+ if (isGitDirty()) {
124
+ console.log(chalk.yellow('\n⚠️ Uncommitted changes detected — applying fixes without committing.'));
125
+ applyVersionBumps(repoPath, bumps);
126
+ console.log(chalk.green('\n✓ package.json updated. Commit when ready.'));
127
+ process.exit(EXIT_CODES.SUCCESS);
128
+ }
88
129
 
89
- logger.logPhaseStart('patch', `Applying patch for ${cve.id}`);
130
+ // Create branch and apply fixes
131
+ const branchName = `codeprobe-fix-${dayjs().format('YYYY-MM-DD-HHmmss')}`;
132
+ console.log(chalk.dim(`\nCreating branch: ${branchName}`));
133
+ createBranch(branchName);
90
134
 
91
- // Mock: just log the patch
92
- console.log(chalk.gray(` Patch preview:\n${cve.patch_diff.split('\n').slice(0, 5).join('\n')}`));
135
+ applyVersionBumps(repoPath, bumps);
136
+ console.log(chalk.green('\n✓ package.json updated'));
93
137
 
94
- // In production: apply patch using git apply or manual file updates
95
- // For now: mock success
96
- logger.logPhaseComplete('patch', `Patched ${cve.package}: ${cve.version_vulnerable} → ${cve.patch_version}`);
97
- }
138
+ const commitMsg = `security: bump ${bumps.length} vulnerable package(s) via codeprobe\\n\\n${bumps.map(b => `- ${b.package}: ${b.from} -> ${b.to}`).join('\\n')}`;
139
+ commitAndPush(commitMsg, branchName);
98
140
 
99
- // Commit with detailed message
100
- const commitMsg =
101
- `[CodeProbe] Fix ${exploitableCVEs.length} exploitable CVE(s)\n\n` +
102
- exploitableCVEs
103
- .map((cve) => `- ${cve.id} (${cve.package} ${cve.version_vulnerable} → ${cve.patch_version})`)
104
- .join('\n');
105
-
106
- logger.logPhaseStart('git', 'Committing patches');
107
- commitChanges(commitMsg);
108
- logger.logPhaseComplete('git', 'Changes committed');
109
-
110
- // Show what to do next
111
- console.log('');
112
- console.log(chalk.green('✓ Patches applied successfully!'));
113
- console.log(chalk.cyan(`\nNext steps:`));
114
- console.log(chalk.cyan(` 1. Review changes: git diff main`));
115
- console.log(chalk.cyan(` 2. Push branch: git push -u origin ${branchName}`));
116
- console.log(chalk.cyan(` 3. Create PR on GitHub`));
117
-
118
- logger.printSeparator();
119
- process.exit(EXIT_CODES.SUCCESS);
120
- } catch (error) {
121
- handleError(error, logger, true);
122
- }
141
+ console.log(chalk.bold.green(`\n✨ Done! Branch '${branchName}' created and pushed.\n`));
142
+ console.log(chalk.cyan('Next steps:'));
143
+ console.log(` 1. Run your package manager: ${chalk.white('bun install')} or ${chalk.white('npm install')}`);
144
+ console.log(` 2. Open a PR from branch: ${chalk.white(branchName)}`);
145
+ console.log('');
146
+
147
+ logger.printSeparator();
148
+ process.exit(EXIT_CODES.SUCCESS);
123
149
  }
@@ -61,10 +61,13 @@ export class CodeProbeEngine {
61
61
  }
62
62
  }
63
63
 
64
- // Step 7: Generate patches (Nosana)
64
+ // Step 7: Generate patches for exploitable CVEs + any HIGH/CRITICAL with a known fix version
65
65
  console.log("\x1b[33m[Nosana]\x1b[0m 🔧 Generating patches with LLM...");
66
66
  await this.patcher.loadPrebakedPatches();
67
- const patches = await this.patcher.generateAllPatches(matchedCves.filter((c) => c.exploitable));
67
+ const patchCandidates = matchedCves.filter((c) =>
68
+ c.exploitable || ((c.severity === "CRITICAL" || c.severity === "HIGH") && c.version_fixed)
69
+ );
70
+ const patches = await this.patcher.generateAllPatches(patchCandidates);
68
71
  for (const cve of matchedCves) {
69
72
  if (patches.has(cve.id)) {
70
73
  cve.patch_diff = patches.get(cve.id);