@lincoln504/pi-research 0.1.13 → 1.0.1
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/LICENSE +1 -1
- package/README.md +55 -138
- package/dist/cli.mjs +23375 -0
- package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
- package/dist/prompts/research-knowledge-search-extractor.md +41 -0
- package/dist/prompts/research-tool-usage.md +92 -0
- package/dist/prompts/researcher.md +89 -0
- package/dist/prompts/system-coordinator.md +75 -0
- package/dist/prompts/system-lead-evaluator.md +118 -0
- package/dist/thread-worker.mjs +2611 -0
- package/dist/webgpu-probe.mjs +80 -0
- package/docs/AGENT-SKILL.md +130 -0
- package/docs/ARCHITECTURE.md +306 -80
- package/docs/CONFIGURATION.md +249 -0
- package/docs/KNOWLEDGE-STORE.md +211 -0
- package/docs/PI-EXTENSION.md +91 -0
- package/docs/SDK.md +122 -0
- package/docs/THIRD-PARTY-NOTICES.md +66 -0
- package/package.json +102 -36
- package/scripts/build.cjs +140 -0
- package/scripts/cleanup.cjs +149 -0
- package/scripts/prepare.cjs +28 -0
- package/scripts/setup.cjs +134 -0
- package/skills/pi-research/README.md +90 -0
- package/skills/pi-research/SKILL.md +102 -0
- package/skills/pi-research/references/configuration.md +77 -0
- package/skills/pi-research/scripts/run.mjs +237 -0
- package/src/cleanup/research-cleanup.ts +140 -0
- package/src/cli.ts +1195 -0
- package/src/config.ts +1032 -155
- package/src/constants.ts +115 -84
- package/src/core/health-check-service.ts +265 -0
- package/src/core/interfaces/health-check-interfaces.ts +50 -0
- package/src/core/interfaces/knowledge-interfaces.ts +190 -0
- package/src/core/interfaces/observer-interfaces.ts +67 -0
- package/src/core/interfaces/orchestration-interfaces.ts +86 -0
- package/src/core/interfaces/planning-interfaces.ts +100 -0
- package/src/core/interfaces/process-interfaces.ts +37 -0
- package/src/core/interfaces/research-plan-types.ts +52 -0
- package/src/core/interfaces/scheduler-interfaces.ts +91 -0
- package/src/core/interfaces/service-names.ts +39 -0
- package/src/core/interfaces/state-manager-interfaces.ts +29 -0
- package/src/core/interfaces/steering-interfaces.ts +31 -0
- package/src/core/llm/agentic-repair.ts +169 -0
- package/src/core/llm/inject-date.ts +52 -0
- package/src/core/llm/llm-timeout.ts +64 -0
- package/src/core/llm/llm-utils.ts +86 -0
- package/src/core/llm/model-registry-factory.ts +287 -0
- package/src/core/llm/pi-ai-completion.ts +29 -0
- package/src/core/llm/prompts.ts +50 -0
- package/src/core/llm/research-model-resolver.ts +91 -0
- package/src/core/planning-constants.ts +13 -0
- package/src/core/planning-service.ts +649 -0
- package/src/core/planning-utils.ts +299 -0
- package/src/core/scheduler-factory.ts +48 -0
- package/src/core/scheduler-service.ts +123 -0
- package/src/core/service-initialization.ts +194 -0
- package/src/core/service-interfaces.ts +48 -0
- package/src/core/service-registry.ts +659 -0
- package/src/healthcheck/index.ts +271 -265
- package/src/healthcheck/registry.ts +130 -0
- package/src/index.ts +639 -50
- package/src/infrastructure/browser/browser-cleanup.ts +309 -0
- package/src/infrastructure/browser/browser-client.ts +199 -0
- package/src/infrastructure/browser/browser-error-utils.ts +160 -0
- package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
- package/src/infrastructure/browser/browser-server.ts +174 -0
- package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
- package/src/infrastructure/browser/cleanup-utils.ts +155 -0
- package/src/infrastructure/browser/client-agent.ts +31 -0
- package/src/infrastructure/browser/config.ts +288 -0
- package/src/infrastructure/browser/ensure-browser.ts +205 -0
- package/src/infrastructure/browser/index.ts +37 -0
- package/src/infrastructure/browser/priority-task-queue.ts +226 -0
- package/src/infrastructure/browser/scheduler-factory.ts +354 -0
- package/src/infrastructure/browser/task-execution-service.ts +209 -0
- package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
- package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
- package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
- package/src/infrastructure/browser/thread-worker-types.ts +25 -0
- package/src/infrastructure/browser/thread-worker.mjs +2611 -0
- package/src/infrastructure/browser/thread-worker.ts +168 -0
- package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
- package/src/infrastructure/embedding/embedding-client.ts +205 -0
- package/src/infrastructure/embedding/embedding-factory.ts +324 -0
- package/src/infrastructure/embedding/embedding-server.ts +535 -0
- package/src/infrastructure/file-lock-service.ts +592 -0
- package/src/infrastructure/gpu-resource-service.ts +191 -0
- package/src/infrastructure/knowledge-store-service.ts +475 -0
- package/src/infrastructure/metrics-service.ts +129 -0
- package/src/infrastructure/process-lifecycle-service.ts +253 -0
- package/src/infrastructure/scheduler-factory-service.ts +58 -0
- package/src/infrastructure/service-initialization.ts +292 -0
- package/src/infrastructure/state/state-backup-manager.ts +290 -0
- package/src/infrastructure/state/state-browser-api.ts +62 -0
- package/src/infrastructure/state/state-browser-manager.ts +70 -0
- package/src/infrastructure/state/state-manager-service.ts +264 -0
- package/src/infrastructure/state/state-manager.ts +547 -0
- package/src/infrastructure/state/state-metrics.ts +92 -0
- package/src/infrastructure/state/state-migration.ts +103 -0
- package/src/infrastructure/state/state-path-configuration.ts +119 -0
- package/src/infrastructure/state/state-session-api.ts +94 -0
- package/src/infrastructure/state/state-session-manager.ts +127 -0
- package/src/infrastructure/state/state-validator.ts +93 -0
- package/src/infrastructure/types/state-types.ts +85 -0
- package/src/knowledge/chunker.ts +157 -0
- package/src/knowledge/embedder-init.ts +413 -0
- package/src/knowledge/embedder-utils.ts +296 -0
- package/src/knowledge/embedder.ts +592 -0
- package/src/knowledge/index.ts +126 -0
- package/src/knowledge/lancedb-loader.ts +35 -0
- package/src/knowledge/migration.ts +17 -0
- package/src/knowledge/model-config.ts +132 -0
- package/src/knowledge/onnx-env.ts +48 -0
- package/src/knowledge/store-operations.ts +291 -0
- package/src/knowledge/store-schema.ts +79 -0
- package/src/knowledge/store.ts +1251 -0
- package/src/knowledge/transformers-loader.ts +31 -0
- package/src/knowledge/webgpu-probe.mjs +80 -0
- package/src/knowledge/webgpu-viability.ts +255 -0
- package/src/knowledge/writer-queue.ts +252 -0
- package/src/logger.ts +316 -122
- package/src/observers/research-observer-impl.ts +459 -0
- package/src/orchestration/deep-research-orchestrator.ts +524 -773
- package/src/orchestration/deep-research-types.ts +47 -35
- package/src/orchestration/headless-observer.ts +114 -0
- package/src/orchestration/orchestration-types.ts +33 -0
- package/src/orchestration/quick-research-orchestrator.ts +463 -0
- package/src/orchestration/research-orchestration-service.ts +549 -0
- package/src/orchestration/research-session-service.ts +178 -0
- package/src/orchestration/research-synthesis-service.ts +348 -0
- package/src/orchestration/researcher-executor.ts +394 -0
- package/src/orchestration/researcher.ts +115 -42
- package/src/orchestration/service-initialization.ts +109 -0
- package/src/orchestration/session-context.ts +7 -3
- package/src/orchestration/session-state.ts +672 -0
- package/src/prompts/research-knowledge-relevance-triage.md +35 -0
- package/src/prompts/research-knowledge-search-extractor.md +41 -0
- package/src/prompts/research-tool-usage.md +63 -16
- package/src/prompts/researcher.md +59 -136
- package/src/prompts/system-coordinator.md +64 -13
- package/src/prompts/system-lead-evaluator.md +82 -189
- package/src/research-config.ts +872 -0
- package/src/sdk.ts +847 -0
- package/src/security/cisa-kev.ts +72 -9
- package/src/security/cvss.ts +92 -0
- package/src/security/ecosystem.ts +45 -0
- package/src/security/github-advisories.ts +168 -428
- package/src/security/github-advisory-types.ts +282 -0
- package/src/security/index.ts +63 -177
- package/src/security/nvd-client.ts +550 -0
- package/src/security/nvd-types.ts +179 -0
- package/src/security/osv-client.ts +246 -0
- package/src/security/osv-types.ts +285 -0
- package/src/security/types.ts +67 -53
- package/src/skill-install/skill-installer.ts +519 -0
- package/src/stackexchange/index.ts +81 -27
- package/src/stackexchange/output/compact.ts +7 -6
- package/src/stackexchange/output/table.ts +11 -7
- package/src/stackexchange/queries.ts +0 -10
- package/src/stackexchange/rest-client.ts +141 -66
- package/src/stackexchange/types.ts +0 -17
- package/src/tool.ts +15 -423
- package/src/tools/grep.ts +32 -310
- package/src/tools/health-tool-definition.ts +101 -0
- package/src/tools/index.ts +38 -9
- package/src/tools/research-knowledge-search.ts +974 -0
- package/src/tools/research-knowledge-types.ts +77 -0
- package/src/tools/research-tool-definition.ts +494 -0
- package/src/tools/scrape.ts +286 -388
- package/src/tools/search.ts +98 -153
- package/src/tools/security.ts +107 -80
- package/src/tools/stackexchange.ts +58 -35
- package/src/tools/youtube-transcript.ts +163 -0
- package/src/tui/knowledge-search-panel.ts +86 -0
- package/src/tui/pulse.ts +86 -0
- package/src/tui/research-health.ts +124 -0
- package/src/tui/research-panel-color-utils.ts +340 -0
- package/src/tui/research-panel-format.ts +54 -0
- package/src/tui/research-panel-state.ts +404 -0
- package/src/tui/research-panel-wave.ts +280 -0
- package/src/tui/research-panel.ts +339 -435
- package/src/tui/research-tui-manager.ts +233 -0
- package/src/tui/tui-controller.ts +137 -0
- package/src/types/extension-context.ts +18 -49
- package/src/types/index.ts +61 -0
- package/src/types/llm.ts +68 -29
- package/src/types/research-panel-types.ts +61 -0
- package/src/utils/circuit-breaker.ts +146 -0
- package/src/utils/citation-utils.ts +97 -0
- package/src/utils/disk-space-checker.ts +55 -0
- package/src/utils/error-tracker.ts +265 -0
- package/src/utils/host-config.ts +45 -0
- package/src/utils/input-validation.ts +8 -10
- package/src/utils/json-utils.ts +150 -16
- package/src/utils/llm-usage.ts +54 -0
- package/src/utils/log-rotation.ts +125 -0
- package/src/utils/log-utils.ts +159 -0
- package/src/utils/make-resource-loader.ts +3 -1
- package/src/utils/metrics-summary.ts +459 -0
- package/src/utils/metrics.ts +302 -0
- package/src/utils/research-export.ts +28 -12
- package/src/utils/safe-unref.ts +31 -0
- package/src/utils/shared-links.ts +195 -90
- package/src/utils/shutdown-manager.ts +65 -0
- package/src/utils/stdio-capture.ts +407 -0
- package/src/utils/text-utils.ts +258 -11
- package/src/utils/tool-usage-tracker.ts +83 -16
- package/src/utils/url-utils.ts +255 -0
- package/src/utils/user-agent.ts +28 -0
- package/src/web-research/browser-search.ts +179 -0
- package/src/web-research/retry-utils.ts +173 -77
- package/src/web-research/scraper-types.ts +72 -0
- package/src/web-research/scraper-utils.ts +434 -0
- package/src/web-research/search.ts +78 -218
- package/src/web-research/types.ts +9 -24
- package/src/web-research/utils.ts +2 -143
- package/src/web-research/web-scraper.ts +477 -0
- package/src/youtube/index.ts +154 -0
- package/src/youtube/potoken.ts +259 -0
- package/src/youtube/transcript-client.ts +390 -0
- package/src/youtube/video-id.ts +118 -0
- package/config/searxng/default-settings.yml +0 -189
- package/config/searxng/limiter.toml +0 -34
- package/config/tooling/eslint.config.js +0 -73
- package/config/tooling/vitest.config.integration.ts +0 -17
- package/config/tooling/vitest.config.ts +0 -40
- package/config/tooling/vitest.config.unit.ts +0 -12
- package/docs/assets/README-banner.jpg +0 -0
- package/scripts/setup.js +0 -212
- package/src/infrastructure/searxng-lifecycle.ts +0 -644
- package/src/infrastructure/searxng-manager.ts +0 -1183
- package/src/infrastructure/state-manager.ts +0 -918
- package/src/orchestration/deep-research-reducer.ts +0 -129
- package/src/orchestration/id-utils.ts +0 -63
- package/src/security/nvd.ts +0 -532
- package/src/security/osv.ts +0 -487
- package/src/stackexchange/cache.ts +0 -111
- package/src/stackexchange/output/index.ts +0 -7
- package/src/stackexchange/output/json.ts +0 -21
- package/src/tui/searxng-status.ts +0 -80
- package/src/utils/inject-date.ts +0 -29
- package/src/utils/research-error.ts +0 -205
- package/src/utils/searxng-config.ts +0 -94
- package/src/utils/session-state.ts +0 -295
- package/src/web-research/dependencies.ts +0 -20
- package/src/web-research/scrapers.ts +0 -935
- package/tsconfig.json +0 -40
package/src/security/cisa-kev.ts
CHANGED
|
@@ -7,10 +7,20 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Vulnerability, CisaKevResult } from './types.ts';
|
|
10
|
-
import { createTimeoutSignal } from '../web-research/retry-utils.ts';
|
|
10
|
+
import { createTimeoutSignal, retryWithBackoff, isTransientError } from '../web-research/retry-utils.ts';
|
|
11
|
+
import { CircuitBreaker } from '../utils/circuit-breaker.ts';
|
|
12
|
+
import { metrics } from '../utils/metrics.ts';
|
|
11
13
|
|
|
12
14
|
const CISA_KEV_URL = 'https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json';
|
|
13
15
|
|
|
16
|
+
// Circuit breaker to prevent cascading failures
|
|
17
|
+
const cisaCircuitBreaker = new CircuitBreaker({
|
|
18
|
+
failureThreshold: 5,
|
|
19
|
+
resetTimeoutMs: 10000,
|
|
20
|
+
name: 'CISA KEV API',
|
|
21
|
+
isTransientError: isTransientError
|
|
22
|
+
});
|
|
23
|
+
|
|
14
24
|
// ============================================================================
|
|
15
25
|
// Type Definitions for CISA KEV API
|
|
16
26
|
// ============================================================================
|
|
@@ -102,6 +112,42 @@ function extractCisaKevItems(data: unknown): readonly CisaKevItem[] {
|
|
|
102
112
|
// Main API Functions
|
|
103
113
|
// ============================================================================
|
|
104
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Fetch with retry and circuit breaker for resilience
|
|
117
|
+
*/
|
|
118
|
+
async function fetchWithRetryImpl(url: string, options: RequestInit, signal?: AbortSignal): Promise<Response> {
|
|
119
|
+
const endpoint = 'cisa_kev_feed';
|
|
120
|
+
return cisaCircuitBreaker.execute(async () => {
|
|
121
|
+
return retryWithBackoff(
|
|
122
|
+
async () => {
|
|
123
|
+
const response = await fetch(url, options);
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
const error = new Error(`HTTP ${response.status}: ${response.statusText}`) as Error & { status?: number };
|
|
126
|
+
error.status = response.status;
|
|
127
|
+
metrics.increment('cisa_kev_errors_total', 1, { endpoint, status_code: response.status.toString() });
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
metrics.increment('cisa_kev_requests_total', 1, { endpoint, status: 'success' });
|
|
131
|
+
return response;
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
maxRetries: 3,
|
|
135
|
+
initialDelay: 1000,
|
|
136
|
+
maxDelay: 10000,
|
|
137
|
+
label: 'CISA KEV API',
|
|
138
|
+
signal,
|
|
139
|
+
isTransientError: (error) => {
|
|
140
|
+
const status = (error as Error & { status?: number })?.status;
|
|
141
|
+
if (typeof status === 'number') {
|
|
142
|
+
return status === 429 || status === 403 || status >= 500;
|
|
143
|
+
}
|
|
144
|
+
return isTransientError(error);
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
105
151
|
/**
|
|
106
152
|
* Fetch and search CISA KEV catalog
|
|
107
153
|
*
|
|
@@ -115,29 +161,36 @@ export async function searchCisaKev(
|
|
|
115
161
|
readonly vendor?: string;
|
|
116
162
|
readonly product?: string;
|
|
117
163
|
readonly maxResults?: number;
|
|
164
|
+
readonly signal?: AbortSignal;
|
|
118
165
|
},
|
|
119
166
|
): Promise<CisaKevResult> {
|
|
167
|
+
const startTime = Date.now();
|
|
120
168
|
const maxResults = options?.maxResults ?? 100;
|
|
121
169
|
const vulnerabilities: Vulnerability[] = [];
|
|
122
170
|
let error: string | undefined = undefined;
|
|
123
171
|
|
|
124
172
|
try {
|
|
125
|
-
const response: Response = await
|
|
173
|
+
const response: Response = await fetchWithRetryImpl(CISA_KEV_URL, {
|
|
126
174
|
headers: {
|
|
127
175
|
'User-Agent': 'pi-research/2.0',
|
|
128
176
|
'Accept': 'application/json',
|
|
129
177
|
},
|
|
130
|
-
signal: createTimeoutSignal(30000), // 30s timeout
|
|
131
|
-
});
|
|
178
|
+
signal: createTimeoutSignal(30000, options?.signal), // 30s timeout, composed with caller cancel
|
|
179
|
+
}, options?.signal);
|
|
132
180
|
|
|
133
|
-
|
|
134
|
-
|
|
181
|
+
let data: unknown;
|
|
182
|
+
try {
|
|
183
|
+
data = await response.json();
|
|
184
|
+
} catch {
|
|
185
|
+
metrics.increment('cisa_kev_errors_total', 1, { error_type: 'malformed_json' });
|
|
186
|
+
throw new Error('CISA KEV feed returned a malformed JSON response.');
|
|
135
187
|
}
|
|
136
188
|
|
|
137
|
-
const data: unknown = await response.json();
|
|
138
|
-
|
|
139
189
|
// CISA KEV format is an array of vulnerability objects or nested in a response object
|
|
140
190
|
const cisaData = extractCisaKevItems(data);
|
|
191
|
+
|
|
192
|
+
// Track cache metrics
|
|
193
|
+
metrics.increment(cisaData.length > 0 ? 'cisa_kev_cache_hits_total' : 'cisa_kev_cache_misses_total', 1);
|
|
141
194
|
|
|
142
195
|
// Map CISA format to our Vulnerability interface
|
|
143
196
|
for (const item of cisaData) {
|
|
@@ -170,6 +223,7 @@ export async function searchCisaKev(
|
|
|
170
223
|
}
|
|
171
224
|
|
|
172
225
|
// Sort by due date (most urgent first)
|
|
226
|
+
// FIX (#24): Guard against NaN from invalid dates to ensure stable sort
|
|
173
227
|
vulnerabilities.sort((a: Vulnerability, b: Vulnerability): number => {
|
|
174
228
|
if (a.dueDate === undefined) {
|
|
175
229
|
return 1;
|
|
@@ -177,11 +231,20 @@ export async function searchCisaKev(
|
|
|
177
231
|
if (b.dueDate === undefined) {
|
|
178
232
|
return -1;
|
|
179
233
|
}
|
|
180
|
-
|
|
234
|
+
const aTime = new Date(a.dueDate).getTime();
|
|
235
|
+
const bTime = new Date(b.dueDate).getTime();
|
|
236
|
+
// Treat invalid dates as Infinity (sorted to the end)
|
|
237
|
+
const aSafe = Number.isFinite(aTime) ? aTime : Infinity;
|
|
238
|
+
const bSafe = Number.isFinite(bTime) ? bTime : Infinity;
|
|
239
|
+
return aSafe - bSafe;
|
|
181
240
|
});
|
|
182
241
|
|
|
183
242
|
} catch (err: unknown) {
|
|
184
243
|
error = err instanceof Error ? err.message : String(err);
|
|
244
|
+
metrics.increment('cisa_kev_search_errors_total', 1, { error_type: err instanceof Error ? err.name : 'unknown' });
|
|
245
|
+
} finally {
|
|
246
|
+
const duration = Date.now() - startTime;
|
|
247
|
+
metrics.observe('cisa_kev_fetch_duration_ms', duration, { has_error: error ? 'true' : 'false' });
|
|
185
248
|
}
|
|
186
249
|
|
|
187
250
|
return {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal CVSS v3.0/v3.1 base-score calculator.
|
|
3
|
+
*
|
|
4
|
+
* Used to derive a qualitative severity + numeric base score from a CVSS vector STRING (OSV
|
|
5
|
+
* `severity[]` entries carry a vector, not a number). Implements the official base-score equations,
|
|
6
|
+
* which are IDENTICAL across v3.0 and v3.1 — there is no version branch in Base scoring. (The
|
|
7
|
+
* `× 0.9731`/exponent-13 changed-scope variant belongs only to the v3.1 Environmental metric group,
|
|
8
|
+
* not to Base; applying it here would mis-band high-impact changed-scope vectors, e.g. reporting
|
|
9
|
+
* the changed-scope vector AV:P/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H as 7.0/HIGH instead of 6.9/MEDIUM.)
|
|
10
|
+
* Returns null for a vector it
|
|
11
|
+
* cannot parse, a non-v3 vector, or an incomplete base vector — callers then fall back to UNKNOWN
|
|
12
|
+
* rather than guess.
|
|
13
|
+
*
|
|
14
|
+
* Only the BASE metric group is computed (temporal/environmental are ignored), which is what a
|
|
15
|
+
* severity label reflects. Verified against the CVSS v3.1 specification examples (9.8, 7.8, 6.1, 5.9)
|
|
16
|
+
* and the changed-scope case AV:P/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H → 6.9/MEDIUM (the shorthand
|
|
17
|
+
* S:C/C:H/I:H/A:H alone, with default AV:N/AC:L/PR:N/UI:N, actually scores 10.0/CRITICAL).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const AV: Readonly<Record<string, number>> = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 };
|
|
21
|
+
const AC: Readonly<Record<string, number>> = { L: 0.77, H: 0.44 };
|
|
22
|
+
const UI: Readonly<Record<string, number>> = { N: 0.85, R: 0.62 };
|
|
23
|
+
// Privileges Required weight depends on Scope (higher when the attack crosses a scope boundary).
|
|
24
|
+
const PR_UNCHANGED: Readonly<Record<string, number>> = { N: 0.85, L: 0.62, H: 0.27 };
|
|
25
|
+
const PR_CHANGED: Readonly<Record<string, number>> = { N: 0.85, L: 0.68, H: 0.5 };
|
|
26
|
+
const IMPACT: Readonly<Record<string, number>> = { H: 0.56, L: 0.22, N: 0 };
|
|
27
|
+
|
|
28
|
+
/** CVSS v3.1 Roundup: the smallest number to one decimal place that is >= input (float-safe). */
|
|
29
|
+
function roundUp1(input: number): number {
|
|
30
|
+
const intInput = Math.round(input * 100000);
|
|
31
|
+
if (intInput % 10000 === 0) return intInput / 100000;
|
|
32
|
+
return (Math.floor(intInput / 10000) + 1) / 10;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Map a CVSS base score (0.0–10.0) to its qualitative severity rating (CVSS v3 bands). */
|
|
36
|
+
export function severityFromScore(score: number): string {
|
|
37
|
+
if (score <= 0) return 'NONE';
|
|
38
|
+
if (score < 4.0) return 'LOW';
|
|
39
|
+
if (score < 7.0) return 'MEDIUM';
|
|
40
|
+
if (score < 9.0) return 'HIGH';
|
|
41
|
+
return 'CRITICAL';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Compute the CVSS v3.0/v3.1 base score + severity from a vector string, or null if it is not a
|
|
46
|
+
* parseable, complete v3 base vector.
|
|
47
|
+
*/
|
|
48
|
+
export function scoreCvss3(vector: string): { score: number; severity: string } | null {
|
|
49
|
+
// Accept only complete v3.0 / v3.1 base vectors. The Base formula is identical across both.
|
|
50
|
+
if (typeof vector !== 'string' || !/^CVSS:3\.[01]\//i.test(vector)) return null;
|
|
51
|
+
|
|
52
|
+
const metrics: Record<string, string> = {};
|
|
53
|
+
for (const part of vector.split('/').slice(1)) {
|
|
54
|
+
const [k, v] = part.split(':');
|
|
55
|
+
if (k && v) metrics[k.toUpperCase()] = v.toUpperCase();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const scope = metrics['S'];
|
|
59
|
+
const av = AV[metrics['AV'] ?? ''];
|
|
60
|
+
const ac = AC[metrics['AC'] ?? ''];
|
|
61
|
+
const ui = UI[metrics['UI'] ?? ''];
|
|
62
|
+
const pr = (scope === 'C' ? PR_CHANGED : PR_UNCHANGED)[metrics['PR'] ?? ''];
|
|
63
|
+
const c = IMPACT[metrics['C'] ?? ''];
|
|
64
|
+
const i = IMPACT[metrics['I'] ?? ''];
|
|
65
|
+
const a = IMPACT[metrics['A'] ?? ''];
|
|
66
|
+
|
|
67
|
+
if ((scope !== 'U' && scope !== 'C') || [av, ac, ui, pr, c, i, a].some(x => x === undefined)) {
|
|
68
|
+
return null; // incomplete / invalid base vector
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const iss = 1 - (1 - c!) * (1 - i!) * (1 - a!);
|
|
72
|
+
// The CVSS v3.0 and v3.1 BASE impact formulas are identical for both scopes — the `× 0.9731`
|
|
73
|
+
// multiplier / exponent-13 variant belongs ONLY to the v3.1 Environmental (ModifiedImpact)
|
|
74
|
+
// metric group, never to Base scoring. Applying it here mis-scored high-impact changed-scope
|
|
75
|
+
// vectors across a severity band (e.g. S:C/C:H/I:H/A:H → 7.0/HIGH instead of the correct
|
|
76
|
+
// 6.9/MEDIUM). There is no version branch in Base scoring.
|
|
77
|
+
const impact = scope === 'U'
|
|
78
|
+
? 6.42 * iss
|
|
79
|
+
: 7.52 * (iss - 0.029) - 3.25 * Math.pow(iss - 0.02, 15);
|
|
80
|
+
const exploitability = 8.22 * av! * ac! * pr! * ui!;
|
|
81
|
+
|
|
82
|
+
let score: number;
|
|
83
|
+
if (impact <= 0) {
|
|
84
|
+
score = 0;
|
|
85
|
+
} else if (scope === 'U') {
|
|
86
|
+
score = roundUp1(Math.min(impact + exploitability, 10));
|
|
87
|
+
} else {
|
|
88
|
+
score = roundUp1(Math.min(1.08 * (impact + exploitability), 10));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { score, severity: severityFromScore(score) };
|
|
92
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ecosystem-name normalization for the vulnerability databases.
|
|
3
|
+
*
|
|
4
|
+
* GitHub's Advisory Database and OSV use DIFFERENT identifiers for the same package ecosystem
|
|
5
|
+
* (e.g. GitHub wants `pip`/`rust`/`go`, OSV wants `PyPI`/`crates.io`/`Go`). The security tool
|
|
6
|
+
* accepts one user-facing `ecosystem` value, so a name that works for one database would be
|
|
7
|
+
* rejected outright by the other (GitHub 422 / OSV 400) — a guaranteed, silent lookup failure
|
|
8
|
+
* for common ecosystems like Python and Rust. This maps a user-friendly value onto the exact
|
|
9
|
+
* identifier each database expects. Unknown values pass through unchanged (prior behavior), so
|
|
10
|
+
* this can only fix a mismatch, never introduce one.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Canonical ecosystem key → per-database identifier. Keys are matched case-insensitively. */
|
|
14
|
+
const ECOSYSTEM_ALIASES: Record<string, { github: string; osv: string }> = {
|
|
15
|
+
npm: { github: 'npm', osv: 'npm' },
|
|
16
|
+
node: { github: 'npm', osv: 'npm' },
|
|
17
|
+
pip: { github: 'pip', osv: 'PyPI' },
|
|
18
|
+
pypi: { github: 'pip', osv: 'PyPI' },
|
|
19
|
+
python: { github: 'pip', osv: 'PyPI' },
|
|
20
|
+
maven: { github: 'maven', osv: 'Maven' },
|
|
21
|
+
go: { github: 'go', osv: 'Go' },
|
|
22
|
+
golang: { github: 'go', osv: 'Go' },
|
|
23
|
+
cargo: { github: 'rust', osv: 'crates.io' },
|
|
24
|
+
rust: { github: 'rust', osv: 'crates.io' },
|
|
25
|
+
'crates.io': { github: 'rust', osv: 'crates.io' },
|
|
26
|
+
nuget: { github: 'nuget', osv: 'NuGet' },
|
|
27
|
+
rubygems: { github: 'rubygems', osv: 'RubyGems' },
|
|
28
|
+
gem: { github: 'rubygems', osv: 'RubyGems' },
|
|
29
|
+
gems: { github: 'rubygems', osv: 'RubyGems' },
|
|
30
|
+
composer: { github: 'composer', osv: 'Packagist' },
|
|
31
|
+
packagist: { github: 'composer', osv: 'Packagist' },
|
|
32
|
+
php: { github: 'composer', osv: 'Packagist' },
|
|
33
|
+
pub: { github: 'pub', osv: 'Pub' },
|
|
34
|
+
dart: { github: 'pub', osv: 'Pub' },
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Map a user-supplied ecosystem name to the identifier the given database expects.
|
|
39
|
+
* Returns the input verbatim (trimmed) when it isn't a known alias.
|
|
40
|
+
*/
|
|
41
|
+
export function normalizeEcosystem(value: string, target: 'github' | 'osv'): string {
|
|
42
|
+
const key = value.trim().toLowerCase();
|
|
43
|
+
const entry = ECOSYSTEM_ALIASES[key];
|
|
44
|
+
return entry ? entry[target] : value.trim();
|
|
45
|
+
}
|