Neuer Stand Assessor #1

Merged
bika merged 33 commits from AI-integration into experimental 2026-09-25 09:34:44 +00:00
5 changed files with 38 additions and 6 deletions
Showing only changes of commit c056c21006 - Show all commits

View File

@ -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.');

View File

@ -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 |',
'| --- | --- | --- |',

View File

@ -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>

View File

@ -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 {

View File

@ -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();
});
});