@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/nvd.ts
DELETED
|
@@ -1,532 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NVD (National Vulnerability Database) API Client
|
|
3
|
-
*
|
|
4
|
-
* NIST National Vulnerability Database
|
|
5
|
-
* API: https://services.nvd.nist.gov/rest/json/cves/2.0
|
|
6
|
-
* Free public API, no authentication required
|
|
7
|
-
*
|
|
8
|
-
* Rate Limits (Free Tier):
|
|
9
|
-
* - ~5 requests per 30 seconds
|
|
10
|
-
* - 50 requests per rolling 30 seconds
|
|
11
|
-
*
|
|
12
|
-
* This module includes a rate limiter to respect these limits.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { Vulnerability, NVDResult } from './types.ts';
|
|
16
|
-
import { logger } from '../logger.ts';
|
|
17
|
-
import { createTimeoutSignal, retryWithBackoff } from '../web-research/retry-utils.ts';
|
|
18
|
-
|
|
19
|
-
// ============================================================================
|
|
20
|
-
// Type Definitions
|
|
21
|
-
// ============================================================================
|
|
22
|
-
|
|
23
|
-
interface CVSSData {
|
|
24
|
-
readonly version?: string;
|
|
25
|
-
readonly vectorString?: string;
|
|
26
|
-
readonly baseScore?: number;
|
|
27
|
-
readonly baseSeverity?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface CVSSMetricV31 {
|
|
31
|
-
readonly cvssData: CVSSData;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface CVSSMetricV30 {
|
|
35
|
-
readonly cvssData: CVSSData;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface WeaknessDescription {
|
|
39
|
-
readonly value?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface Weakness {
|
|
43
|
-
readonly description?: WeaknessDescription[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface Reference {
|
|
47
|
-
readonly url?: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface CPEMatch {
|
|
51
|
-
readonly criteria?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface Node {
|
|
55
|
-
readonly cpeMatch?: CPEMatch[];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface Configuration {
|
|
59
|
-
readonly nodes?: Node[];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
interface Description {
|
|
63
|
-
readonly value?: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
interface Metrics {
|
|
67
|
-
readonly cvssMetricV31?: CVSSMetricV31[];
|
|
68
|
-
readonly cvssMetricV30?: CVSSMetricV30[];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
interface CVE {
|
|
72
|
-
readonly id?: string;
|
|
73
|
-
readonly descriptions?: Description[];
|
|
74
|
-
readonly published?: string;
|
|
75
|
-
readonly lastModified?: string;
|
|
76
|
-
readonly metrics?: Metrics;
|
|
77
|
-
readonly weaknesses?: Weakness[];
|
|
78
|
-
readonly references?: Reference[];
|
|
79
|
-
readonly configurations?: Configuration[];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
interface NVDEntry {
|
|
83
|
-
readonly cve: CVE;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface NVDApiResponse {
|
|
87
|
-
readonly vulnerabilities?: NVDEntry[];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ============================================================================
|
|
91
|
-
// Type Guards
|
|
92
|
-
// ============================================================================
|
|
93
|
-
|
|
94
|
-
function isWeaknessDescription(value: unknown): value is WeaknessDescription {
|
|
95
|
-
return (
|
|
96
|
-
typeof value === 'object' &&
|
|
97
|
-
value !== null &&
|
|
98
|
-
'value' in value
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function isWeakness(value: unknown): value is Weakness {
|
|
103
|
-
return (
|
|
104
|
-
typeof value === 'object' &&
|
|
105
|
-
value !== null &&
|
|
106
|
-
'description' in value
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function isReference(value: unknown): value is Reference {
|
|
111
|
-
return (
|
|
112
|
-
typeof value === 'object' &&
|
|
113
|
-
value !== null &&
|
|
114
|
-
'url' in value
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function isCPEMatch(value: unknown): value is CPEMatch {
|
|
119
|
-
return (
|
|
120
|
-
typeof value === 'object' &&
|
|
121
|
-
value !== null &&
|
|
122
|
-
'criteria' in value
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function isNode(value: unknown): value is Node {
|
|
127
|
-
return (
|
|
128
|
-
typeof value === 'object' &&
|
|
129
|
-
value !== null &&
|
|
130
|
-
'cpeMatch' in value
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function isConfiguration(value: unknown): value is Configuration {
|
|
135
|
-
return (
|
|
136
|
-
typeof value === 'object' &&
|
|
137
|
-
value !== null &&
|
|
138
|
-
'nodes' in value
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function isDescription(value: unknown): value is Description {
|
|
143
|
-
return (
|
|
144
|
-
typeof value === 'object' &&
|
|
145
|
-
value !== null &&
|
|
146
|
-
'value' in value
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function isNVDEntry(value: unknown): value is NVDEntry {
|
|
151
|
-
return (
|
|
152
|
-
typeof value === 'object' &&
|
|
153
|
-
value !== null &&
|
|
154
|
-
'cve' in value
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function isNVDApiResponse(value: unknown): value is NVDApiResponse {
|
|
159
|
-
return (
|
|
160
|
-
typeof value === 'object' &&
|
|
161
|
-
value !== null &&
|
|
162
|
-
'vulnerabilities' in value
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// ============================================================================
|
|
167
|
-
// Constants
|
|
168
|
-
// ============================================================================
|
|
169
|
-
|
|
170
|
-
const NVD_BASE_URL = 'https://services.nvd.nist.gov/rest/json/cves/2.0';
|
|
171
|
-
const DEFAULT_MAX_RESULTS = 20;
|
|
172
|
-
const MAX_RESULTS_PER_PAGE = 2000;
|
|
173
|
-
|
|
174
|
-
// ============================================================================
|
|
175
|
-
// NVD Rate Limiter
|
|
176
|
-
//
|
|
177
|
-
// NVD API has rate limits (free tier: ~5 req/30s).
|
|
178
|
-
// This rate limiter ensures we stay within limits by:
|
|
179
|
-
// 1. Tracking time between requests
|
|
180
|
-
// 2. Waiting if necessary to respect the minimum interval
|
|
181
|
-
// ============================================================================
|
|
182
|
-
|
|
183
|
-
class NVDRateLimiter {
|
|
184
|
-
private lastRequest: number = 0;
|
|
185
|
-
// 6 seconds between requests to stay within ~5 req/30s limit
|
|
186
|
-
private readonly minInterval: number = 6000;
|
|
187
|
-
|
|
188
|
-
async acquire(): Promise<void> {
|
|
189
|
-
const now = Date.now();
|
|
190
|
-
const waitTime = Math.max(0, this.minInterval - (now - this.lastRequest));
|
|
191
|
-
|
|
192
|
-
// Update lastRequest IMMEDIATELY to block subsequent parallel callers
|
|
193
|
-
this.lastRequest = now + waitTime;
|
|
194
|
-
|
|
195
|
-
if (waitTime > 0) {
|
|
196
|
-
// Unref the timeout so it doesn't prevent process exit
|
|
197
|
-
await new Promise<void>(resolve => {
|
|
198
|
-
const timeoutId = setTimeout(resolve, waitTime);
|
|
199
|
-
timeoutId.unref();
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Singleton rate limiter instance
|
|
206
|
-
const nvdRateLimiter = new NVDRateLimiter();
|
|
207
|
-
|
|
208
|
-
// ============================================================================
|
|
209
|
-
// Types for query options
|
|
210
|
-
// ============================================================================
|
|
211
|
-
|
|
212
|
-
type Severity = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
213
|
-
|
|
214
|
-
interface SearchOptions {
|
|
215
|
-
readonly severity?: Severity;
|
|
216
|
-
readonly maxResults?: number;
|
|
217
|
-
readonly includeExploited?: boolean;
|
|
218
|
-
readonly cweId?: string;
|
|
219
|
-
readonly startDate?: string;
|
|
220
|
-
readonly endDate?: string;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
interface RetryOptions {
|
|
224
|
-
readonly maxRetries: number;
|
|
225
|
-
readonly initialDelay: number;
|
|
226
|
-
readonly maxDelay: number;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// ============================================================================
|
|
230
|
-
// Helper Functions
|
|
231
|
-
// ============================================================================
|
|
232
|
-
|
|
233
|
-
function isKnownExploited(options: SearchOptions | undefined): boolean {
|
|
234
|
-
return options?.includeExploited === true;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function extractCVSSScore(metrics: Metrics | undefined): {
|
|
238
|
-
readonly score: number | undefined;
|
|
239
|
-
readonly vector: string | undefined;
|
|
240
|
-
readonly severity: string;
|
|
241
|
-
} {
|
|
242
|
-
let cvssScore: number | undefined;
|
|
243
|
-
let cvssVector: string | undefined;
|
|
244
|
-
let severity: string = 'UNKNOWN';
|
|
245
|
-
|
|
246
|
-
if (metrics?.cvssMetricV31 && metrics.cvssMetricV31.length > 0) {
|
|
247
|
-
const firstMetric = metrics.cvssMetricV31[0];
|
|
248
|
-
const cvssData = firstMetric?.cvssData;
|
|
249
|
-
cvssScore = cvssData?.baseScore;
|
|
250
|
-
cvssVector = cvssData?.vectorString;
|
|
251
|
-
severity = cvssData?.baseSeverity ?? 'UNKNOWN';
|
|
252
|
-
} else if (metrics?.cvssMetricV30 && metrics.cvssMetricV30.length > 0) {
|
|
253
|
-
const firstMetric = metrics.cvssMetricV30[0];
|
|
254
|
-
const cvssData = firstMetric?.cvssData;
|
|
255
|
-
cvssScore = cvssData?.baseScore;
|
|
256
|
-
cvssVector = cvssData?.vectorString;
|
|
257
|
-
severity = cvssData?.baseSeverity ?? 'UNKNOWN';
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return { score: cvssScore, vector: cvssVector, severity };
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function extractCWEs(cve: CVE): string[] {
|
|
264
|
-
const cwes: string[] = [];
|
|
265
|
-
|
|
266
|
-
if (cve.weaknesses) {
|
|
267
|
-
for (const weakness of cve.weaknesses) {
|
|
268
|
-
if (isWeakness(weakness) && weakness.description) {
|
|
269
|
-
for (const desc of weakness.description) {
|
|
270
|
-
if (isWeaknessDescription(desc) && typeof desc.value === 'string' && desc.value.startsWith('CWE-')) {
|
|
271
|
-
cwes.push(desc.value);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return cwes;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function extractReferences(cve: CVE): string[] {
|
|
282
|
-
const references: string[] = [];
|
|
283
|
-
|
|
284
|
-
if (cve.references) {
|
|
285
|
-
for (const ref of cve.references) {
|
|
286
|
-
if (isReference(ref) && typeof ref.url === 'string') {
|
|
287
|
-
references.push(ref.url);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return references;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function extractAffectedProducts(cve: CVE): string[] {
|
|
296
|
-
const affectedProducts: string[] = [];
|
|
297
|
-
|
|
298
|
-
if (cve.configurations) {
|
|
299
|
-
for (const config of cve.configurations) {
|
|
300
|
-
if (isConfiguration(config) && config.nodes) {
|
|
301
|
-
for (const node of config.nodes) {
|
|
302
|
-
if (isNode(node) && node.cpeMatch) {
|
|
303
|
-
for (const match of node.cpeMatch) {
|
|
304
|
-
if (isCPEMatch(match) && typeof match.criteria === 'string') {
|
|
305
|
-
affectedProducts.push(match.criteria);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return affectedProducts;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function getCVEDescription(cve: CVE): string {
|
|
318
|
-
if (cve.descriptions && cve.descriptions.length > 0) {
|
|
319
|
-
const firstDesc = cve.descriptions[0];
|
|
320
|
-
if (isDescription(firstDesc) && typeof firstDesc.value === 'string' && firstDesc.value.length > 0) {
|
|
321
|
-
return firstDesc.value;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return 'No description available';
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function parseNVDEntry(nvdEntry: NVDEntry, options: SearchOptions | undefined): Vulnerability {
|
|
328
|
-
const cve = nvdEntry.cve;
|
|
329
|
-
const metrics = cve.metrics;
|
|
330
|
-
|
|
331
|
-
const { score: cvssScore, vector: cvssVector, severity } = extractCVSSScore(metrics);
|
|
332
|
-
const cwes = extractCWEs(cve);
|
|
333
|
-
const references = extractReferences(cve);
|
|
334
|
-
const affectedProducts = extractAffectedProducts(cve);
|
|
335
|
-
const knownExploited = isKnownExploited(options);
|
|
336
|
-
|
|
337
|
-
return {
|
|
338
|
-
id: cve.id ?? 'UNKNOWN',
|
|
339
|
-
source: 'nvd',
|
|
340
|
-
severity,
|
|
341
|
-
description: getCVEDescription(cve),
|
|
342
|
-
published: cve.published,
|
|
343
|
-
modified: cve.lastModified,
|
|
344
|
-
cvssScore,
|
|
345
|
-
cvssVector,
|
|
346
|
-
cwes,
|
|
347
|
-
references,
|
|
348
|
-
affectedProducts,
|
|
349
|
-
fixes: [], // NVD doesn't provide fix info directly
|
|
350
|
-
knownExploited,
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function parseNVDResponse(data: unknown, options: SearchOptions | undefined): Vulnerability[] {
|
|
355
|
-
if (!isNVDApiResponse(data)) {
|
|
356
|
-
return [];
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (Array.isArray(data.vulnerabilities)) {
|
|
360
|
-
return data.vulnerabilities
|
|
361
|
-
.filter(isNVDEntry)
|
|
362
|
-
.map((entry) => parseNVDEntry(entry, options));
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return [];
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function buildURL(term: string, options: SearchOptions | undefined, maxResults: number): string {
|
|
369
|
-
const params = new globalThis.URLSearchParams();
|
|
370
|
-
|
|
371
|
-
// Add keyword search (supports multiple keywords with AND logic)
|
|
372
|
-
params.append('keywordSearch', term);
|
|
373
|
-
|
|
374
|
-
// Add optional filters
|
|
375
|
-
if (options?.severity) {
|
|
376
|
-
params.append('cvssV3Severity', options.severity);
|
|
377
|
-
}
|
|
378
|
-
if (options?.includeExploited) {
|
|
379
|
-
params.append('hasKev', '');
|
|
380
|
-
}
|
|
381
|
-
if (options?.cweId) {
|
|
382
|
-
params.append('cweId', options.cweId);
|
|
383
|
-
}
|
|
384
|
-
if (options?.startDate && options.endDate) {
|
|
385
|
-
params.append('pubStartDate', options.startDate);
|
|
386
|
-
params.append('pubEndDate', options.endDate);
|
|
387
|
-
}
|
|
388
|
-
params.append('resultsPerPage', maxResults.toString());
|
|
389
|
-
|
|
390
|
-
return `${NVD_BASE_URL}?${params.toString()}`;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function createFetchOptions(): RequestInit {
|
|
394
|
-
return {
|
|
395
|
-
headers: {
|
|
396
|
-
'User-Agent': 'pi-research/2.0',
|
|
397
|
-
'Accept': 'application/json',
|
|
398
|
-
},
|
|
399
|
-
signal: createTimeoutSignal(30000), // 30s timeout
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function handleFetchError(error: unknown): never {
|
|
404
|
-
if (error instanceof Error) {
|
|
405
|
-
if (error.name === 'AbortError' || error.name === 'DOMException') {
|
|
406
|
-
throw new Error(`NVD API timeout: ${error.message}`);
|
|
407
|
-
}
|
|
408
|
-
throw new Error(`NVD API network error: ${error.message}`);
|
|
409
|
-
}
|
|
410
|
-
throw new Error(`NVD API network error: ${String(error)}`);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function handleResponseStatus(response: Response): void {
|
|
414
|
-
if (!response.ok) {
|
|
415
|
-
// Handle rate limit specifically with a more helpful message
|
|
416
|
-
if (response.status === 429) {
|
|
417
|
-
throw new Error('NVD API rate limit exceeded (HTTP 429). Retrying with backoff...');
|
|
418
|
-
}
|
|
419
|
-
// Server errors (5xx) are also retryable
|
|
420
|
-
if (response.status >= 500) {
|
|
421
|
-
throw new Error(`NVD server error (HTTP ${response.status}). Retrying with backoff...`);
|
|
422
|
-
}
|
|
423
|
-
// Client errors (4xx except 429) are not retryable
|
|
424
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function fetchWithRetry(url: string): Promise<Response> {
|
|
429
|
-
const retryOptions: RetryOptions = {
|
|
430
|
-
maxRetries: 3,
|
|
431
|
-
initialDelay: 2000, // Start with 2s delay
|
|
432
|
-
maxDelay: 10000, // Max 10s delay
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
return retryWithBackoff(async () => {
|
|
436
|
-
let response: Response;
|
|
437
|
-
try {
|
|
438
|
-
response = await fetch(url, createFetchOptions());
|
|
439
|
-
} catch (error) {
|
|
440
|
-
handleFetchError(error);
|
|
441
|
-
}
|
|
442
|
-
handleResponseStatus(response);
|
|
443
|
-
return response;
|
|
444
|
-
}, retryOptions);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
async function searchSingleTerm(
|
|
448
|
-
term: string,
|
|
449
|
-
options: SearchOptions | undefined,
|
|
450
|
-
maxResults: number,
|
|
451
|
-
): Promise<Vulnerability[]> {
|
|
452
|
-
const url = buildURL(term, options, maxResults);
|
|
453
|
-
|
|
454
|
-
// Apply rate limiting before making the request
|
|
455
|
-
await nvdRateLimiter.acquire();
|
|
456
|
-
|
|
457
|
-
// Wrap fetch with retry logic for transient errors (rate limiting, server errors)
|
|
458
|
-
const response = await fetchWithRetry(url);
|
|
459
|
-
const data = await response.json();
|
|
460
|
-
|
|
461
|
-
return parseNVDResponse(data, options);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function deduplicateVulnerabilities(vulnerabilityArrays: Vulnerability[][]): Vulnerability[] {
|
|
465
|
-
const uniqueVulns = new Map<string, Vulnerability>();
|
|
466
|
-
|
|
467
|
-
// Deduplicate by CVE ID
|
|
468
|
-
for (const termResults of vulnerabilityArrays) {
|
|
469
|
-
for (const vuln of termResults) {
|
|
470
|
-
if (!uniqueVulns.has(vuln.id)) {
|
|
471
|
-
uniqueVulns.set(vuln.id, vuln);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return Array.from(uniqueVulns.values());
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Search NVD for vulnerabilities
|
|
481
|
-
*
|
|
482
|
-
* @param terms - Search terms (CVE IDs, keywords, CPE names)
|
|
483
|
-
* @param options - Optional filters
|
|
484
|
-
* @returns Promise<NVDResult> containing vulnerability search results
|
|
485
|
-
*/
|
|
486
|
-
export async function searchNVD(
|
|
487
|
-
terms: string[],
|
|
488
|
-
options?: SearchOptions,
|
|
489
|
-
): Promise<NVDResult> {
|
|
490
|
-
const maxResults = Math.min(options?.maxResults ?? DEFAULT_MAX_RESULTS, MAX_RESULTS_PER_PAGE);
|
|
491
|
-
const vulnerabilities: Vulnerability[] = [];
|
|
492
|
-
let totalResults = 0;
|
|
493
|
-
let error: string | undefined = undefined;
|
|
494
|
-
|
|
495
|
-
try {
|
|
496
|
-
// Search each term and combine results
|
|
497
|
-
const searchPromises = terms.map(
|
|
498
|
-
(term: string) => searchSingleTerm(term, options, maxResults),
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
const allResults = await Promise.all(searchPromises);
|
|
502
|
-
const uniqueVulns = deduplicateVulnerabilities(allResults);
|
|
503
|
-
|
|
504
|
-
totalResults = uniqueVulns.length;
|
|
505
|
-
vulnerabilities.push(...uniqueVulns.slice(0, maxResults));
|
|
506
|
-
|
|
507
|
-
} catch (err) {
|
|
508
|
-
error = err instanceof Error ? err.message : String(err);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return {
|
|
512
|
-
count: totalResults,
|
|
513
|
-
vulnerabilities,
|
|
514
|
-
error,
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Get specific CVE by ID
|
|
520
|
-
*
|
|
521
|
-
* @param cveId - The CVE ID to fetch (e.g., "CVE-2023-1234")
|
|
522
|
-
* @returns Promise<Vulnerability | null> containing the vulnerability or null if not found
|
|
523
|
-
*/
|
|
524
|
-
export async function getCVEById(cveId: string): Promise<Vulnerability | null> {
|
|
525
|
-
try {
|
|
526
|
-
const results = await searchNVD([cveId], { maxResults: 1 });
|
|
527
|
-
return results.vulnerabilities[0] ?? null;
|
|
528
|
-
} catch (err) {
|
|
529
|
-
logger.error(`[NVD] Error fetching CVE ${cveId}:`, err);
|
|
530
|
-
return null;
|
|
531
|
-
}
|
|
532
|
-
}
|