Skip to content

Commit f75611a

Browse files
Address scope-review findings from deep PR review
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a57cd8a commit f75611a

1 file changed

Lines changed: 50 additions & 4 deletions

File tree

  • x-pack/solutions/security/plugins/security_solution/server/threat_intel/adapters/kev

x-pack/solutions/security/plugins/security_solution/server/threat_intel/adapters/kev/kev_adapter.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,47 @@ interface KevEnvelope {
6262
vulnerabilities: KevVulnerability[];
6363
}
6464

65+
const isRecord = (value: unknown): value is Record<string, unknown> =>
66+
typeof value === 'object' && value !== null && !Array.isArray(value);
67+
68+
const readOptionalStringArray = (value: unknown): string[] | undefined => {
69+
if (value === undefined) return undefined;
70+
if (!Array.isArray(value)) return undefined;
71+
return value.every((entry) => typeof entry === 'string') ? value : undefined;
72+
};
73+
74+
const parseKevVulnerability = (value: unknown): KevVulnerability | undefined => {
75+
if (!isRecord(value)) return undefined;
76+
const cwes = readOptionalStringArray(value.cwes);
77+
if (value.cwes !== undefined && cwes === undefined) return undefined;
78+
const vuln = {
79+
cveID: value.cveID,
80+
vendorProject: value.vendorProject,
81+
product: value.product,
82+
vulnerabilityName: value.vulnerabilityName,
83+
dateAdded: value.dateAdded,
84+
shortDescription: value.shortDescription,
85+
requiredAction: value.requiredAction,
86+
dueDate: value.dueDate,
87+
knownRansomwareCampaignUse: value.knownRansomwareCampaignUse,
88+
notes: value.notes,
89+
cwes,
90+
};
91+
if (
92+
typeof vuln.cveID !== 'string' ||
93+
typeof vuln.vendorProject !== 'string' ||
94+
typeof vuln.product !== 'string' ||
95+
typeof vuln.vulnerabilityName !== 'string' ||
96+
typeof vuln.dateAdded !== 'string' ||
97+
typeof vuln.shortDescription !== 'string' ||
98+
typeof vuln.requiredAction !== 'string' ||
99+
typeof vuln.dueDate !== 'string'
100+
) {
101+
return undefined;
102+
}
103+
return vuln as KevVulnerability;
104+
};
105+
65106
const readFeedUrl = (source: SourceHit): string => {
66107
const url = source._source.config.url;
67108
return typeof url === 'string' && url.length > 0 ? url : KEV_FEED_URL;
@@ -159,7 +200,11 @@ export const kevAdapter: FetchAdapter = {
159200

160201
let envelope: KevEnvelope;
161202
try {
162-
envelope = JSON.parse(response.body) as KevEnvelope;
203+
const parsed = JSON.parse(response.body) as unknown;
204+
if (!isRecord(parsed) || !Array.isArray(parsed.vulnerabilities)) {
205+
throw new Error('KEV feed missing vulnerabilities array');
206+
}
207+
envelope = parsed as unknown as KevEnvelope;
163208
} catch (err) {
164209
throw new Error(`KEV feed response is not valid JSON: ${(err as Error).message}`);
165210
}
@@ -172,13 +217,14 @@ export const kevAdapter: FetchAdapter = {
172217
}
173218

174219
const reports: NormalizedReport[] = [];
175-
for (const vuln of vulnerabilities) {
176-
if (isCompleteKevEntry(vuln)) {
220+
for (const rawEntry of vulnerabilities) {
221+
const vuln = parseKevVulnerability(rawEntry);
222+
if (vuln && isCompleteKevEntry(vuln)) {
177223
reports.push(buildKevReport(vuln, provenanceUrl, ingestedAt, spaceId, source._id));
178224
} else {
179225
log.warn(
180226
`kev-adapter: skipping malformed entry (missing required fields): ${JSON.stringify(
181-
vuln
227+
rawEntry
182228
).slice(0, 200)}`
183229
);
184230
}

0 commit comments

Comments
 (0)