codeprobe-scanner 1.0.8 → 1.0.9

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.8",
3
+ "version": "1.0.9",
4
4
  "description": "Automated vulnerability scanner with exploit verification and video evidence",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +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
+ import { createScraper } from '../../engine/scraper.js';
12
12
 
13
13
  interface ScanOptions {
14
14
  fix: boolean;
@@ -105,23 +105,6 @@ export async function scanCommand(args: string[]): Promise<void> {
105
105
 
106
106
  logger.printHeader();
107
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
-
125
108
  const startTime = Date.now();
126
109
 
127
110
  try {
@@ -139,6 +122,31 @@ export async function scanCommand(args: string[]): Promise<void> {
139
122
  // Display results
140
123
  displayReport(report, options.json, duration);
141
124
 
125
+ // Show recent npm threats from GitHub Advisory Database
126
+ if (!options.json) {
127
+ try {
128
+ const scraper = createScraper();
129
+ const threats = await scraper.fetchRecentThreats();
130
+ if (threats.length > 0) {
131
+ console.log(chalk.bold('\n🌐 Recent npm Security Threats (GitHub Advisory Database):'));
132
+ console.log(chalk.gray('─'.repeat(60)));
133
+ for (const t of threats.slice(0, 5)) {
134
+ const sev = t.severity === 'CRITICAL' ? chalk.red(t.severity)
135
+ : t.severity === 'HIGH' ? chalk.yellow(t.severity)
136
+ : chalk.blue(t.severity);
137
+ console.log(` ${sev} ${chalk.bold(t.title)}`);
138
+ if (t.packages.length > 0) {
139
+ console.log(chalk.dim(` Packages: ${t.packages.join(', ')}`));
140
+ }
141
+ console.log(chalk.dim(` ${t.published?.slice(0, 10)} · ${t.url}`));
142
+ }
143
+ console.log('');
144
+ }
145
+ } catch {
146
+ // Non-fatal — threats feed is best-effort
147
+ }
148
+ }
149
+
142
150
  // If --fix, handle that next
143
151
  if (options.fix) {
144
152
  const { scanWithFixCommand } = await import('./scan-with-fix.js');
@@ -27,8 +27,8 @@ export class CodeProbeEngine {
27
27
  const dependencies = await this.parser.parseDependencies(repoPath);
28
28
  console.log(` Found ${dependencies.length} dependencies`);
29
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...");
30
+ // Step 2: Scrape CVEs from OSV.dev + npm audit
31
+ console.log("🔍 Checking OSV.dev + npm advisory database...");
32
32
  const cves = await this.scraper.scrapeAll(dependencies);
33
33
  console.log(` Found ${cves.length} CVEs`);
34
34
 
@@ -1,34 +1,111 @@
1
1
  import axios from "axios";
2
- import { CVE, ScrapeResult } from "../shared/types";
3
- import { API_ENDPOINTS, TIMEOUTS, PATHS, DEMO_CVE } from "../shared/constants";
4
- import { getConfig } from "../cli/config.js";
2
+ import { CVE } from "../shared/types";
3
+ import { DEMO_CVE } from "../shared/constants";
4
+
5
+ const OSV_API = "https://api.osv.dev/v1";
6
+ const GITHUB_ADVISORY_API = "https://api.github.com/advisories";
7
+
8
+ export interface ThreatIntel {
9
+ id: string;
10
+ title: string;
11
+ severity: string;
12
+ packages: string[];
13
+ published: string;
14
+ url: string;
15
+ summary: string;
16
+ }
5
17
 
6
18
  export class CVEScraper {
7
- private apiKey: string = "";
8
- private cache: Map<string, CVE> = new Map();
19
+ // Query OSV.dev exact package+version match, no false positives
20
+ private async queryOSV(packageName: string, version: string): Promise<CVE[]> {
21
+ try {
22
+ const response = await axios.post(
23
+ `${OSV_API}/query`,
24
+ {
25
+ package: { name: packageName, ecosystem: "npm" },
26
+ version,
27
+ },
28
+ { timeout: 10000, headers: { "Content-Type": "application/json" } }
29
+ );
9
30
 
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 : "";
31
+ const vulns = response.data?.vulns;
32
+ if (!Array.isArray(vulns)) return [];
33
+
34
+ return vulns.map((v: any) => {
35
+ const severity = v.severity?.[0]?.score || v.database_specific?.severity || "MEDIUM";
36
+ const cvss = typeof severity === "number" ? severity : this.severityToScore(severity);
37
+ const aliases: string[] = v.aliases || [];
38
+ const cveId = aliases.find((a: string) => a.startsWith("CVE-")) || v.id;
39
+
40
+ return {
41
+ id: cveId,
42
+ package: packageName,
43
+ affected_versions: [version],
44
+ fixed_version: this.extractFixedVersion(v, packageName),
45
+ severity: this.normalizeSeverity(severity),
46
+ cvss,
47
+ description: v.details || v.summary || "",
48
+ cwe: v.database_specific?.cwe_ids?.[0] || "",
49
+ exploit_url: `https://osv.dev/vulnerability/${v.id}`,
50
+ } as CVE;
51
+ });
52
+ } catch {
53
+ return [];
54
+ }
14
55
  }
15
56
 
16
- async scrapeForCVEs(packageName: string, version: string): Promise<CVE[]> {
17
- this.apiKey = await this.resolveApiKey();
57
+ // Fetch recent npm security threats from GitHub Advisory Database
58
+ async fetchRecentThreats(): Promise<ThreatIntel[]> {
59
+ try {
60
+ const response = await axios.get(GITHUB_ADVISORY_API, {
61
+ params: {
62
+ ecosystem: "npm",
63
+ per_page: 10,
64
+ sort: "published",
65
+ direction: "desc",
66
+ },
67
+ timeout: 10000,
68
+ headers: { Accept: "application/vnd.github+json" },
69
+ });
70
+
71
+ return (response.data || []).map((a: any) => ({
72
+ id: a.ghsa_id,
73
+ title: a.summary || a.ghsa_id,
74
+ severity: a.severity?.toUpperCase() || "UNKNOWN",
75
+ packages: (a.vulnerabilities || []).map((v: any) => v.package?.name).filter(Boolean),
76
+ published: a.published_at,
77
+ url: a.html_url || `https://github.com/advisories/${a.ghsa_id}`,
78
+ summary: a.description?.substring(0, 200) || "",
79
+ }));
80
+ } catch {
81
+ return [];
82
+ }
83
+ }
18
84
 
85
+ async scrapeForCVEs(packageName: string, version: string): Promise<CVE[]> {
19
86
  if (packageName === DEMO_CVE.package) {
20
87
  return [this.buildDemoCVE()];
21
88
  }
89
+ return await this.queryOSV(packageName, version);
90
+ }
22
91
 
23
- if (!this.apiKey) {
24
- return [];
25
- }
26
-
27
- try {
28
- return await this.fetchFromBrightData(packageName, version);
29
- } catch (error) {
30
- return [];
92
+ async scrapeAll(
93
+ dependencies: Array<{ name: string; version: string }>
94
+ ): Promise<CVE[]> {
95
+ const results = await Promise.all(
96
+ dependencies.map(dep => this.scrapeForCVEs(dep.name, dep.version))
97
+ );
98
+ const seen = new Set<string>();
99
+ const allCVEs: CVE[] = [];
100
+ for (const cves of results) {
101
+ for (const cve of cves) {
102
+ if (!seen.has(cve.id)) {
103
+ seen.add(cve.id);
104
+ allCVEs.push(cve);
105
+ }
106
+ }
31
107
  }
108
+ return allCVEs;
32
109
  }
33
110
 
34
111
  private buildDemoCVE(): CVE {
@@ -41,87 +118,38 @@ export class CVEScraper {
41
118
  cvss: DEMO_CVE.cvss,
42
119
  description: DEMO_CVE.description,
43
120
  cwe: "CWE-94",
44
- exploit_url: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-29078",
121
+ exploit_url: `https://nvd.nist.gov/vuln/detail/${DEMO_CVE.id}`,
45
122
  };
46
123
  }
47
124
 
48
- private async fetchFromBrightData(packageName: string, version: string): Promise<CVE[]> {
49
- try {
50
- // Use Bright Data Scraper API with Bearer token authentication
51
- // For NVD data, we'll use the public NVD API endpoint with Bright Data proxy/unblocking
52
- console.log(`[Bright Data] Scraping ${packageName} from NVD...`);
53
-
54
- const response = await axios.get(
55
- `${API_ENDPOINTS.NVD}?keyword=${packageName}`,
56
- {
57
- headers: {
58
- Authorization: `Bearer ${this.apiKey}`,
59
- "Accept": "application/json",
60
- },
61
- timeout: TIMEOUTS.BRIGHT_DATA_SCRAPE,
125
+ private extractFixedVersion(vuln: any, packageName: string): string {
126
+ const affected = vuln.affected || [];
127
+ for (const a of affected) {
128
+ if (a.package?.name === packageName) {
129
+ for (const range of a.ranges || []) {
130
+ for (const event of range.events || []) {
131
+ if (event.fixed) return event.fixed;
132
+ }
62
133
  }
63
- );
64
-
65
- if (response.data?.vulnerabilities && Array.isArray(response.data.vulnerabilities)) {
66
- return response.data.vulnerabilities
67
- .filter((vuln: any) => {
68
- const affectedProducts = vuln.cve?.configurations?.[0]?.nodes?.flatMap((n: any) =>
69
- n.cpeMatch?.map((m: any) => m.criteria) || []
70
- ) || [];
71
- return affectedProducts.some((p: string) => p?.includes(packageName));
72
- })
73
- .map((vuln: any) => {
74
- const descriptions = vuln.cve?.descriptions || [];
75
- return {
76
- id: vuln.id,
77
- package: packageName,
78
- affected_versions: [version],
79
- fixed_version: "",
80
- severity: vuln.impact?.baseSeverity || "MEDIUM",
81
- cvss: vuln.impact?.baseScore || 5.0,
82
- description: descriptions[0]?.value || "",
83
- cwe: vuln.cve?.weaknesses?.[0]?.source || "",
84
- exploit_url: `https://nvd.nist.gov/vuln/detail/${vuln.id}`,
85
- };
86
- })
87
- .slice(0, 5); // Limit to top 5 results
88
134
  }
89
-
90
- return [];
91
- } catch (error) {
92
- // Bright Data failed, will fall back to cache
93
- console.warn(`[Bright Data] Scraping failed: ${error instanceof Error ? error.message : String(error)}`);
94
- throw new Error(`Bright Data API call failed: ${error instanceof Error ? error.message : String(error)}`);
95
135
  }
136
+ return "";
96
137
  }
97
138
 
98
- private async loadFromCache(): Promise<CVE[]> {
99
- try {
100
- const cacheFile = Bun.file(PATHS.CACHE_FILE);
101
- const exists = await cacheFile.exists();
102
-
103
- if (exists) {
104
- const content = await cacheFile.text();
105
- const cacheData = JSON.parse(content);
106
- return cacheData.cves || [];
107
- }
108
- } catch (error) {
109
- console.warn("Failed to load CVE cache:", error);
110
- }
111
-
112
- // Return at least the demo CVE
113
- return [this.buildDemoCVE()];
139
+ private normalizeSeverity(s: string): "CRITICAL" | "HIGH" | "MEDIUM" | "LOW" {
140
+ const upper = (s || "").toUpperCase();
141
+ if (upper === "CRITICAL") return "CRITICAL";
142
+ if (upper === "HIGH") return "HIGH";
143
+ if (upper === "LOW") return "LOW";
144
+ return "MEDIUM";
114
145
  }
115
146
 
116
- async scrapeAll(dependencies: Array<{ name: string; version: string }>): Promise<CVE[]> {
117
- const allCVEs: CVE[] = [];
118
-
119
- for (const dep of dependencies) {
120
- const cves = await this.scrapeForCVEs(dep.name, dep.version);
121
- allCVEs.push(...cves);
122
- }
123
-
124
- return allCVEs;
147
+ private severityToScore(s: string): number {
148
+ const upper = (s || "").toUpperCase();
149
+ if (upper === "CRITICAL") return 9.0;
150
+ if (upper === "HIGH") return 7.5;
151
+ if (upper === "LOW") return 3.0;
152
+ return 5.0;
125
153
  }
126
154
  }
127
155