Neuer Stand Assessor #1
|
|
@ -83,6 +83,12 @@ export function cveIdFromRuleId(value: string | undefined): string | null {
|
|||
return value ? normalizeCveId(value) : null;
|
||||
}
|
||||
|
||||
/** Derives the public MITRE CWE detail page for a `CWE-<number>` weakness id, or null for non-CWE ids. */
|
||||
export function cweDetailUrl(weaknessId: string): string | null {
|
||||
const match = /^CWE-(\d+)$/i.exec(weaknessId.trim());
|
||||
return match ? `https://cwe.mitre.org/data/definitions/${match[1]}.html` : null;
|
||||
}
|
||||
|
||||
export function cveEnrichmentValidationErrors(record: CveEnrichmentRecord): string[] {
|
||||
const errors: string[] = [];
|
||||
if (normalizeCveId(record.cveId) !== record.cveId) errors.push('CVE ID must be normalized.');
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { makeSheet } from '../../importers/sheet-factory';
|
|||
import { stableSheetId } from '../../shared/identifiers';
|
||||
import { groupProjectImportFiles } from './datahub-import-groups';
|
||||
import type { CveAffectedConfiguration, CveConfigurationMatch, CveEnrichmentRecord } from '../../domain/cve-enrichment';
|
||||
import { cveIdFromRuleId } from '../../domain/cve-enrichment';
|
||||
import { cveIdFromRuleId, cweDetailUrl } from '../../domain/cve-enrichment';
|
||||
import { canonicalSecurityScores } from '../../scoring/canonical-security-score';
|
||||
import { defaultSecurityScoringPolicy } from '../../domain/security-scoring';
|
||||
|
||||
|
|
@ -111,9 +111,11 @@ export function cveFieldsForDisplay(record: CveEnrichmentRecord): OriginalField[
|
|||
'| --- | --- | ---: | --- | --- |',
|
||||
...record.metrics.map(metric => `| ${markdownCell(metric.version)} | ${markdownCell(metric.source)} | ${metric.baseScore} | ${markdownCell(metric.baseSeverity ?? '')} | ${markdownCell(metric.vector)} |`)
|
||||
].join('\n') : '');
|
||||
add('nvd-weaknesses', 'NVD Weaknesses', record.weaknesses.map(item =>
|
||||
`- ${item.id}${item.description ? ` — ${item.description}` : ''} (${item.source})`
|
||||
).join('\n'));
|
||||
add('nvd-weaknesses', 'NVD Weaknesses', record.weaknesses.map(item => {
|
||||
const url = cweDetailUrl(item.id);
|
||||
const label = url ? `[${markdownCell(item.id)}](${url})` : markdownCell(item.id);
|
||||
return `- ${label}${item.description ? ` — ${markdownCell(item.description)}` : ''} (${markdownCell(item.source)})`;
|
||||
}).join('\n'));
|
||||
add('nvd-references', 'NVD References', record.references.length ? [
|
||||
'| Reference | Source | Tags |',
|
||||
'| --- | --- | --- |',
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { browserDownloadAdapter } from '../../platform/browser/browser-download-
|
|||
import { downloadText } from '../../exports/text-download';
|
||||
import { buildUnifiedResultJsonl, queryAllUnifiedResults, unifiedResultJsonlFileName } from '../../exports/unified-result-jsonl';
|
||||
import { hasSelectedTimeslotScope } from '../../app/work-scope';
|
||||
import { cweDetailUrl } from '../../domain/cve-enrichment';
|
||||
|
||||
const assessorApi = new AssessorApiClient();
|
||||
const PAGE_SIZE = 50;
|
||||
|
|
@ -273,7 +274,13 @@ function ResultRow({ item, expanded, onToggle, onUpdated }: {
|
|||
<li>Metrics: {item.cveEnrichment.metrics.map(metric =>
|
||||
`CVSS ${metric.version} ${metric.baseScore}${metric.baseSeverity ? ` ${metric.baseSeverity}` : ''} (${metric.source})`
|
||||
).join(' · ') || 'Not supplied'}</li>
|
||||
<li>Weaknesses: {item.cveEnrichment.weaknesses.map(weakness => weakness.id).join(', ') || 'Not supplied'}</li>
|
||||
<li>Weaknesses: {item.cveEnrichment.weaknesses.length ? item.cveEnrichment.weaknesses.map((weakness, index) => {
|
||||
const url = cweDetailUrl(weakness.id);
|
||||
return <span key={`${weakness.id}-${index}`}>
|
||||
{index > 0 ? ', ' : ''}
|
||||
{url ? <a href={url} target="_blank" rel="noreferrer">{weakness.id}</a> : weakness.id}
|
||||
</span>;
|
||||
}) : 'Not supplied'}</li>
|
||||
</ul>
|
||||
{item.cveEnrichment.references.length > 0 && <details>
|
||||
<summary>Advisories, solutions, and tools ({item.cveEnrichment.references.length.toLocaleString('en-US')})</summary>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ function scoreObservation(item: EnvironmentObservation): EnvironmentObservation
|
|||
|
||||
export function securityToolId(value: string): string {
|
||||
const normalized = value.toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||
return ({ purpleknight: 'purple-knight', pingcastle: 'pingcastle', ciscat: 'cis-cat', ciscatpro: 'cis-cat', maester: 'maester', gpos: 'group-policy', gpoanalysis: 'group-policy', grouppolicy: 'group-policy' } as Record<string, string>)[normalized] ?? value.toLowerCase();
|
||||
return ({ purpleknight: 'purple-knight', pingcastle: 'pingcastle', ciscat: 'cis-cat', ciscatpro: 'cis-cat', maester: 'maester', gpos: 'group-policy', gpoanalysis: 'group-policy', grouppolicy: 'group-policy', defenderforiotvulnerabilities: 'defender-for-iot', microsoftdefenderforiot: 'defender-for-iot' } as Record<string, string>)[normalized] ?? value.toLowerCase();
|
||||
}
|
||||
|
||||
function valueOf(item: EnvironmentObservation): JsonValue {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { cweDetailUrl } from '../../src/domain/cve-enrichment';
|
||||
|
||||
describe('CWE detail URL derivation', () => {
|
||||
it('links a CWE-<number> id to its MITRE definition page', () => {
|
||||
expect(cweDetailUrl('CWE-119')).toBe('https://cwe.mitre.org/data/definitions/119.html');
|
||||
expect(cweDetailUrl('cwe-79')).toBe('https://cwe.mitre.org/data/definitions/79.html');
|
||||
expect(cweDetailUrl(' CWE-89 ')).toBe('https://cwe.mitre.org/data/definitions/89.html');
|
||||
});
|
||||
|
||||
it('returns null for non-CWE weakness ids', () => {
|
||||
expect(cweDetailUrl('NVD-CWE-noinfo')).toBeNull();
|
||||
expect(cweDetailUrl('NVD-CWE-Other')).toBeNull();
|
||||
expect(cweDetailUrl('CWE-119 extra')).toBeNull();
|
||||
expect(cweDetailUrl('')).toBeNull();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue