diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index 6b98321..c4f0b56 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -4231,6 +4231,7 @@ Verifiziert 2026-09-08 (Phase 7/13B): GPO-Quellenkorrektur fuer Work und Client | 2026-10-06 | Phase 7: Data Hub Deviation/Passed now use the same assessed-percentage helper as Client Info; all six donuts state their population basis. 29 focused tests and synthetic Chrome Dark/Light checks passed; absolute counts, other population shares and assessment semantics retained. Client build, design and UI/API version checks passed. Nine out-of-scope report test failures, a report Local-only failure and report server type errors prevent full release verification; attempted 2.16.9 reverted to 2.16.8 pending successful production builds. | | 2026-10-06 | Phase 7: Client Info Deviations percentage now divides by Passed + Deviations and labels Of assessed checks; zero assessed shows a dash while preserving absolute zero. Permanent UI/click invariance tests in both themes and synthetic Chrome presentation verified; unchanged counts, verdicts, scores and other donuts. Patch 2.16.8: both builds and version contracts passed; full run 1,936 passed/15 skipped with one stale API-version assertion corrected and passed in the 43-test focused rerun; Local-only/design checks passed. | | 2026-10-06 | Phase 13B: GPO folder read diagnostics retain only safe error names, stages and counts, distinguish empty/denied/unreadable selections, preserve cancellation and wrap complete timeslot messages. 27 focused GPO UI tests and synthetic Chrome Dark/Light checks passed; both builds and version contracts verified for patch 2.16.7. Full run: 1,932 passed, 15 skipped, one general import timeout passed on separate rerun; Local-only/design checks passed. Customer-specific read failure remains unconfirmed; no customer path/data used, Phase 13B remains in progress. | +| 2026-10-02 | Phase 13A, Disclosure Policy 3.5.0: Defender-Teil von `21a6266` uebernommen; `48a1888` (PingCastle-Aggregate, steht gegen die Zaehlwert-Grenze der Policy 2.0.0) und `78ea9a6` auf Nutzerentscheidung ausgelassen. Die Result DB kennt Microsoft Defender for IoT als Werkzeug statt `Security assessment`; freigegeben wird nur eine formal gueltige oeffentliche CVE-ID als Rule ID und zugleich als Titel. Geraete-, Site-, Sensor- und Zaehlwerte bleiben ausgeschlossen, `affectedAsset` bleibt zurueckgehalten; das Alerts-Profil bleibt generisch. Die zurueckgestellte `securityToolId`-Zeile aus `c056c21` bildet beide Defender-Namen auf den Schluessel `defender-for-iot` ab. Regression: Work und Result DB benennen ein Defender-Finding ueber die Projektion mit seiner CVE. `docs/RESULT_CONTRACT.md` beschreibt 3.5.0; die Versionsaufzaehlung der Result-DB-Regel in `AGENTS.md` ist nicht angepasst (Governance nicht freigegeben). Die materialisierte Result DB wird nach dem naechsten Backendstart mit der neuen Policy neu aufgebaut. Verifikation: beide Typechecks, Gesamtlauf mit 1.921 bestandenen und 18 uebersprungenen Tests sowie dem bekannten lastbedingten Ausfall von `sheet-selection-cost.test.tsx` (allein bestanden), Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phase 13A: Geraeteteil von `95ec322` nachgezogen. `defenderRiskTargetsForOccurrences` ordnet Devices ueber die Quellzeilen der jeweiligen Occurrence zu statt ueber die CVE allein; fuehren zwei Occurrences dieselbe CVE mit unterschiedlichen Devices, bleibt jedes Device bei seiner eigenen Occurrence, und ein `device-finding` fuer ein Device ausserhalb der gewaehlten Occurrence wird abgelehnt. Das stuetzt die Regel, dass eine geraetebezogene Entscheidung nur ihre erfassten Occurrences deckt. Zwei Regressionen (Domain, lokales Repository) uebernommen. Keine API-, SQL- oder Persistenzaenderung. Verifikation: beide Typechecks, Gesamtlauf mit 1.922 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phase 7: Defender-Teil von `3f6259d` (Stile und Dashboardtest) nachgezogen. Kennzahlbeschriftungen und Tabellenkoepfe des Defender-Panels nutzen die 14px-Tabellenrolle statt der 12px-Labelrolle; die Kennzahlen stehen als kompakte Kacheln (`--tile-compact-width`) in einem Segment statt in sechs festen Spalten; der Zeilenmarker nutzt `--marker-width`. Beibehalten gegenueber dem Original: Kachelkontur `--tile-border`, gemeinsame Textrollen und die blaue Tabellenzeilen-Markierung. Nur CSS und Testassertions; nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.920 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | | 2026-10-02 | Phasen 6/7/13A, Produktversion 2.12.0: `fdd46e8` (Profil Microsoft Defender for IoT Alerts) angepasst uebernommen. Profil `defender-for-iot-alerts` 1.6.0 mit Domain-Modell, Source Extractor, Manifestzeilen und deklarierten Scoring-/Prioritaets-/Timeslot-Vertraegen (kein nativer Gesamtscore, native Alarm-Severity, kein Timeslot-Vergleich). Alarme werden je Alarmtyp gruppiert; unterschiedliche Beschreibungen eines Typs erscheinen als gemeinsame Woerter mit `…`, die zugehoerige Nutzerregel steht mit ausdruecklicher Governance-Freigabe in `AGENTS.md`. Werkzeuguebergreifend: Unterstriche innerhalb eines Wortes sind in `markdown.ts` keine Auszeichnung mehr; ein Profil kann per `selectExtractedTables` die Tabellen behalten, aus denen sein Extractor Findings gebildet hat; ein Statusmapping kann sein Outcome deklarieren; beide Repositories setzen eine bereits durch einen ersetzten Lauf belegte Scan-Sequenz hinter alle gespeicherten Laeufe. Die Ausblendung von `Source Observations` liegt auf dieser Basis in `FindingReader.tsx`. Zwei Korrekturen aus spaeteren Commits waren fuer Defender auf dieser Basis noetig und sind vorgezogen: aus `210bbc0` der Titel-Rueckfall in `recordTitleFor` (eine leere gewaehlte Titelspalte faellt wieder auf den Datensatztitel zurueck; mit `7d3ef5f` war dieser Rueckfall fuer alle Scanner verloren gegangen) samt Regression fuer die GPO-Zeile, und aus `21a6266` die Ausnahme in `withSourceRuleId`, die eine native ID-Spalte nicht in `Source Rule ID` umbenennt, wenn sie zugleich der Finding-Titel ist (Defender `CVE`, Alert `Title`); die Regel-ID wird dann zusaetzlich angefuegt. Nicht uebernommen: die Versionsangabe 2.10.8 des Originals. Produktversion: Minor-Erhoehung von 2.11.2 auf 2.12.0 fuer die Defender-Serie (zwei neue Scannerprofile, geraetebezogene Risikoakzeptanz); `package.json`, Root-Eintraege in `package-lock.json` und die Versionsvertragstests sind synchron. Die `securityToolId`-Zeile aus `c056c21` folgt mit dem Result-DB-Werkzeugeintrag aus `21a6266`. Neue Profile, daher kein Reimport bestehender Importe; keine SQL-Migration. Nicht im Browser geprueft. Verifikation: beide Typechecks, Gesamtlauf mit 1.920 bestandenen und 18 uebersprungenen Tests, Local-only-/Designpruefung und beide Produktionsbuilds erfolgreich. | diff --git a/docs/RESULT_CONTRACT.md b/docs/RESULT_CONTRACT.md index 4550ebb..fd405e3 100644 --- a/docs/RESULT_CONTRACT.md +++ b/docs/RESULT_CONTRACT.md @@ -534,6 +534,12 @@ private Literale noch weitere nicht oeffentliche Hosts enthalten. Tenant-Hosts u uebrigen Hosts bleiben geschwaerzt. Dateinamen, ScubaGear-Kennungen, Graph-Berechtigungen, Code-Namespaces und -Bezeichner sowie Satzverbindungen ohne Leerzeichen gelten nicht als Domain. +Disclosure Policy 3.5.0 (2026-10-02, Nutzerentscheidung) nimmt Microsoft Defender +for IoT als bekanntes Werkzeug auf. Freigegeben wird nur eine formal gueltige +oeffentliche CVE-ID (`CVE-JJJJ-NNNN…`) als Rule ID und zugleich als Titel; Geraete-, +Site-, Sensor- und Zaehlwerte bleiben wie bisher ausgeschlossen. Das Profil +Defender for IoT Alerts bleibt ein generisches Werkzeug ohne freigegebene Rule ID. + Gespeicherte Result DB (2026-09-28, verbindliche Nutzeranforderung, Migration 024): Die Zeilen werden beim Erzeugen offengelegt, geprueft und in `assessor_result_db_rows` gespeichert, mit Statussatz `assessor_result_db_state` (Revision, Policy- und Waechterversion, Zeilen, Ersetzungen, gesperrte Zeilen). diff --git a/src/domain/result-db-disclosure.ts b/src/domain/result-db-disclosure.ts index 8b00495..fd68964 100644 --- a/src/domain/result-db-disclosure.ts +++ b/src/domain/result-db-disclosure.ts @@ -6,7 +6,7 @@ import { wellKnownSidName } from '../shared/windows-codes'; import { applyResultDbIdentityGuard, RESULT_DB_IDENTITY_GUARD_VERSION, type ResultDbIdentityGuard } from './result-db-identity-guard'; import { addedPlaceholders, type ResultDbDisclosureTrace } from './result-db-trace'; -export const RESULT_DB_DISCLOSURE_POLICY_VERSION = '3.4.0'; +export const RESULT_DB_DISCLOSURE_POLICY_VERSION = '3.5.0'; const SAFE_TIMESTAMP = '1970-01-01T00:00:00.000Z'; @@ -21,7 +21,9 @@ const toolCatalog: Record = { 'purple-knight-combined': { id: 'purple-knight-combined', label: 'Purple Knight for Active Directory and Entra ID' }, 'purpleknight': { id: 'purple-knight', label: 'Purple Knight' }, 'gpos': { id: 'GPOs', label: 'Group Policy' }, - 'group-policy': { id: 'GPOs', label: 'Group Policy' } + 'group-policy': { id: 'GPOs', label: 'Group Policy' }, + 'defender-for-iot-vulnerabilities': { id: 'defender-for-iot-vulnerabilities', label: 'Microsoft Defender for IoT' }, + 'defender-for-iot': { id: 'defender-for-iot-vulnerabilities', label: 'Microsoft Defender for IoT' } }; function safeTool(value?: string): { id: string; label: string } { @@ -67,6 +69,10 @@ function safePublicRuleId(item: UnifiedResultProjection): string | undefined { const id = item.sourceRuleId?.trim(); return id && /^SI\d{3,12}$/i.test(id) ? id : undefined; } + if (tool === 'defender-for-iot-vulnerabilities') { + const id = item.sourceRuleId?.trim(); + return id && /^CVE-\d{4}-\d{4,7}$/i.test(id) ? id.toUpperCase() : undefined; + } if (scannerFamily !== 'cis-cat') return undefined; // CIS recommendation numbers reach seven levels (18.10.42.11.1.1.1); a shorter cap truncates them // into a sibling's number. A longer dotted run is not a recommendation number. @@ -607,6 +613,8 @@ function controlledTitle(item: UnifiedResultProjection): string { } const tool = disclosureTool(item); const ruleId = safePublicRuleId(item); + // A public CVE ID is the complete Defender finding name. + if (tool.id === 'defender-for-iot-vulnerabilities' && ruleId) return ruleId; const title = redactNarrative(item, item.title, 'title'); if (title) return title; return ruleId ? `${tool.label} public rule ${ruleId}` : `${tool.label} finding`; diff --git a/src/scoring/canonical-security-score.ts b/src/scoring/canonical-security-score.ts index e7acaa6..3de352d 100644 --- a/src/scoring/canonical-security-score.ts +++ b/src/scoring/canonical-security-score.ts @@ -23,7 +23,8 @@ export function securityToolId(value: string): string { purpleknight: 'purple-knight', purpleknightforactivedirectory: 'purple-knight', purpleknightforentraid: 'purple-knight', purpleknightforactivedirectoryandentraid: 'purple-knight', pingcastle: 'pingcastle', ciscat: 'cis-cat', ciscatpro: 'cis-cat', maester: 'maester', - gpos: 'group-policy', gpoanalysis: 'group-policy', grouppolicy: 'group-policy' + gpos: 'group-policy', gpoanalysis: 'group-policy', grouppolicy: 'group-policy', + defenderforiotvulnerabilities: 'defender-for-iot', microsoftdefenderforiot: 'defender-for-iot' } as Record)[normalized] ?? value.toLowerCase(); } diff --git a/tests/exports/unified-result-jsonl.test.ts b/tests/exports/unified-result-jsonl.test.ts index 7ce1739..cb2e23c 100644 --- a/tests/exports/unified-result-jsonl.test.ts +++ b/tests/exports/unified-result-jsonl.test.ts @@ -49,7 +49,7 @@ describe('filtered Result DB JSONL export', () => { recordType: 'manifest', format: RESULT_DB_JSONL_FORMAT, schemaVersion: RESULT_DB_JSONL_SCHEMA_VERSION, - disclosurePolicyVersion: '3.4.0', + disclosurePolicyVersion: '3.5.0', handling: RESULT_DB_EXPORT_HANDLING, generatedOn: '2026-09-03', resultCount: 2, diff --git a/tests/importers/defender-for-iot.test.ts b/tests/importers/defender-for-iot.test.ts index 74a8d80..62682c7 100644 --- a/tests/importers/defender-for-iot.test.ts +++ b/tests/importers/defender-for-iot.test.ts @@ -11,6 +11,8 @@ import { canonicalModelToDataset } from '../../src/features/project-import/canon import { defenderModelForDataset } from '../../src/features/defender-for-iot/defender-dataset-model'; import { summarizeScanProcessingReadiness } from '../../src/features/project-import/datahub-processing-readiness'; import { createScannerUnifiedResult } from '../../src/domain/unified-result'; +import { discloseResultDbProjection } from '../../src/domain/result-db-disclosure'; +import { recordTitleFor } from '../../src/features/dataset-viewer/record-title'; const summaryHeaders = ['Name', 'IP', 'Site', 'Sensor', 'TotalCVEs', 'Critical', 'High', 'Medium', 'Low']; const detailHeaders = ['Name', 'IP', 'Site', 'Sensor', 'CVE', 'Severity', 'NULL', 'Beschreibung']; @@ -175,6 +177,17 @@ describe('Microsoft Defender for IoT vulnerability model', () => { expect(model.cves).toHaveLength(5); expect(model.cves.every(cve => cve.sites.join() === 'Riverbend Plant')).toBe(true); expect(model.cves.every(cve => cve.sensors.join() === 'SENS-RB-01')).toBe(true); + // Regression: Work and Result DB must name each Defender finding by its CVE. + const workSheet = projected!.sheets.find(sheet => sheet.toolId === 'defender-for-iot-vulnerabilities' && sheet.analysis.records.length)!; + const workRecord = workSheet.analysis.records.find(record => record.fields.some(field => field.value === 'CVE-2099-1001'))!; + expect(recordTitleFor(workRecord, '', workSheet.toolId)).toBe('CVE-2099-1001'); + expect(workRecord.fields.find(field => field.label === 'Source Rule ID')?.value).toBe('CVE-2099-1001'); + const occurrence = canonical.findingOccurrences.find(item => item.sourceIdentity.originalRuleId === 'CVE-2099-1001')!; + const resultDbItem = discloseResultDbProjection(createScannerUnifiedResult({ + project: canonical.project, assessment: canonical.assessment, scanRun: canonical.scanRuns[0], importFile: canonical.importFiles[0], + definition: canonical.findingDefinitions.find(definition => definition.id === occurrence.definitionId)!, occurrence, reviews: canonical.reviews + })); + expect(resultDbItem).toMatchObject({ title: 'CVE-2099-1001', sourceRuleId: 'CVE-2099-1001', sourceTool: 'defender-for-iot-vulnerabilities' }); expect(inventory.profileId).toBe('defender-for-iot-vulnerabilities'); expect(inventory.profileVersion).toBe('1.2.0'); expect(inventory.extraction?.candidates).toHaveLength(0);