feat(defender-for-iot): add vulnerability finding result type and backfill logic
This commit is contained in:
parent
24893f965a
commit
524a3da3b7
|
|
@ -95,3 +95,17 @@ Haupttabelle. Der unveränderte externe Vertragstext liegt unter
|
|||
und ihre Begründung stehen in
|
||||
[`RESULT_CONTRACT.md`](RESULT_CONTRACT.md#abweichungen-der-assessor-realisierung).
|
||||
Dieses Dokument bleibt der fachlich verbindliche, implementierte Stand.
|
||||
|
||||
### ASSESSOR-Erweiterung: Vulnerability Finding
|
||||
|
||||
Seit 2026-09-11 erweitert ASSESSOR den geschlossenen externen
|
||||
`result_type`-Katalog additiv um `vulnerability_finding`. Der Typ bezeichnet
|
||||
einen Scannerbefund zu einer konkreten Schwachstelle, ohne ihn als bestaetigten
|
||||
Indicator of Exposure zu klassifizieren. Microsoft-Defender-for-IoT-CVEs werden
|
||||
als `vulnerability_finding` mit `outcome = warning`, `is_ioe = false` und damit
|
||||
`is_deviation = true` projiziert. Severity bleibt eine unabhaengige Achse.
|
||||
|
||||
`environment-observation` bleibt dagegen ein `source_kind`, kein weiterer
|
||||
`result_type`; diese Kontext- und Inventardaten werden als `informational`
|
||||
projiziert. Der unveraenderte externe Referenzvertrag 1.0.0 wird durch diese
|
||||
ASSESSOR-spezifische Erweiterung nicht rueckwirkend umgeschrieben.
|
||||
|
|
|
|||
|
|
@ -2580,6 +2580,7 @@ Verifiziert 2026-09-09 (Phase 7): Bereichswechsel beschleunigt und Performance-V
|
|||
Verifiziert 2026-09-08 (Phase 7/13B): GPO-Quellenkorrektur fuer Work und Client Info. Aktuelle Evaluationen aus dem vorhandenen autoritativen Readmodell statt ergebnisabhaengiger Backupgruppierung; Import- und Auswertungszeit getrennt, Source-Scan-Provenienz auch auf GPO-only-Blaettern. 1.014 Tests bestanden, acht optionale PostgreSQL-Tests uebersprungen; Local-only-/Designpruefung und beide Builds erfolgreich. Dummy lesend bestaetigt: eine statt drei GPO-Quellenoptionen, 3.805 aktuelle Zeilen, elf historische Evaluationen unveraendert. Keine Persistenzmutation oder Neuanalyse.
|
||||
|
||||
| Datum | Änderung |
|
||||
| 2026-09-11 | Phasen 2/6/7/11 um `vulnerability_finding` erweitert. Das Defender-Profil 1.1.0 setzt fuer statuslose CVE-Findings `outcome=warning`; Work und Unified Result projizieren `result_type=vulnerability_finding`, `is_ioe=false` und `deviation=true`. Migration 023 aktualisiert bestehende abgeleitete Result-Type-/Outcome-Spalten, ohne Originalfelder, Occurrence-JSON oder Profilprovenienz zu veraendern. Profil 1.0.0 benoetigt fuer einen vollstaendig aktuellen Datensatz Reimport/Reprocessing. |
|
||||
| 2026-09-10 | Separate globale CVE-Anreicherung nach dem Import umgesetzt: Das tenantunabhaengige CVE-Modell und Migration 021 speichern normalisierte NVD-Beschreibung, CVSS-Metriken, CWE, Referenzen, betroffene Konfigurationen, Zeitpunkte und Provenienz einmal je CVE. Local- und PostgreSQL-Repository, Assessment-/Export- und Unified-Result-Projektionen verwenden denselben Bestand fuer passende Defender-Vorkommen. Same-Origin-API und Results-Browser bieten manuelle Enrich-/Refresh-Aktionen; der NVD-Client verwendet den festen CVE-2.0-Endpunkt mit Timeout und Antwortgrenze. Keine Scanner-Originalfelder werden veraendert. |
|
||||
| 2026-09-10 | Defender-Betroffenheitszahl von der Rolle `score` auf `asset` umgestellt: In ASSESSOR ist `score` die Risikomagnitude, waehrend die Anzahl betroffener Devices eine Verbreitungskennzahl ist. Das aggregierte CVE traegt die Anzahl daher als `affectedAsset`; der Results-Browser zeigt sie unter der bestehenden Betroffenheitszeile, und `score` bleibt fuer echte Risikoscores frei. Kein Vergleichsschluessel betroffen (weiter `rule-id`). |
|
||||
| 2026-09-10 | Results-Browser zeigt fuer Defender-CVEs die aggregierte Betroffenheit als `Affected Devices`-Anzahl statt eines leeren `Affected Object`. Andere Werkzeuge behalten die `Affected Object`-Zeile unveraendert. |
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import nativePolicyTargets from './migrations/019_native_policy_targets.sql?raw'
|
|||
import baselineDrivenPolicyEvaluation from './migrations/020_baseline_driven_policy_evaluation.sql?raw';
|
||||
import cveEnrichment from './migrations/021_cve_enrichment.sql?raw';
|
||||
import vulnerabilityFindingResultType from './migrations/022_vulnerability_finding_result_type.sql?raw';
|
||||
import defenderVulnerabilityBackfill from './migrations/023_defender_vulnerability_backfill.sql?raw';
|
||||
|
||||
export interface DatabaseMigration {
|
||||
version: string;
|
||||
|
|
@ -49,5 +50,6 @@ export const databaseMigrations: readonly DatabaseMigration[] = [
|
|||
{ version: '019', name: 'native-policy-targets', sql: nativePolicyTargets },
|
||||
{ version: '020', name: 'baseline-driven-policy-evaluation', sql: baselineDrivenPolicyEvaluation },
|
||||
{ version: '021', name: 'cve-enrichment', sql: cveEnrichment },
|
||||
{ version: '022', name: 'vulnerability-finding-result-type', sql: vulnerabilityFindingResultType }
|
||||
{ version: '022', name: 'vulnerability-finding-result-type', sql: vulnerabilityFindingResultType },
|
||||
{ version: '023', name: 'defender-vulnerability-backfill', sql: defenderVulnerabilityBackfill }
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
-- Backfill ausschliesslich der abgeleiteten, indizierten Read-Model-Spalten
|
||||
-- fuer bereits importierte Defender-CVEs. Originalfelder, Occurrence-JSON und
|
||||
-- Importprovenienz bleiben unveraendert.
|
||||
|
||||
UPDATE assessor_finding_occurrences
|
||||
SET
|
||||
result_type = 'vulnerability_finding',
|
||||
outcome_normalized = CASE
|
||||
WHEN outcome_normalized = 'unknown' THEN 'warning'
|
||||
ELSE outcome_normalized
|
||||
END
|
||||
WHERE lower(btrim(tool_id)) = 'defender-for-iot-vulnerabilities';
|
||||
|
|
@ -8,8 +8,9 @@ export type JsonPrimitive = string | number | boolean | null;
|
|||
export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue };
|
||||
|
||||
/**
|
||||
* result_type aus RESULT_CONTRACT 1.0.0 (docs/RESULT_CONTRACT.md). Additive
|
||||
* Facette, ausschließlich über src/domain/result-contract-mapping.ts abgeleitet.
|
||||
* result_type aus RESULT_CONTRACT 1.0.0 plus dokumentierte ASSESSOR-Erweiterungen
|
||||
* (docs/CANONICAL_MODEL.md). Additive Facette, ausschließlich über
|
||||
* src/domain/result-contract-mapping.ts abgeleitet.
|
||||
*/
|
||||
export type CanonicalResultType = ResultContractResultType;
|
||||
|
||||
|
|
|
|||
|
|
@ -359,10 +359,19 @@ function mappingConfidence(occurrence: FindingOccurrence): number {
|
|||
export function createScannerUnifiedResult(source: ScannerUnifiedResultSource): UnifiedResultProjection {
|
||||
const review = matchingReview(source);
|
||||
const dimensions = reviewDimensions(review);
|
||||
const outcomeNormalized = source.occurrence.outcome?.normalizedValue
|
||||
?? outcomeFromLegacyStatus(source.occurrence.status.normalizedValue ?? 'unknown');
|
||||
const resultType = source.occurrence.resultType
|
||||
?? deriveResultType(source.occurrence.sourceIdentity.tool.id);
|
||||
const recordedOutcome = source.occurrence.outcome?.normalizedValue
|
||||
?? outcomeFromLegacyStatus(source.occurrence.status.normalizedValue ?? 'unknown');
|
||||
const originalOutcome = source.occurrence.outcome?.originalValue ?? source.occurrence.status.originalValue;
|
||||
const hasOriginalOutcome = typeof originalOutcome === 'string'
|
||||
? Boolean(originalOutcome.trim())
|
||||
: originalOutcome !== null && originalOutcome !== undefined;
|
||||
const outcomeNormalized = resultType === 'vulnerability_finding'
|
||||
&& recordedOutcome === 'unknown'
|
||||
&& !hasOriginalOutcome
|
||||
? 'warning'
|
||||
: recordedOutcome;
|
||||
const exposureState = source.occurrence.exposureState
|
||||
?? deriveExposureState(resultType, outcomeNormalized);
|
||||
const isIoe = source.occurrence.isIoe
|
||||
|
|
|
|||
|
|
@ -178,6 +178,23 @@ describe('Microsoft Defender for IoT vulnerability model', () => {
|
|||
isIoe: false,
|
||||
isDeviation: true
|
||||
});
|
||||
expect(createScannerUnifiedResult({
|
||||
project: canonical.project,
|
||||
assessment: canonical.assessment,
|
||||
scanRun: canonical.scanRuns[0],
|
||||
importFile: canonical.importFiles[0],
|
||||
definition: canonical.findingDefinitions[0],
|
||||
occurrence: {
|
||||
...canonical.findingOccurrences[0],
|
||||
outcome: { ...canonical.findingOccurrences[0].outcome!, normalizedValue: 'unknown' }
|
||||
},
|
||||
reviews: canonical.reviews
|
||||
})).toMatchObject({
|
||||
resultType: 'vulnerability_finding',
|
||||
outcomeNormalized: 'warning',
|
||||
isIoe: false,
|
||||
isDeviation: true
|
||||
});
|
||||
// The aggregated CVE carries its affected-device count as the affected asset (two devices here); the device list lives in the evidence table.
|
||||
expect(canonical.findingOccurrences[0].affectedAsset?.normalizedValue).toBe('2');
|
||||
expect(canonical.findingOccurrences[0].score).toBeUndefined();
|
||||
|
|
|
|||
|
|
@ -12,19 +12,40 @@ const migrationPath = resolve(
|
|||
'migrations',
|
||||
'022_vulnerability_finding_result_type.sql'
|
||||
);
|
||||
const backfillPath = resolve(
|
||||
import.meta.dirname,
|
||||
'..',
|
||||
'..',
|
||||
'server',
|
||||
'postgres',
|
||||
'migrations',
|
||||
'023_defender_vulnerability_backfill.sql'
|
||||
);
|
||||
|
||||
describe('PostgreSQL vulnerability finding result type migration', () => {
|
||||
it('registers migration 022 after CVE enrichment', () => {
|
||||
expect(databaseMigrations.at(-1)).toMatchObject({
|
||||
it('registers the immutable catalog extension before the Defender backfill', () => {
|
||||
expect(databaseMigrations.at(-2)).toMatchObject({
|
||||
version: '022',
|
||||
name: 'vulnerability-finding-result-type'
|
||||
});
|
||||
expect(databaseMigrations.at(-1)).toMatchObject({
|
||||
version: '023',
|
||||
name: 'defender-vulnerability-backfill'
|
||||
});
|
||||
});
|
||||
|
||||
it('extends the closed result type constraint without rewriting occurrences', async () => {
|
||||
it('keeps migration 022 limited to the closed result type constraint', async () => {
|
||||
const sql = await readFile(migrationPath, 'utf8');
|
||||
expect(sql).toContain("'vulnerability_finding'");
|
||||
expect(sql).toContain('assessor_occurrences_result_type_check');
|
||||
expect(sql).not.toMatch(/UPDATE\s+assessor_finding_occurrences/i);
|
||||
});
|
||||
|
||||
it('backfills only derived Defender columns in migration 023', async () => {
|
||||
const sql = await readFile(backfillPath, 'utf8');
|
||||
expect(sql).toMatch(/UPDATE\s+assessor_finding_occurrences/i);
|
||||
expect(sql).toContain("outcome_normalized = 'unknown' THEN 'warning'");
|
||||
expect(sql).toContain("lower(btrim(tool_id)) = 'defender-for-iot-vulnerabilities'");
|
||||
expect(sql).not.toMatch(/jsonb_set|\bdata\s*=/i);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue