@lincoln504/pi-research 0.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.
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/config/searxng/default-settings.yml +189 -0
- package/config/searxng/limiter.toml +34 -0
- package/config/tooling/eslint.config.js +73 -0
- package/config/tooling/vitest.config.integration.ts +17 -0
- package/config/tooling/vitest.config.ts +40 -0
- package/config/tooling/vitest.config.unit.ts +12 -0
- package/docs/ARCHITECTURE.md +109 -0
- package/docs/assets/README-banner.jpg +0 -0
- package/package.json +92 -0
- package/scripts/setup.js +174 -0
- package/src/config.ts +206 -0
- package/src/constants.ts +148 -0
- package/src/healthcheck/index.ts +309 -0
- package/src/index.ts +71 -0
- package/src/infrastructure/searxng-lifecycle.ts +577 -0
- package/src/infrastructure/searxng-manager.ts +1173 -0
- package/src/infrastructure/state-manager.ts +918 -0
- package/src/logger.ts +246 -0
- package/src/orchestration/deep-research-orchestrator.ts +848 -0
- package/src/orchestration/deep-research-reducer.ts +129 -0
- package/src/orchestration/deep-research-types.ts +54 -0
- package/src/orchestration/id-utils.ts +63 -0
- package/src/orchestration/researcher.ts +104 -0
- package/src/orchestration/session-context.ts +43 -0
- package/src/prompts/research-tool-usage.md +45 -0
- package/src/prompts/researcher.md +166 -0
- package/src/prompts/system-coordinator.md +24 -0
- package/src/prompts/system-lead-evaluator.md +225 -0
- package/src/security/cisa-kev.ts +242 -0
- package/src/security/github-advisories.ts +607 -0
- package/src/security/index.ts +406 -0
- package/src/security/nvd.ts +532 -0
- package/src/security/osv.ts +487 -0
- package/src/security/types.ts +138 -0
- package/src/stackexchange/cache.ts +111 -0
- package/src/stackexchange/index.ts +352 -0
- package/src/stackexchange/output/compact.ts +64 -0
- package/src/stackexchange/output/index.ts +7 -0
- package/src/stackexchange/output/json.ts +21 -0
- package/src/stackexchange/output/table.ts +123 -0
- package/src/stackexchange/queries.ts +168 -0
- package/src/stackexchange/rest-client.ts +127 -0
- package/src/stackexchange/types.ts +129 -0
- package/src/tool.ts +430 -0
- package/src/tools/grep.ts +335 -0
- package/src/tools/index.ts +77 -0
- package/src/tools/scrape.ts +451 -0
- package/src/tools/search.ts +188 -0
- package/src/tools/security.ts +292 -0
- package/src/tools/stackexchange.ts +101 -0
- package/src/tui/research-panel.ts +576 -0
- package/src/tui/searxng-status.ts +80 -0
- package/src/types/extension-context.ts +60 -0
- package/src/types/llm.ts +111 -0
- package/src/utils/inject-date.ts +29 -0
- package/src/utils/input-validation.ts +103 -0
- package/src/utils/json-utils.ts +262 -0
- package/src/utils/make-resource-loader.ts +43 -0
- package/src/utils/research-error.ts +205 -0
- package/src/utils/research-export.ts +199 -0
- package/src/utils/searxng-config.ts +94 -0
- package/src/utils/session-state.ts +295 -0
- package/src/utils/shared-links.ts +123 -0
- package/src/utils/shutdown-manager.ts +54 -0
- package/src/utils/text-utils.ts +83 -0
- package/src/utils/tool-usage-tracker.ts +135 -0
- package/src/web-research/dependencies.ts +20 -0
- package/src/web-research/retry-utils.ts +232 -0
- package/src/web-research/scrapers.ts +935 -0
- package/src/web-research/search.ts +235 -0
- package/src/web-research/types.ts +47 -0
- package/src/web-research/utils.ts +158 -0
- package/tsconfig.json +40 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Lead Evaluator
|
|
2
|
+
|
|
3
|
+
You orchestrate the next phase of research.
|
|
4
|
+
|
|
5
|
+
## Your Context
|
|
6
|
+
|
|
7
|
+
- **ROOT QUERY**: {ROOT_QUERY}
|
|
8
|
+
- **ORIGINAL AGENDA**: {ORIGINAL_AGENDA}
|
|
9
|
+
- **Current round**: {ROUND_NUMBER}
|
|
10
|
+
- **Target rounds**: {MAX_ROUNDS}
|
|
11
|
+
|
|
12
|
+
You have reports from all researchers in this round. Some reports may contain errors (see Step 0).
|
|
13
|
+
|
|
14
|
+
### Transition from Researcher to Lead Evaluator
|
|
15
|
+
|
|
16
|
+
You were the final researcher to finish. **Your research task is CLOSED.** Your findings are recorded. Your responsibility is to evaluate ALL findings and determine the next step.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Decision Framework
|
|
21
|
+
|
|
22
|
+
### Step 0: Check for Researcher Errors
|
|
23
|
+
|
|
24
|
+
**Check if any researchers encountered errors:**
|
|
25
|
+
|
|
26
|
+
- Review all researcher reports for those starting with `ERROR:`
|
|
27
|
+
- Identify specific error messages and which researchers failed
|
|
28
|
+
- Determine if this represents a systemic issue (e.g., search service down, network problems)
|
|
29
|
+
|
|
30
|
+
**If ALL researchers failed with errors:**
|
|
31
|
+
|
|
32
|
+
You MUST quit and report the errors. Do not attempt synthesis or delegation.
|
|
33
|
+
|
|
34
|
+
Output the errors in this format:
|
|
35
|
+
|
|
36
|
+
```markdown
|
|
37
|
+
# Research Failed: Critical Errors
|
|
38
|
+
|
|
39
|
+
## Error Summary
|
|
40
|
+
Research cannot proceed. All researchers encountered errors.
|
|
41
|
+
|
|
42
|
+
## Individual Researcher Failures
|
|
43
|
+
|
|
44
|
+
### Researcher 1
|
|
45
|
+
[Report the exact error from Researcher 1's report]
|
|
46
|
+
|
|
47
|
+
### Researcher 2
|
|
48
|
+
[Report the exact error from Researcher 2's report]
|
|
49
|
+
|
|
50
|
+
[Continue for all failed researchers...]
|
|
51
|
+
|
|
52
|
+
## Systemic Issue Analysis
|
|
53
|
+
[Analyze if errors indicate a pattern: search service unavailable, network timeout, all engines blocked, etc.]
|
|
54
|
+
|
|
55
|
+
## Recommended Actions
|
|
56
|
+
[Suggest what user should do: check network, try again later, use different query, etc.]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**If SOME researchers succeeded but others failed:**
|
|
60
|
+
|
|
61
|
+
Proceed with synthesis but include a section acknowledging the failures:
|
|
62
|
+
- Note which researchers failed and why
|
|
63
|
+
- Explain how partial failures affected comprehensiveness
|
|
64
|
+
- Identify areas that may need future research
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### Step 1: Assess Information Completeness
|
|
69
|
+
|
|
70
|
+
Evaluate cumulative findings against ROOT QUERY and ORIGINAL AGENDA.
|
|
71
|
+
|
|
72
|
+
**For each agenda item**:
|
|
73
|
+
- Is it covered in depth with specific, actionable information?
|
|
74
|
+
- Are claims backed by multiple corroborating sources?
|
|
75
|
+
- Are there significant gaps or superficial coverage?
|
|
76
|
+
- Is information contradicted without resolution?
|
|
77
|
+
|
|
78
|
+
**SYNTHESIZE NOW if:**
|
|
79
|
+
- Every agenda item substantively addressed with depth
|
|
80
|
+
- Root query answerable with high confidence
|
|
81
|
+
- Findings corroborated by multiple sources
|
|
82
|
+
- Remaining gaps trivial, academic, or beyond reasonable scope
|
|
83
|
+
- Information high-quality, specific, actionable
|
|
84
|
+
|
|
85
|
+
**DELEGATE FURTHER RESEARCH if:**
|
|
86
|
+
- One or more agenda items missing or barely explored
|
|
87
|
+
- Critical aspects of root query unaddressed
|
|
88
|
+
- Findings superficial, generic, lack specificity
|
|
89
|
+
- Claims lack adequate evidence or corroboration
|
|
90
|
+
- High-value scrape candidates would fill gaps
|
|
91
|
+
- Contradictions exist requiring targeted resolution
|
|
92
|
+
|
|
93
|
+
### Step 2: Consider Depth Budget
|
|
94
|
+
|
|
95
|
+
**If current round < Target rounds**: You have capacity. Prioritize fulfilling all agenda items before synthesizing.
|
|
96
|
+
|
|
97
|
+
**If current round == Target rounds**: At intended depth. Synthesize if the agenda is substantially covered. Only delegate if CRITICAL GAPS remain unexplored — limit to 1-2 high-impact queries.
|
|
98
|
+
|
|
99
|
+
**If current round > Target rounds**: Extended territory (bonus rounds). Synthesize NOW unless a catastrophic coverage failure occurred. Do NOT delegate for minor gaps.
|
|
100
|
+
|
|
101
|
+
### Step 3: Make Strategic Decision
|
|
102
|
+
|
|
103
|
+
**If synthesizing**: Provide comprehensive synthesis. Be exhaustive. Integrate findings from all researchers. Resolve conflicts where possible. Acknowledge limitations.
|
|
104
|
+
|
|
105
|
+
**If delegating**: Formulate targeted queries for remaining gaps. Focus on unaddressed agenda items. Limit to 1-2 high-impact queries.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Researcher Scrape Protocol
|
|
110
|
+
|
|
111
|
+
Each researcher followed a context-aware up-to-4-call scrape protocol:
|
|
112
|
+
1. **Handshake**: Check for already-scraped links (no network)
|
|
113
|
+
2. **Batch 1**: Up to 3 URLs — primary broad scraping
|
|
114
|
+
3. **Batch 2**: Up to 2 URLs — targeted follow-up (auto-deduplicated)
|
|
115
|
+
4. **Batch 3**: Up to 3 URLs — deep-dive
|
|
116
|
+
|
|
117
|
+
Batches automatically skipped when researcher's context exceeds 55%. Fewer sources than maximum is expected and not a failure.
|
|
118
|
+
|
|
119
|
+
**When evaluating findings**:
|
|
120
|
+
- **Corroborating**: Multiple researchers found similar information — strong confidence
|
|
121
|
+
- **Complementary**: Different researchers covered different aspects — integrate both
|
|
122
|
+
- **Conflicting**: Sources disagree — identify conflict, explain evidence, suggest resolution or acknowledge uncertainty
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Synthesis Output Format
|
|
127
|
+
|
|
128
|
+
Provide **full breadth, depth, nuance** of information from all sources. This is the final result — be thorough.
|
|
129
|
+
|
|
130
|
+
```markdown
|
|
131
|
+
# Research Synthesis: [Comprehensive Topic Title]
|
|
132
|
+
|
|
133
|
+
## Executive Summary
|
|
134
|
+
[High-level analytical overview. Identify most critical insights, patterns, conclusions. Comprehensive summary that stands alone.]
|
|
135
|
+
|
|
136
|
+
## Detailed Key Findings
|
|
137
|
+
|
|
138
|
+
### [Major Theme/Category 1]
|
|
139
|
+
- **In-Depth Analysis**: [Exhaustive explanation. Incorporate multiple sources and perspectives. Cite researchers where relevant. Provide context.]
|
|
140
|
+
- **Sub-Finding 1**: [Specific detail with citations]
|
|
141
|
+
- **Sub-Finding 2**: [Specific detail with citations]
|
|
142
|
+
- **Context & Implications**: [What do findings mean? Why significant? How relate to broader topic?]
|
|
143
|
+
|
|
144
|
+
### [Major Theme/Category 2]
|
|
145
|
+
- **In-Depth Analysis**: [Continue for all major themes...]
|
|
146
|
+
|
|
147
|
+
## Critical Nuance and Conflicting Perspectives
|
|
148
|
+
|
|
149
|
+
[Identify areas where:
|
|
150
|
+
- Sources provided different or contradictory information
|
|
151
|
+
- Topic inherently complex or ambiguous
|
|
152
|
+
- Findings require interpretation or multiple perspectives
|
|
153
|
+
- Uncertainty or lack of consensus
|
|
154
|
+
|
|
155
|
+
For each: describe conflicting information, explain evidence, provide synthesis or acknowledge ambiguity, suggest resolution or additional research needs]
|
|
156
|
+
|
|
157
|
+
## Coverage & Agenda Assessment
|
|
158
|
+
|
|
159
|
+
[Address specifically:
|
|
160
|
+
- Which initial agenda items fully resolved
|
|
161
|
+
- Which items partially explored or unaddressed
|
|
162
|
+
- Why certain areas incomplete (sources unavailable, complexity, etc.)
|
|
163
|
+
- Overall comprehensiveness relative to original query]
|
|
164
|
+
|
|
165
|
+
## Final Conclusions & Strategic Recommendations
|
|
166
|
+
|
|
167
|
+
[Synthesize key takeaways:
|
|
168
|
+
- Most important conclusions
|
|
169
|
+
- Practical implications
|
|
170
|
+
- Actions/decisions informed by findings
|
|
171
|
+
- Research limitations
|
|
172
|
+
- Most valuable additional information if further research conducted]
|
|
173
|
+
|
|
174
|
+
## CITED LINKS
|
|
175
|
+
|
|
176
|
+
[List every URL cited in synthesis, deduplicated. For each:]
|
|
177
|
+
* [URL] — Brief description of contribution and significance
|
|
178
|
+
|
|
179
|
+
## Research Limitations
|
|
180
|
+
|
|
181
|
+
[Only include if there are material gaps that significantly constrain the findings — omit if coverage is adequate. Do NOT list scrape candidates as raw URLs. Instead describe what remains unknown and why it matters:]
|
|
182
|
+
- [Topic/area] — What is unknown and its relevance to the root query
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Delegation Output Format
|
|
188
|
+
|
|
189
|
+
Provide a JSON object with `action` and `queries` fields:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{"action": "delegate", "queries": ["Targeted research question addressing critical gap", "Another specific research direction for remaining gaps"]}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**CRITICAL REQUIREMENTS FOR DELEGATION**:
|
|
196
|
+
- `action` must be exactly the string `"delegate"` (not `delegate` without quotes)
|
|
197
|
+
- `queries` must be an array of strings (not objects, not numbers)
|
|
198
|
+
- Each query string must be non-empty after trimming
|
|
199
|
+
- Do not include nested objects like `{"query": "text"}` — use plain strings only
|
|
200
|
+
|
|
201
|
+
**Delegation Guidance**:
|
|
202
|
+
- Each query addresses specific, high-impact gap
|
|
203
|
+
- Queries distinct and complementary, not overlapping
|
|
204
|
+
- **For unfulfilled agenda items: copy the exact query string from the "Unfulfilled Agenda Items" list** — do not rephrase. This ensures the system can track coverage.
|
|
205
|
+
- For new gaps not in the original agenda, formulate targeted queries
|
|
206
|
+
- Consider what information would most transform understanding
|
|
207
|
+
- Avoid duplicating already-researched topics
|
|
208
|
+
- Ensure queries specific enough to yield actionable results
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Critical Rules
|
|
213
|
+
|
|
214
|
+
- **CHECK FOR ERRORS FIRST**: If all researchers failed with `ERROR:` reports, output error report format.
|
|
215
|
+
- **ONE DECISION ONLY**: Either synthesize OR delegate. Do not provide both.
|
|
216
|
+
- **OUTPUT FORMAT DETERMINES ACTION**:
|
|
217
|
+
- JSON object `{"action": "delegate", "queries": [...]}` → Delegating additional research
|
|
218
|
+
- Any other output (markdown, prose) → Treated as final synthesis
|
|
219
|
+
- **IMPORTANT**: The JSON must be valid, properly formatted, with string values in the `queries` array
|
|
220
|
+
- **NO DECISION MEMOS**: Never output text explaining decision (e.g., "# Decision: NO"). Provide ONLY synthesis, error report, or JSON array.
|
|
221
|
+
- **BE EXHAUSTIVE IN SYNTHESIS**: Provide full depth and breadth. Do not truncate excessively.
|
|
222
|
+
- **NO SCRAPE CANDIDATE LISTS**: Do not enumerate URLs from researcher SCRAPE CANDIDATES sections — these are internal scaffolding. Mention material gaps in prose under Research Limitations only if they affect the findings.
|
|
223
|
+
- **BE TARGETED IN DELEGATION**: Provide specific, high-impact queries addressing genuine gaps.
|
|
224
|
+
- **USE PLAIN STRINGS IN QUERIES**: Do not use objects like `{"query": "text"}` in the queries array — use plain strings only.
|
|
225
|
+
- **SUBMIT AND STOP**: After providing decision, stop. System handles next steps.
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CISA Known Exploited Vulnerabilities (KEV) Catalog Client
|
|
3
|
+
*
|
|
4
|
+
* CISA KEV Catalog - actively exploited vulnerabilities
|
|
5
|
+
* JSON feed: https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json
|
|
6
|
+
* Free public feed, no authentication required
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Vulnerability, CisaKevResult } from './types.ts';
|
|
10
|
+
import { createTimeoutSignal } from '../web-research/retry-utils.ts';
|
|
11
|
+
|
|
12
|
+
const CISA_KEV_URL = 'https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json';
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Type Definitions for CISA KEV API
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* CISA KEV Catalog Item structure from the official API
|
|
20
|
+
*/
|
|
21
|
+
interface CisaKevItem {
|
|
22
|
+
readonly cveID?: string;
|
|
23
|
+
readonly cve_id?: string;
|
|
24
|
+
readonly id?: string;
|
|
25
|
+
readonly vendorProject?: string;
|
|
26
|
+
readonly vendor?: string;
|
|
27
|
+
readonly product?: string;
|
|
28
|
+
readonly vulnerabilityName?: string;
|
|
29
|
+
readonly shortDescription?: string;
|
|
30
|
+
readonly description?: string;
|
|
31
|
+
readonly dateAdded?: string;
|
|
32
|
+
readonly addedDate?: string;
|
|
33
|
+
readonly dueDate?: string;
|
|
34
|
+
readonly requiredAction?: string;
|
|
35
|
+
readonly action?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* CISA KEV API Response structure
|
|
40
|
+
*/
|
|
41
|
+
interface CisaKevResponse {
|
|
42
|
+
readonly vulnerabilities?: readonly CisaKevItem[];
|
|
43
|
+
readonly title?: string;
|
|
44
|
+
readonly catalogVersion?: string;
|
|
45
|
+
readonly dateReleased?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Type guard to check if value is a valid CisaKevItem
|
|
50
|
+
*/
|
|
51
|
+
function isCisaKevItem(value: unknown): value is CisaKevItem {
|
|
52
|
+
if (value === null || typeof value !== 'object') {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const item = value as Record<string, unknown>;
|
|
57
|
+
return (
|
|
58
|
+
typeof item['cveID'] === 'string' ||
|
|
59
|
+
typeof item['cve_id'] === 'string' ||
|
|
60
|
+
typeof item['id'] === 'string'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Type guard to check if value is an array of CisaKevItem
|
|
66
|
+
*/
|
|
67
|
+
function isCisaKevItemArray(value: unknown): value is readonly CisaKevItem[] {
|
|
68
|
+
return Array.isArray(value) && value.every(isCisaKevItem);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Type guard to check if value is a CisaKevResponse
|
|
73
|
+
*/
|
|
74
|
+
function isCisaKevResponse(value: unknown): value is CisaKevResponse {
|
|
75
|
+
if (value === null || typeof value !== 'object') {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const response = value as Record<string, unknown>;
|
|
80
|
+
return (
|
|
81
|
+
typeof response === 'object' &&
|
|
82
|
+
(response['vulnerabilities'] === undefined || Array.isArray(response['vulnerabilities']))
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Extract CISA KEV items from various possible response formats
|
|
88
|
+
*/
|
|
89
|
+
function extractCisaKevItems(data: unknown): readonly CisaKevItem[] {
|
|
90
|
+
if (isCisaKevItemArray(data)) {
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (isCisaKevResponse(data) && data.vulnerabilities) {
|
|
95
|
+
return data.vulnerabilities.filter(isCisaKevItem);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// Main API Functions
|
|
103
|
+
// ============================================================================
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Fetch and search CISA KEV catalog
|
|
107
|
+
*
|
|
108
|
+
* @param terms - CVE IDs to search for (optional)
|
|
109
|
+
* @param options - Optional filters
|
|
110
|
+
* @returns Promise resolving to CISA KEV search results
|
|
111
|
+
*/
|
|
112
|
+
export async function searchCisaKev(
|
|
113
|
+
terms: string[] = [],
|
|
114
|
+
options?: {
|
|
115
|
+
readonly vendor?: string;
|
|
116
|
+
readonly product?: string;
|
|
117
|
+
readonly maxResults?: number;
|
|
118
|
+
},
|
|
119
|
+
): Promise<CisaKevResult> {
|
|
120
|
+
const maxResults = options?.maxResults ?? 100;
|
|
121
|
+
const vulnerabilities: Vulnerability[] = [];
|
|
122
|
+
let error: string | undefined = undefined;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const response: Response = await fetch(CISA_KEV_URL, {
|
|
126
|
+
headers: {
|
|
127
|
+
'User-Agent': 'pi-research/2.0',
|
|
128
|
+
'Accept': 'application/json',
|
|
129
|
+
},
|
|
130
|
+
signal: createTimeoutSignal(30000), // 30s timeout
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const data: unknown = await response.json();
|
|
138
|
+
|
|
139
|
+
// CISA KEV format is an array of vulnerability objects or nested in a response object
|
|
140
|
+
const cisaData = extractCisaKevItems(data);
|
|
141
|
+
|
|
142
|
+
// Map CISA format to our Vulnerability interface
|
|
143
|
+
for (const item of cisaData) {
|
|
144
|
+
const vuln: Vulnerability = mapCisaItemToVulnerability(item);
|
|
145
|
+
|
|
146
|
+
// Filter by search terms if provided
|
|
147
|
+
if (terms.length > 0 && !terms.some((term: string): boolean =>
|
|
148
|
+
vuln.id.toLowerCase().includes(term.toLowerCase()) ||
|
|
149
|
+
vuln.description.toLowerCase().includes(term.toLowerCase()),
|
|
150
|
+
)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Filter by vendor/product if provided
|
|
155
|
+
if (options?.vendor !== undefined) {
|
|
156
|
+
const vendorLower = vuln.vendor?.toLowerCase() ?? '';
|
|
157
|
+
if (!vendorLower.includes(options.vendor.toLowerCase())) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (options?.product !== undefined) {
|
|
163
|
+
const productLower = vuln.product?.toLowerCase() ?? '';
|
|
164
|
+
if (!productLower.includes(options.product.toLowerCase())) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
vulnerabilities.push(vuln);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Sort by due date (most urgent first)
|
|
173
|
+
vulnerabilities.sort((a: Vulnerability, b: Vulnerability): number => {
|
|
174
|
+
if (a.dueDate === undefined) {
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
177
|
+
if (b.dueDate === undefined) {
|
|
178
|
+
return -1;
|
|
179
|
+
}
|
|
180
|
+
return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
} catch (err: unknown) {
|
|
184
|
+
error = err instanceof Error ? err.message : String(err);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
count: vulnerabilities.length,
|
|
189
|
+
vulnerabilities: vulnerabilities.slice(0, maxResults),
|
|
190
|
+
error,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Safely extract a string property from an unknown object
|
|
196
|
+
*/
|
|
197
|
+
function safeString(value: unknown, defaultValue: string = ''): string {
|
|
198
|
+
if (typeof value === 'string') {
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
return defaultValue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Map CISA KEV item to Vulnerability interface
|
|
206
|
+
*
|
|
207
|
+
* @param item - CISA KEV item from API
|
|
208
|
+
* @returns Mapped Vulnerability object
|
|
209
|
+
*/
|
|
210
|
+
function mapCisaItemToVulnerability(item: CisaKevItem): Vulnerability {
|
|
211
|
+
// CISA KEV format (example based on scraped structure)
|
|
212
|
+
const cveId: string = [item.cveID, item.cve_id, item.id].map(v => safeString(v)).find(s => s.length > 0) ?? '';
|
|
213
|
+
const vendor: string = [item.vendorProject, item.vendor].map(v => safeString(v)).find(s => s.length > 0) ?? '';
|
|
214
|
+
const product: string = [item.product, item.vulnerabilityName].map(v => safeString(v)).find(s => s.length > 0) ?? '';
|
|
215
|
+
const description: string = [item.shortDescription, item.description].map(v => safeString(v)).find(s => s.length > 0) ?? '';
|
|
216
|
+
const dateAdded: string = [item.dateAdded, item.addedDate].map(v => safeString(v)).find(s => s.length > 0) ?? '';
|
|
217
|
+
const dueDate: string = safeString(item.dueDate);
|
|
218
|
+
const requiredAction: string = [item.requiredAction, item.action].map(v => safeString(v)).find(s => s.length > 0) ?? '';
|
|
219
|
+
|
|
220
|
+
const affectedProducts: string[] = [vendor, product].filter(
|
|
221
|
+
(value: string): boolean => value.length > 0,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
id: cveId,
|
|
226
|
+
source: 'cisa_kev',
|
|
227
|
+
severity: 'CRITICAL', // All KEV entries are critical priority
|
|
228
|
+
description,
|
|
229
|
+
published: dateAdded.length > 0 ? dateAdded : undefined,
|
|
230
|
+
modified: dateAdded.length > 0 ? dateAdded : undefined,
|
|
231
|
+
cvssScore: undefined, // CISA doesn't include CVSS score
|
|
232
|
+
cwes: [],
|
|
233
|
+
references: [],
|
|
234
|
+
affectedProducts,
|
|
235
|
+
fixes: [],
|
|
236
|
+
vendor: vendor.length > 0 ? vendor : undefined,
|
|
237
|
+
product: product.length > 0 ? product : undefined,
|
|
238
|
+
knownExploited: true,
|
|
239
|
+
dueDate: dueDate.length > 0 ? dueDate : undefined,
|
|
240
|
+
requiredAction: requiredAction.length > 0 ? requiredAction : undefined,
|
|
241
|
+
};
|
|
242
|
+
}
|