diff --git a/AGENTS.md b/AGENTS.md index 2533592..a22cfa8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,7 +76,7 @@ ## Context layer (Phase 13E) -- Latest explicit user decision 2026-10-01 supersedes the preceding permanent-Context decision: the Work toolbar has an `Engines` button at the former Context-button position. It stays marked blue, including on hover and while the popup is closed (an explicit colour-role exception for this control only). The shared popup lists Core Engine, Context Engine, Report Engine and, since the explicit user decision of 2026-10-05, Compliance Engine with checkboxes. All default to active; Core Engine is always checked and cannot be disabled. User choices for optional engines are stored locally under the shared Core-owned engine-activation contract. Disabling Context removes its UI, stops new context requests and clears the active Context Cluster filter; disabling Report blocks new report generation from the UI; disabling Compliance removes the Finding Reader ISO action and panel, the Result DB `Compliance mapping` batch action and `Export JSON`, the Client Info `Compliance` and `AI Overview` tabs and the Data Hub ISO corpus tile and stops their requests, and re-enabling starts the Finding Reader ISO panel closed. As the only server-side effect of this local preference (explicit user decision 2026-10-08), disabling Compliance also switches the automatic ISO mapping of new uploads off for the whole server; re-enabling does not switch it on again. Already submitted backend work is not cancelled by this local preference; its late UI output is suppressed. Cluster badges still select the exact Work filter, and the Context detail section still starts collapsed. Engine independence and Core invariance remain binding. +- Latest explicit user decision 2026-10-01 supersedes the preceding permanent-Context decision: the Work toolbar has an `Engines` button at the former Context-button position. It stays marked blue, including on hover and while the popup is closed (an explicit colour-role exception for this control only). The shared popup lists Core Engine, Context Engine, Report Engine, Remediation Engine (see `Remediation Engine (2026-10-08)`) and, since the explicit user decision of 2026-10-05, Compliance Engine with checkboxes, in this order. All default to active; Core Engine is always checked and cannot be disabled. User choices for optional engines are stored locally under the shared Core-owned engine-activation contract. Disabling Context removes its UI, stops new context requests and clears the active Context Cluster filter; disabling Report blocks new report generation from the UI; disabling Compliance removes the Finding Reader ISO action and panel, the Result DB `Compliance mapping` batch action and `Export JSON`, the Client Info `Compliance` and `AI Overview` tabs and the Data Hub ISO corpus tile and stops their requests, and re-enabling starts the Finding Reader ISO panel closed. As the only server-side effect of this local preference (explicit user decision 2026-10-08), disabling Compliance also switches the automatic ISO mapping of new uploads off for the whole server; re-enabling does not switch it on again. Already submitted backend work is not cancelled by this local preference; its late UI output is suppressed. Cluster badges still select the exact Work filter, and the Context detail section still starts collapsed. Engine independence and Core invariance remain binding. - Explicit user decision 2026-10-01 supersedes the earlier Context on/off, default-off, locally saved switch, hover-button and badge-scroll agreements: Context is permanently active and the global toolbar Context button is removed. Ignore obsolete locally saved on/off preferences. Clicking a Finding Reader Context cluster badge selects that exact `Context Cluster` Work facet value, replacing the previous cluster selection while preserving other filters. The Context detail section still starts collapsed for each finding and opens through its own summary. All read-only, provenance and verdict/score boundaries remain binding; only an explicit cluster-filter selection restricts the Work record set. diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md index 9251c87..e6b8e71 100644 --- a/docs/IMPLEMENTATION_PLAN.md +++ b/docs/IMPLEMENTATION_PLAN.md @@ -4346,6 +4346,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-10-12 | Integration (Core-Freigabe alle, Governance): Michaels Commit `614ac1b` (Remediation Engine, geschuetzte Importvertraege, Client-Info-Tab `Security`, Technical Report) ist die Basis; die 39 Commits seit `250c213` (AI/ISO/BSI-Compliance-Engine, Defender for IoT) sind darauf rebased (Branch `rebase/michael-backup`, `compliance-mapping` unveraendert). Beide Engines stehen nebeneinander in Engines-Popup (Core, Context, Report, Remediation, Compliance), Hook-Tabelle, Grenztest, Server und Work-Facetten (Work Package an der Stelle von Result Message, ISO/BSI-Facetten weiter direkt vor `Source: Scan`). Client Info nutzt Michaels `ClientInfoTabs` in der vereinbarten Reihenfolge Overview, Security, Microsoft Defender for IoT, Compliance, AI Overview; die drei Engine-/Defender-Tabs sind `lazy` und mounten nur ausgewaehlt (neuer Verhaltenstest), die alte Tab-Navigation und ihre CSS-Regeln entfallen. Finding Reader: `Map ISO controls` steht direkt vor Michaels fester Aktionsgruppe `AI search`/Bookmark/`Results`. Abgeleiteter Forest-Hinweis nach `client-info-facts.ts` portiert. Importgate: zwei additive synthetische Defender-Vertraege (Schwachstellen- und Alert-CSV, je 3 Findings); die Snapshots `pingcastle-html`/`pingcastle-xml` weichen durch die freigegebenen PingCastle-Aenderungen aus 2.19.1 ab (Profil 1.5.0 -> 1.7.0, XML-Domaenenfakten, Vergleichsschluessel) und bleiben bis zur Nutzerpruefung unveraendert, das Gate ist deshalb rot. Sechs weitere Testfehler (Context-Cluster-Reihenfolge, Pathfinder-Mock, Data-Hub-Donut- und DB-Structure-Layouttest) bestehen bereits auf `614ac1b` allein. Produktversion 2.25.0 (Minor: beide Funktionslinien vereint); Versionstests, beide Produktionsbuilds, Local-only- und Designpruefung bestanden. Vor dem ersten Serverstart legt die Remediation Engine das Schema `assessor_remediation` an; vorher Backup des kanonischen Datastores. | | 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. | diff --git a/fixtures/README.md b/fixtures/README.md index 9fae9f2..1b2454d 100644 --- a/fixtures/README.md +++ b/fixtures/README.md @@ -45,3 +45,8 @@ All files below are deliberately small, synthetic, and free of customer or compa `maester/application-json-binding.synthetic.html` protects the current static declaration in an application/json script. `maester/multi-tenant.synthetic.html` protects separate native tenant paths. Both contain only invented Dummy values. Compatibility references use synthetic data only, including when a real report reveals a new structure. + +## Microsoft Defender for IoT + +- Files: `defender-for-iot/cves.synthetic.csv` (vulnerability export) and `defender-for-iot-alerts/alerts.synthetic.csv` (alert export) +- Basis: the same fictional devices, CVE IDs from the reserved `CVE-2099-*` range, TEST-NET addresses and documentation MACs as the Defender importer tests. Added 2026-10-12 as additive contracts when the protected gate met the existing Defender profiles. diff --git a/fixtures/defender-for-iot-alerts/alerts.synthetic.csv b/fixtures/defender-for-iot-alerts/alerts.synthetic.csv new file mode 100644 index 0000000..ed0c895 --- /dev/null +++ b/fixtures/defender-for-iot-alerts/alerts.synthetic.csv @@ -0,0 +1,4 @@ +Id,Title,First Detection,Last Detection,Last Activity,Severity,Description,Acknowledged,Additional Information,Source Address,Destination Address +A-1,Synthetic Unauthorized PLC Programming,11/09/2026 10:05:12,11/09/2026 10:07:11,11/09/2026 10:07:11,Critical,Synthetic programming from 192.0.2.41,False,192.0.2.21 over port CIP (44818),192.0.2.41,192.0.2.21 +A-3,Synthetic Internet Connection,13/09/2026 09:00:00,13/09/2026 09:30:00,13/09/2026 09:30:00,Major,Synthetic outbound connection,False,198.51.100.7 over port HTTPS (443),00-00-5E-00-53-01, +A-4,Synthetic Unused Protocol,14/09/2026 07:00:00,14/09/2026 07:00:00,14/09/2026 07:05:00,Warning,Synthetic unused protocol,True,,192.0.2.50,192.0.2.51 diff --git a/fixtures/defender-for-iot/cves.synthetic.csv b/fixtures/defender-for-iot/cves.synthetic.csv new file mode 100644 index 0000000..2013327 --- /dev/null +++ b/fixtures/defender-for-iot/cves.synthetic.csv @@ -0,0 +1,4 @@ +Device ID,Device Name,IP Address,Site,Sensor,CVE,CVSS Score,Severity,Component,Status +DEV-OT-001,PLC-LINE1-01,192.0.2.21,Riverbend Plant,SENS-RB-01,CVE-2099-1001,9.1,Critical,Synthetic component,Open +DEV-OT-002,HMI-LINE1-01,192.0.2.31,Riverbend Plant,SENS-RB-01,CVE-2099-1002,7.5,High,Synthetic component,Open +DEV-OT-004,HISTORIAN-01,192.0.2.51,Riverbend Plant,SENS-RB-01,CVE-2099-1005,8.2,High,Synthetic component,Mitigation planned diff --git a/package-lock.json b/package-lock.json index 310b0ff..f41524a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "assessor", - "version": "2.24.2", + "version": "2.25.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "assessor", - "version": "2.24.2", + "version": "2.25.0", "dependencies": { "@fontsource-variable/inter": "^5.3.0", "jsdom": "^29.1.1", diff --git a/package.json b/package.json index 6d37b69..cdfbcbd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "assessor", - "version": "2.24.2", + "version": "2.25.0", "private": true, "type": "module", "scripts": { diff --git a/src/compliance/ui/ComplianceOverview.tsx b/src/compliance/ui/ComplianceOverview.tsx index efc31bf..d258a3e 100644 --- a/src/compliance/ui/ComplianceOverview.tsx +++ b/src/compliance/ui/ComplianceOverview.tsx @@ -28,7 +28,7 @@ export const footprintStep = (findings: number) => findings >= 25 ? 4 : findings /** Switches the overview row between the frameworks; the framework tiles below stay as they are. */ export function ComplianceFrameworkSwitch({ value, onChange }: { value: ComplianceFramework; onChange(value: ComplianceFramework): void }) { // The Client Info tab buttons: the approved selection control of this view. - return
+ return
{FRAMEWORKS.map(([id, label]) => )}
; diff --git a/src/compliance/ui/MappingReviewTile.tsx b/src/compliance/ui/MappingReviewTile.tsx index 90c0aa9..07cbc1a 100644 --- a/src/compliance/ui/MappingReviewTile.tsx +++ b/src/compliance/ui/MappingReviewTile.tsx @@ -75,7 +75,7 @@ function ReviewList({ review, projectId, backendStatus, onChanged }: {

Each rule compares the BSI requirements accepted in its direct BSI mapping with the requirements the BSI mapping {review.crosswalkVersion} derives from its accepted ISO controls. Agreement shows consistency, not correctness. Nothing here changes a decision until you accept or reject one.

-
+
{FILTERS.map(option => { const amount = review.items.filter(item => item.state === option.state).length; return
; } diff --git a/src/features/dashboard/DashboardPanel.tsx b/src/features/dashboard/DashboardPanel.tsx index a9a1b09..02bdb4e 100644 --- a/src/features/dashboard/DashboardPanel.tsx +++ b/src/features/dashboard/DashboardPanel.tsx @@ -1147,7 +1147,6 @@ export function dashboardAnalysis( export function DashboardPanel({ onOpenWork }: DashboardPanelProps) { const app = useApp(); const { dataset, activeSheet, activeProfile, reviews, setStatusFilter, setFacetFilter, setPathFindingIds, resetFilters, setView } = app; - const [clientInfoView, setClientInfoView] = useState<'overview' | 'defender' | 'compliance' | 'ai'>('overview'); const hasDefender = useMemo(() => Boolean(dataset && defenderModelForDataset(dataset).available), [dataset]); const pureDefender = dataset?.profileId === DEFENDER_FOR_IOT_PROFILE_ID; const environment = useEnvironmentSnapshot(app.targetProjectId, app.activeScanRunIds); @@ -1161,12 +1160,6 @@ export function DashboardPanel({ onOpenWork }: DashboardPanelProps) { if (showCompliance && app.targetProjectId) preloadClientComplianceOverview(app.targetProjectId, app.activeAssessmentIds, app.activeScanRunIds); }, [showCompliance, app.targetProjectId, app.activeAssessmentIds, app.activeScanRunIds]); - useEffect(() => { - if (pureDefender && clientInfoView === 'overview') setClientInfoView('defender'); - else if (clientInfoView === 'defender' && !hasDefender) setClientInfoView('overview'); - else if (clientInfoView === 'ai' && !showAiOverview) setClientInfoView(pureDefender ? 'defender' : 'overview'); - else if (clientInfoView === 'compliance' && !showCompliance) setClientInfoView(pureDefender ? 'defender' : 'overview'); - }, [clientInfoView, hasDefender, pureDefender, showAiOverview, showCompliance]); const gpoInventory = useGpoInventory(app.targetProjectId); const sourceFilter = app.facetFilters['provenance:source-scan']; const analysis = useDashboardAnalysis(activeSheet, dataset, activeProfile, reviews, sourceFilter); @@ -1251,19 +1244,8 @@ export function DashboardPanel({ onOpenWork }: DashboardPanelProps) { setView('columns'); onOpenWork(); }; - const clientInfoViews: Array = [ - ...(pureDefender ? [] : [['overview', 'Overview'] as const]), - ...(hasDefender ? [['defender', 'Microsoft Defender for IoT'] as const] : []), - ...(showCompliance ? [['compliance', 'Compliance'] as const] : []), - ...(showAiOverview ? [['ai', 'AI Overview'] as const] : []) - ]; - const clientInfoTabs = clientInfoViews.length > 1 ? : undefined; - if (clientInfoView === 'ai' && showAiOverview) return ; // Compliance Engine tab: one tile per framework; a Results window opens a control or theme in Work. - if (clientInfoView === 'compliance' && showCompliance) return jump(() => { const targetSheet = sourceScanNavigationSheet(dataset, activeSheet); if (targetSheet) { @@ -1308,12 +1290,10 @@ export function DashboardPanel({ onOpenWork }: DashboardPanelProps) { context={
Client:{dataset.fileName}
Assessment:
-
}/>; - if (hasDefender && (clientInfoView === 'defender' || pureDefender)) { - return }/> : null; + const defenderContent = hasDefender ? app.recordScopedRiskAcceptance(write) : undefined} />; - } + onScopedRiskAcceptance={app.backendStatus === 'connected' ? write => app.recordScopedRiskAcceptance(write) : undefined} /> : null; const { total, statusFacet, severityFacet, sourceFacet, tools, reports, @@ -1373,8 +1353,7 @@ export function DashboardPanel({ onOpenWork }: DashboardPanelProps) { : 'all'; return ( - - {clientInfoTabs} +
Client:{dataset.fileName}
Assessment:
@@ -1501,6 +1480,9 @@ export function DashboardPanel({ onOpenWork }: DashboardPanelProps) { jump(() => setPathFindingIds([recordId]))} />
}, { id: 'security', label: 'Security', content: }]} /> + dataset={dataset} sheetId={activeSheet.id} toolId={activeSheet.toolId} contextStatus={impactContext.status} {...securityView}/> }]), + ...(defenderContent ? [{ id: 'defender', label: 'Microsoft Defender for IoT', content: defenderContent, lazy: true }] : []), + ...(complianceContent ? [{ id: 'compliance', label: 'Compliance', content: complianceContent, lazy: true }] : []), + ...(showAiOverview ? [{ id: 'ai', label: 'AI Overview', content: , lazy: true }] : [])]} /> ); } diff --git a/src/features/dashboard/RiskRegister.tsx b/src/features/dashboard/RiskRegister.tsx index 149a83a..126525d 100644 --- a/src/features/dashboard/RiskRegister.tsx +++ b/src/features/dashboard/RiskRegister.tsx @@ -137,7 +137,7 @@ export function RiskRegister({ dataset, reviews, scopedRiskAcceptances = [], onO {entry.title}{entry.source} {entry.state} - {recordFor(entry) && onOpenFinding?.(entry.recordId)}/>}
)} + {recordFor(entry) && onOpenFinding?.(recordFor(entry)!.id)}/>})}
@@ -146,7 +146,7 @@ export function RiskRegister({ dataset, reviews, scopedRiskAcceptances = [], onO
Risk Information{selected.source}
{onOpenFinding && selected.recordId && }{selected.state}
-
Finding

{selected.title}

{recordFor(selected) && onOpenFinding?.(selected.recordId)}/>}
+
Finding

{selected.title}

{recordFor(selected) && onOpenFinding?.(recordFor(selected)!.id)}/>}
{selected.coveredCves &&
Immutable system scope

{selected.device}

{selected.coveredCves.length.toLocaleString('en-US')} covered CVE{selected.coveredCves.length === 1 ? '' : 's'}: {selected.coveredCves.join(', ')}

}
diff --git a/src/features/dashboard/client-info-facts.ts b/src/features/dashboard/client-info-facts.ts index 17a37ab..6d860eb 100644 --- a/src/features/dashboard/client-info-facts.ts +++ b/src/features/dashboard/client-info-facts.ts @@ -73,10 +73,10 @@ export function factText(value: string | undefined): string { */ function forestFact(snapshot: EnvironmentSnapshot | undefined): DashboardFact { const observed = (snapshot?.observations ?? []).filter(item => item.property === 'forest.name' && item.state === 'observed'); - const observation = observed.find(item => !item.attributes.derivation) ?? observed[0]; + const observation = observed.find(item => !item.attributes?.derivation) ?? observed[0]; if (!observation) return { label: 'Forest', value: UNKNOWN_FACT }; const value = factText(displayValue(observation)); - return observation.attributes.derivation === 'configuration-partition-dn' && value !== UNKNOWN_FACT + return observation.attributes?.derivation === 'configuration-partition-dn' && value !== UNKNOWN_FACT ? { label: 'Forest', value, note: 'derived from configuration path' } : { label: 'Forest', value }; } diff --git a/src/styles/defender-for-iot.css b/src/styles/defender-for-iot.css index 0ac3e13..9790ccf 100644 --- a/src/styles/defender-for-iot.css +++ b/src/styles/defender-for-iot.css @@ -6,11 +6,11 @@ .defender-metrics article { display: grid; align-content: center; gap: var(--space-2); min-width: 0; min-height: 88px; padding: var(--tile-inset); border: var(--border-hair) solid var(--tile-border); border-radius: var(--tile-radius); background: var(--tile-surface); box-shadow: none; } .defender-metrics span { color: var(--muted); font-size: var(--type-table-body-size); font-weight: var(--fw-semibold); line-height: 1.3; } .defender-metrics strong { color: var(--heading); font-size: var(--type-h2-size); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; line-height: 1; } -.defender-tabs, .client-info-tabs { display: flex; flex-wrap: wrap; gap: var(--space-2); } -.defender-tabs button, .client-info-tabs button { min-height: 36px; padding: var(--space-2) var(--space-4); border: var(--border-hair) solid transparent; border-radius: var(--btn-radius); color: var(--text); background: transparent; font-size: var(--ui-control-size); font-weight: var(--fw-semibold); cursor: pointer; } -.defender-tabs button:hover, .client-info-tabs button:hover { background: var(--tile-surface-hover); } -.defender-tabs button:focus-visible, .client-info-tabs button:focus-visible { outline: var(--bw-emphasis) solid var(--focus-ring); outline-offset: calc(-1 * var(--bw-emphasis)); } -.defender-tabs button[aria-pressed="true"], .client-info-tabs button[aria-pressed="true"] { outline: var(--bw-emphasis) solid var(--marking-color); outline-offset: calc(-1 * var(--bw-emphasis)); background: var(--tile-surface); } +.defender-tabs { display: flex; flex-wrap: wrap; gap: var(--space-2); } +.defender-tabs button { min-height: 36px; padding: var(--space-2) var(--space-4); border: var(--border-hair) solid transparent; border-radius: var(--btn-radius); color: var(--text); background: transparent; font-size: var(--ui-control-size); font-weight: var(--fw-semibold); cursor: pointer; } +.defender-tabs button:hover { background: var(--tile-surface-hover); } +.defender-tabs button:focus-visible { outline: var(--bw-emphasis) solid var(--focus-ring); outline-offset: calc(-1 * var(--bw-emphasis)); } +.defender-tabs button[aria-pressed="true"] { outline: var(--bw-emphasis) solid var(--marking-color); outline-offset: calc(-1 * var(--bw-emphasis)); background: var(--tile-surface); } .defender-view { display: grid; gap: var(--space-4); min-width: 0; padding: var(--tile-inset); border: var(--border-hair) solid var(--tile-border); border-radius: var(--tile-radius); background: var(--tile-surface); } .defender-view > header { display: flex; align-items: baseline; justify-content: space-between; flex-wrap: wrap; gap: var(--space-2); } .defender-view > header h2 { margin: 0; color: var(--heading); font-size: var(--type-h2-size); font-weight: var(--fw-semibold); } diff --git a/tests/app/app-version.test.ts b/tests/app/app-version.test.ts index d5612c8..a91fa04 100644 --- a/tests/app/app-version.test.ts +++ b/tests/app/app-version.test.ts @@ -9,7 +9,7 @@ const components = readFileSync(new URL('../../src/styles/components.css', impor describe('ASSESSOR version in the brand header', () => { it('shows the centrally maintained product version and opens About from the brand', () => { - expect(ASSESSOR_PRODUCT_VERSION).toBe('2.24.2'); + expect(ASSESSOR_PRODUCT_VERSION).toBe('2.25.0'); expect(app).toContain("'db-structure': 'Data Flow'"); expect(app).toContain("['db-structure', 'Data Flow']"); expect(app).not.toContain("'DB Structure'"); diff --git a/tests/compliance/ai-overview-panel.test.tsx b/tests/compliance/ai-overview-panel.test.tsx index d93481c..adbfdde 100644 --- a/tests/compliance/ai-overview-panel.test.tsx +++ b/tests/compliance/ai-overview-panel.test.tsx @@ -39,9 +39,8 @@ describe('Client Info AI Overview', () => { it('adds the AI tab beside the Client Info overview', () => { const dashboard = readFileSync(new URL('../../src/features/dashboard/DashboardPanel.tsx', import.meta.url), 'utf8'); - expect(dashboard).toContain("[['overview', 'Overview'] as const]"); - expect(dashboard).toContain('{clientInfoTabs}'); - expect(dashboard).toContain("...(showAiOverview ? [['ai', 'AI Overview'] as const] : [])"); - expect(dashboard).toContain("clientInfoView === 'ai'"); + expect(dashboard).toContain("[{ id: 'overview', label: 'Overview', content:"); + // The AI tab is the last Client Info tab and mounts only while selected. + expect(dashboard).toContain("...(showAiOverview ? [{ id: 'ai', label: 'AI Overview', content: , lazy: true }] : [])]} />"); }); }); diff --git a/tests/compliance/client-iso-controls.test.tsx b/tests/compliance/client-iso-controls.test.tsx index b0e8507..b66d69f 100644 --- a/tests/compliance/client-iso-controls.test.tsx +++ b/tests/compliance/client-iso-controls.test.tsx @@ -157,14 +157,17 @@ describe('Client Info ISO controls', () => { it('is its own Client Info tab, not part of the Overview, and opens Work through the ISO control and theme filters', () => { const source = readFileSync(join(process.cwd(), 'src/features/dashboard/DashboardPanel.tsx'), 'utf8'); - expect(source).toContain("...(showCompliance ? [['compliance', 'Compliance'] as const] : [])"); - expect(source.indexOf("['compliance', 'Compliance']")).toBeLessThan(source.indexOf("['ai', 'AI Overview']")); - const tab = source.slice(source.indexOf("if (clientInfoView === 'compliance' && showCompliance) return source.indexOf(id)); + expect(order.every(index => index > 0)).toBe(true); + expect([...order].sort((a, b) => a - b)).toEqual(order); + const tab = source.slice(source.indexOf('const complianceContent = showCompliance ? { } finally { await act(async () => root.unmount()); container.remove(); vi.unstubAllGlobals(); } }); }); + +describe('Client Info lazy engine tabs', () => { + it('mounts a lazy tab only while it is selected, so hidden engine tabs start no requests', async () => { + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); + const mounted = vi.fn(); + function Probe() { mounted(); return

Synthetic engine tab

; } + const host = document.createElement('div'); document.body.append(host); + const root = createRoot(host); + try { + await act(async () => root.render(Synthetic overview

}, + { id: 'compliance', label: 'Compliance', content: , lazy: true } + ]}/>)); + const tabs = host.querySelectorAll('[role="tab"]'); + const panels = host.querySelectorAll('[role="tabpanel"]'); + expect(mounted).not.toHaveBeenCalled(); + expect(panels[1].childNodes).toHaveLength(0); + await act(async () => tabs[1].click()); + expect(mounted).toHaveBeenCalled(); + expect(panels[1].textContent).toBe('Synthetic engine tab'); + await act(async () => tabs[0].click()); + expect(panels[1].childNodes).toHaveLength(0); + } finally { await act(async () => root.unmount()); host.remove(); vi.unstubAllGlobals(); } + }); +}); diff --git a/tests/features/defender-dashboard.test.ts b/tests/features/defender-dashboard.test.ts index 2f59a86..fcf98af 100644 --- a/tests/features/defender-dashboard.test.ts +++ b/tests/features/defender-dashboard.test.ts @@ -51,9 +51,9 @@ describe('Defender vulnerability dashboard projection', () => { expect(component).toContain('className="dashboard-page client-command-center defender-page"'); expect(component).toContain('className="client-overview-header defender-header"'); expect(component).toContain('Source:Microsoft Defender for IoT'); - expect(dashboard).toContain("[['overview', 'Overview'] as const]"); - expect(dashboard).toContain("[['defender', 'Microsoft Defender for IoT'] as const]"); - expect(dashboard).toContain(' app.recordScopedRiskAcceptance(write) : undefined}'); expect(styles).toContain('padding: var(--panel-inset)'); diff --git a/tests/importers/__snapshots__/import-compatibility.test.ts.snap b/tests/importers/__snapshots__/import-compatibility.test.ts.snap index b03aeae..0b44860 100644 --- a/tests/importers/__snapshots__/import-compatibility.test.ts.snap +++ b/tests/importers/__snapshots__/import-compatibility.test.ts.snap @@ -3098,6 +3098,1753 @@ exports[`protected scanner import contracts (synthetic data only) > 'cis-cat-sum } `; +exports[`protected scanner import contracts (synthetic data only) > 'defender-for-iot-alerts-csv' retains recognition, preview and approved canonical results in browser and backend 1`] = ` +{ + "approved": { + "recordCount": 3, + "selected": true, + }, + "definitions": [ + { + "canonicalKey": "defender-for-iot-alerts:synthetic unauthorized plc programming", + "category": undefined, + "description": "Synthetic programming from 192.0.2.41", + "frameworkReferences": [], + "identity": { + "durable": true, + "evidence": [ + "Werkzeugkennung und gemappte native Regel-ID sind vorhanden.", + ], + "strategy": "tool-rule-id", + "version": 1, + }, + "recommendation": undefined, + "title": "Synthetic Unauthorized PLC Programming", + }, + { + "canonicalKey": "defender-for-iot-alerts:synthetic internet connection", + "category": undefined, + "description": "Synthetic outbound connection", + "frameworkReferences": [], + "identity": { + "durable": true, + "evidence": [ + "Werkzeugkennung und gemappte native Regel-ID sind vorhanden.", + ], + "strategy": "tool-rule-id", + "version": 1, + }, + "recommendation": undefined, + "title": "Synthetic Internet Connection", + }, + { + "canonicalKey": "defender-for-iot-alerts:synthetic unused protocol", + "category": undefined, + "description": "Synthetic unused protocol", + "frameworkReferences": [], + "identity": { + "durable": true, + "evidence": [ + "Werkzeugkennung und gemappte native Regel-ID sind vorhanden.", + ], + "strategy": "tool-rule-id", + "version": 1, + }, + "recommendation": undefined, + "title": "Synthetic Unused Protocol", + }, + ], + "evidence": [ + { + "finding": { + "canonicalRuleId": "Synthetic Unauthorized PLC Programming", + "originalRuleId": "Synthetic Unauthorized PLC Programming", + "originalTitle": "Synthetic Unauthorized PLC Programming", + "tool": { + "id": "defender-for-iot-alerts", + "name": "Microsoft Defender for IoT Alerts", + }, + }, + "kind": "object", + "sourceFieldKeys": [ + "source-observations", + ], + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 1, + }, + "value": [ + { + "fields": { + "Acknowledged": "False", + "Additional Information": "192.0.2.21 over port CIP (44818)", + "Description": "Synthetic programming from 192.0.2.41", + "Destination Address": "192.0.2.21", + "First Detection": "11/09/2026 10:05:12", + "Id": "A-1", + "Last Activity": "11/09/2026 10:07:11", + "Last Detection": "11/09/2026 10:07:11", + "Severity": "Critical", + "Source Address": "192.0.2.41", + "Title": "Synthetic Unauthorized PLC Programming", + }, + "source": { + "rowIndex": 1, + "tableIndex": 0, + }, + }, + ], + }, + { + "finding": { + "canonicalRuleId": "Synthetic Internet Connection", + "originalRuleId": "Synthetic Internet Connection", + "originalTitle": "Synthetic Internet Connection", + "tool": { + "id": "defender-for-iot-alerts", + "name": "Microsoft Defender for IoT Alerts", + }, + }, + "kind": "object", + "sourceFieldKeys": [ + "source-observations", + ], + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 2, + }, + "value": [ + { + "fields": { + "Acknowledged": "False", + "Additional Information": "198.51.100.7 over port HTTPS (443)", + "Description": "Synthetic outbound connection", + "Destination Address": "", + "First Detection": "13/09/2026 09:00:00", + "Id": "A-3", + "Last Activity": "13/09/2026 09:30:00", + "Last Detection": "13/09/2026 09:30:00", + "Severity": "Major", + "Source Address": "00-00-5E-00-53-01", + "Title": "Synthetic Internet Connection", + }, + "source": { + "rowIndex": 2, + "tableIndex": 0, + }, + }, + ], + }, + { + "finding": { + "canonicalRuleId": "Synthetic Unused Protocol", + "originalRuleId": "Synthetic Unused Protocol", + "originalTitle": "Synthetic Unused Protocol", + "tool": { + "id": "defender-for-iot-alerts", + "name": "Microsoft Defender for IoT Alerts", + }, + }, + "kind": "object", + "sourceFieldKeys": [ + "source-observations", + ], + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 3, + }, + "value": [ + { + "fields": { + "Acknowledged": "True", + "Additional Information": "", + "Description": "Synthetic unused protocol", + "Destination Address": "192.0.2.51", + "First Detection": "14/09/2026 07:00:00", + "Id": "A-4", + "Last Activity": "14/09/2026 07:05:00", + "Last Detection": "14/09/2026 07:00:00", + "Severity": "Warning", + "Source Address": "192.0.2.50", + "Title": "Synthetic Unused Protocol", + }, + "source": { + "rowIndex": 3, + "tableIndex": 0, + }, + }, + ], + }, + ], + "findings": [ + { + "affectedAsset": undefined, + "fieldMappings": [ + { + "decisionSource": "automatic", + "normalizedValue": "Synthetic Unauthorized PLC Programming", + "sourceFieldKeys": [ + "title", + ], + "targetRole": "rule-id", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Critical", + "sourceFieldKeys": [ + "severity", + ], + "targetRole": "severity", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Critical", + "sourceFieldKeys": [ + "defender-severity", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Unacknowledged", + "sourceFieldKeys": [ + "status", + ], + "targetRole": "status", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Synthetic programming from 192.0.2.41", + "sourceFieldKeys": [ + "description", + ], + "targetRole": "description", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "alert-count", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "detail-rows", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "unacknowledged-alerts", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "| Id | Acknowledged | First Detection | Last Detection | Last Activity | Source Address | Destination Address | Description | Additional Information | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| A-1 | False | 11/09/2026 10:05:12 | 11/09/2026 10:07:11 | 11/09/2026 10:07:11 | 192.0.2.41 | 192.0.2.21 | Synthetic programming from 192.0.2.41 | 192.0.2.21 over port CIP (44818) |", + "sourceFieldKeys": [ + "alerts", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "[{"fields":{"Id":"A-1","Title":"Synthetic Unauthorized PLC Programming","First Detection":"11/09/2026 10:05:12","Last Detection":"11/09/2026 10:07:11","Last Activity":"11/09/2026 10:07:11","Severity":"Critical","Description":"Synthetic programming from 192.0.2.41","Acknowledged":"False","Additional Information":"192.0.2.21 over port CIP (44818)","Source Address":"192.0.2.41","Destination Address":"192.0.2.21"},"source":{"tableIndex":0,"rowIndex":1}}]", + "sourceFieldKeys": [ + "source-observations", + ], + "targetRole": "evidence", + }, + ], + "originalFields": [ + { + "columnIndex": undefined, + "key": "title", + "label": "Title", + "path": undefined, + "rawValue": "Synthetic Unauthorized PLC Programming", + "value": "Synthetic Unauthorized PLC Programming", + }, + { + "columnIndex": undefined, + "key": "severity", + "label": "Severity", + "path": undefined, + "rawValue": "Critical", + "value": "Critical", + }, + { + "columnIndex": undefined, + "key": "defender-severity", + "label": "Defender Severity", + "path": undefined, + "rawValue": "Critical", + "value": "Critical", + }, + { + "columnIndex": undefined, + "key": "status", + "label": "Status", + "path": undefined, + "rawValue": "Unacknowledged", + "value": "Unacknowledged", + }, + { + "columnIndex": undefined, + "key": "description", + "label": "Description", + "path": undefined, + "rawValue": "Synthetic programming from 192.0.2.41", + "value": "Synthetic programming from 192.0.2.41", + }, + { + "columnIndex": undefined, + "key": "alert-count", + "label": "Alert Count", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "detail-rows", + "label": "Detail Rows", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "unacknowledged-alerts", + "label": "Unacknowledged Alerts", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "alerts", + "label": "Alerts", + "path": undefined, + "rawValue": "| Id | Acknowledged | First Detection | Last Detection | Last Activity | Source Address | Destination Address | Description | Additional Information | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| A-1 | False | 11/09/2026 10:05:12 | 11/09/2026 10:07:11 | 11/09/2026 10:07:11 | 192.0.2.41 | 192.0.2.21 | Synthetic programming from 192.0.2.41 | 192.0.2.21 over port CIP (44818) |", + "value": "| Id | Acknowledged | First Detection | Last Detection | Last Activity | Source Address | Destination Address | Description | Additional Information | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| A-1 | False | 11/09/2026 10:05:12 | 11/09/2026 10:07:11 | 11/09/2026 10:07:11 | 192.0.2.41 | 192.0.2.21 | Synthetic programming from 192.0.2.41 | 192.0.2.21 over port CIP (44818) |", + }, + { + "columnIndex": undefined, + "key": "source-observations", + "label": "Source Observations", + "path": undefined, + "rawValue": "[{"fields":{"Id":"A-1","Title":"Synthetic Unauthorized PLC Programming","First Detection":"11/09/2026 10:05:12","Last Detection":"11/09/2026 10:07:11","Last Activity":"11/09/2026 10:07:11","Severity":"Critical","Description":"Synthetic programming from 192.0.2.41","Acknowledged":"False","Additional Information":"192.0.2.21 over port CIP (44818)","Source Address":"192.0.2.41","Destination Address":"192.0.2.21"},"source":{"tableIndex":0,"rowIndex":1}}]", + "value": [ + { + "fields": { + "Acknowledged": "False", + "Additional Information": "192.0.2.21 over port CIP (44818)", + "Description": "Synthetic programming from 192.0.2.41", + "Destination Address": "192.0.2.21", + "First Detection": "11/09/2026 10:05:12", + "Id": "A-1", + "Last Activity": "11/09/2026 10:07:11", + "Last Detection": "11/09/2026 10:07:11", + "Severity": "Critical", + "Source Address": "192.0.2.41", + "Title": "Synthetic Unauthorized PLC Programming", + }, + "source": { + "rowIndex": 1, + "tableIndex": 0, + }, + }, + ], + }, + ], + "outcome": { + "normalizedValue": "warning", + "originalValue": "Unacknowledged", + }, + "score": undefined, + "severity": { + "normalizedValue": "critical", + "originalValue": "Critical", + }, + "sourceIdentity": { + "canonicalRuleId": "Synthetic Unauthorized PLC Programming", + "originalRuleId": "Synthetic Unauthorized PLC Programming", + "originalTitle": "Synthetic Unauthorized PLC Programming", + "tool": { + "id": "defender-for-iot-alerts", + "name": "Microsoft Defender for IoT Alerts", + }, + }, + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 1, + }, + "status": { + "normalizedValue": "open", + "originalValue": "Unacknowledged", + }, + "technicalExecutionState": { + "normalizedValue": "completed", + "originalValue": "Unacknowledged", + }, + }, + { + "affectedAsset": undefined, + "fieldMappings": [ + { + "decisionSource": "automatic", + "normalizedValue": "Synthetic Internet Connection", + "sourceFieldKeys": [ + "title", + ], + "targetRole": "rule-id", + }, + { + "decisionSource": "automatic", + "normalizedValue": "High", + "sourceFieldKeys": [ + "severity", + ], + "targetRole": "severity", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Major", + "sourceFieldKeys": [ + "defender-severity", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Unacknowledged", + "sourceFieldKeys": [ + "status", + ], + "targetRole": "status", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Synthetic outbound connection", + "sourceFieldKeys": [ + "description", + ], + "targetRole": "description", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "alert-count", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "detail-rows", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "unacknowledged-alerts", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "| Id | Acknowledged | First Detection | Last Detection | Last Activity | Source Address | Destination Address | Description | Additional Information | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| A-3 | False | 13/09/2026 09:00:00 | 13/09/2026 09:30:00 | 13/09/2026 09:30:00 | 00-00-5E-00-53-01 | | Synthetic outbound connection | 198.51.100.7 over port HTTPS (443) |", + "sourceFieldKeys": [ + "alerts", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "[{"fields":{"Id":"A-3","Title":"Synthetic Internet Connection","First Detection":"13/09/2026 09:00:00","Last Detection":"13/09/2026 09:30:00","Last Activity":"13/09/2026 09:30:00","Severity":"Major","Description":"Synthetic outbound connection","Acknowledged":"False","Additional Information":"198.51.100.7 over port HTTPS (443)","Source Address":"00-00-5E-00-53-01","Destination Address":""},"source":{"tableIndex":0,"rowIndex":2}}]", + "sourceFieldKeys": [ + "source-observations", + ], + "targetRole": "evidence", + }, + ], + "originalFields": [ + { + "columnIndex": undefined, + "key": "title", + "label": "Title", + "path": undefined, + "rawValue": "Synthetic Internet Connection", + "value": "Synthetic Internet Connection", + }, + { + "columnIndex": undefined, + "key": "severity", + "label": "Severity", + "path": undefined, + "rawValue": "High", + "value": "High", + }, + { + "columnIndex": undefined, + "key": "defender-severity", + "label": "Defender Severity", + "path": undefined, + "rawValue": "Major", + "value": "Major", + }, + { + "columnIndex": undefined, + "key": "status", + "label": "Status", + "path": undefined, + "rawValue": "Unacknowledged", + "value": "Unacknowledged", + }, + { + "columnIndex": undefined, + "key": "description", + "label": "Description", + "path": undefined, + "rawValue": "Synthetic outbound connection", + "value": "Synthetic outbound connection", + }, + { + "columnIndex": undefined, + "key": "alert-count", + "label": "Alert Count", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "detail-rows", + "label": "Detail Rows", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "unacknowledged-alerts", + "label": "Unacknowledged Alerts", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "alerts", + "label": "Alerts", + "path": undefined, + "rawValue": "| Id | Acknowledged | First Detection | Last Detection | Last Activity | Source Address | Destination Address | Description | Additional Information | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| A-3 | False | 13/09/2026 09:00:00 | 13/09/2026 09:30:00 | 13/09/2026 09:30:00 | 00-00-5E-00-53-01 | | Synthetic outbound connection | 198.51.100.7 over port HTTPS (443) |", + "value": "| Id | Acknowledged | First Detection | Last Detection | Last Activity | Source Address | Destination Address | Description | Additional Information | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| A-3 | False | 13/09/2026 09:00:00 | 13/09/2026 09:30:00 | 13/09/2026 09:30:00 | 00-00-5E-00-53-01 | | Synthetic outbound connection | 198.51.100.7 over port HTTPS (443) |", + }, + { + "columnIndex": undefined, + "key": "source-observations", + "label": "Source Observations", + "path": undefined, + "rawValue": "[{"fields":{"Id":"A-3","Title":"Synthetic Internet Connection","First Detection":"13/09/2026 09:00:00","Last Detection":"13/09/2026 09:30:00","Last Activity":"13/09/2026 09:30:00","Severity":"Major","Description":"Synthetic outbound connection","Acknowledged":"False","Additional Information":"198.51.100.7 over port HTTPS (443)","Source Address":"00-00-5E-00-53-01","Destination Address":""},"source":{"tableIndex":0,"rowIndex":2}}]", + "value": [ + { + "fields": { + "Acknowledged": "False", + "Additional Information": "198.51.100.7 over port HTTPS (443)", + "Description": "Synthetic outbound connection", + "Destination Address": "", + "First Detection": "13/09/2026 09:00:00", + "Id": "A-3", + "Last Activity": "13/09/2026 09:30:00", + "Last Detection": "13/09/2026 09:30:00", + "Severity": "Major", + "Source Address": "00-00-5E-00-53-01", + "Title": "Synthetic Internet Connection", + }, + "source": { + "rowIndex": 2, + "tableIndex": 0, + }, + }, + ], + }, + ], + "outcome": { + "normalizedValue": "warning", + "originalValue": "Unacknowledged", + }, + "score": undefined, + "severity": { + "normalizedValue": "high", + "originalValue": "High", + }, + "sourceIdentity": { + "canonicalRuleId": "Synthetic Internet Connection", + "originalRuleId": "Synthetic Internet Connection", + "originalTitle": "Synthetic Internet Connection", + "tool": { + "id": "defender-for-iot-alerts", + "name": "Microsoft Defender for IoT Alerts", + }, + }, + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 2, + }, + "status": { + "normalizedValue": "open", + "originalValue": "Unacknowledged", + }, + "technicalExecutionState": { + "normalizedValue": "completed", + "originalValue": "Unacknowledged", + }, + }, + { + "affectedAsset": undefined, + "fieldMappings": [ + { + "decisionSource": "automatic", + "normalizedValue": "Synthetic Unused Protocol", + "sourceFieldKeys": [ + "title", + ], + "targetRole": "rule-id", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Low", + "sourceFieldKeys": [ + "severity", + ], + "targetRole": "severity", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Warning", + "sourceFieldKeys": [ + "defender-severity", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Acknowledged", + "sourceFieldKeys": [ + "status", + ], + "targetRole": "status", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Synthetic unused protocol", + "sourceFieldKeys": [ + "description", + ], + "targetRole": "description", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "alert-count", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "detail-rows", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "0", + "sourceFieldKeys": [ + "unacknowledged-alerts", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "| Id | Acknowledged | First Detection | Last Detection | Last Activity | Source Address | Destination Address | Description | Additional Information | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| A-4 | True | 14/09/2026 07:00:00 | 14/09/2026 07:00:00 | 14/09/2026 07:05:00 | 192.0.2.50 | 192.0.2.51 | Synthetic unused protocol | |", + "sourceFieldKeys": [ + "alerts", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "[{"fields":{"Id":"A-4","Title":"Synthetic Unused Protocol","First Detection":"14/09/2026 07:00:00","Last Detection":"14/09/2026 07:00:00","Last Activity":"14/09/2026 07:05:00","Severity":"Warning","Description":"Synthetic unused protocol","Acknowledged":"True","Additional Information":"","Source Address":"192.0.2.50","Destination Address":"192.0.2.51"},"source":{"tableIndex":0,"rowIndex":3}}]", + "sourceFieldKeys": [ + "source-observations", + ], + "targetRole": "evidence", + }, + ], + "originalFields": [ + { + "columnIndex": undefined, + "key": "title", + "label": "Title", + "path": undefined, + "rawValue": "Synthetic Unused Protocol", + "value": "Synthetic Unused Protocol", + }, + { + "columnIndex": undefined, + "key": "severity", + "label": "Severity", + "path": undefined, + "rawValue": "Low", + "value": "Low", + }, + { + "columnIndex": undefined, + "key": "defender-severity", + "label": "Defender Severity", + "path": undefined, + "rawValue": "Warning", + "value": "Warning", + }, + { + "columnIndex": undefined, + "key": "status", + "label": "Status", + "path": undefined, + "rawValue": "Acknowledged", + "value": "Acknowledged", + }, + { + "columnIndex": undefined, + "key": "description", + "label": "Description", + "path": undefined, + "rawValue": "Synthetic unused protocol", + "value": "Synthetic unused protocol", + }, + { + "columnIndex": undefined, + "key": "alert-count", + "label": "Alert Count", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "detail-rows", + "label": "Detail Rows", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "unacknowledged-alerts", + "label": "Unacknowledged Alerts", + "path": undefined, + "rawValue": "0", + "value": 0, + }, + { + "columnIndex": undefined, + "key": "alerts", + "label": "Alerts", + "path": undefined, + "rawValue": "| Id | Acknowledged | First Detection | Last Detection | Last Activity | Source Address | Destination Address | Description | Additional Information | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| A-4 | True | 14/09/2026 07:00:00 | 14/09/2026 07:00:00 | 14/09/2026 07:05:00 | 192.0.2.50 | 192.0.2.51 | Synthetic unused protocol | |", + "value": "| Id | Acknowledged | First Detection | Last Detection | Last Activity | Source Address | Destination Address | Description | Additional Information | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| A-4 | True | 14/09/2026 07:00:00 | 14/09/2026 07:00:00 | 14/09/2026 07:05:00 | 192.0.2.50 | 192.0.2.51 | Synthetic unused protocol | |", + }, + { + "columnIndex": undefined, + "key": "source-observations", + "label": "Source Observations", + "path": undefined, + "rawValue": "[{"fields":{"Id":"A-4","Title":"Synthetic Unused Protocol","First Detection":"14/09/2026 07:00:00","Last Detection":"14/09/2026 07:00:00","Last Activity":"14/09/2026 07:05:00","Severity":"Warning","Description":"Synthetic unused protocol","Acknowledged":"True","Additional Information":"","Source Address":"192.0.2.50","Destination Address":"192.0.2.51"},"source":{"tableIndex":0,"rowIndex":3}}]", + "value": [ + { + "fields": { + "Acknowledged": "True", + "Additional Information": "", + "Description": "Synthetic unused protocol", + "Destination Address": "192.0.2.51", + "First Detection": "14/09/2026 07:00:00", + "Id": "A-4", + "Last Activity": "14/09/2026 07:05:00", + "Last Detection": "14/09/2026 07:00:00", + "Severity": "Warning", + "Source Address": "192.0.2.50", + "Title": "Synthetic Unused Protocol", + }, + "source": { + "rowIndex": 3, + "tableIndex": 0, + }, + }, + ], + }, + ], + "outcome": { + "normalizedValue": "informational", + "originalValue": "Acknowledged", + }, + "score": undefined, + "severity": { + "normalizedValue": "low", + "originalValue": "Low", + }, + "sourceIdentity": { + "canonicalRuleId": "Synthetic Unused Protocol", + "originalRuleId": "Synthetic Unused Protocol", + "originalTitle": "Synthetic Unused Protocol", + "tool": { + "id": "defender-for-iot-alerts", + "name": "Microsoft Defender for IoT Alerts", + }, + }, + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 3, + }, + "status": { + "normalizedValue": "open", + "originalValue": "Acknowledged", + }, + "technicalExecutionState": { + "normalizedValue": "completed", + "originalValue": "Acknowledged", + }, + }, + ], + "observations": [], + "preview": { + "mappingsToConfirm": 0, + "recordCount": 3, + "selected": true, + }, + "recognition": "defender-for-iot-alerts", + "reportSections": [], + "scores": [ + { + "assessed": 2, + "assessmentId": "assessment-protected-dummy-import", + "comparisonKey": undefined, + "contentSha256": "c554f60079cf276f21c90b2b9c0b4b3fe6976945fd726b6d1ba1331391e3c184", + "deviations": 2, + "eligible": 3, + "evaluationScopeKey": "["[\\"Synthetic Internet Connection\\",\\"evaluated\\"]","[\\"Synthetic Unauthorized PLC Programming\\",\\"evaluated\\"]","[\\"Synthetic Unused Protocol\\",\\"informational\\"]"]", + "importFileIds": [ + "file-protected-dummy-import-0", + ], + "issues": [ + "Evidence present, not scoreable", + "Incomplete assessment", + "Scan identity or time not verified", + "Scanner version unavailable", + ], + "method": "unavailable", + "nativeProperty": undefined, + "nativeValue": undefined, + "observedAt": undefined, + "occurrenceIds": [ + "occurrence-protected-dummy-import-0-0", + "occurrence-protected-dummy-import-0-1", + "occurrence-protected-dummy-import-0-2", + ], + "openOccurrenceIds": [ + "occurrence-protected-dummy-import-0-0", + "occurrence-protected-dummy-import-0-1", + ], + "passed": 0, + "policy": { + "defaultWeight": 1, + "version": "1.0.0", + "weights": { + "cis-cat": 1, + "group-policy": 1, + "maester": 1, + "pingcastle": 1, + "purple-knight": 1, + }, + }, + "processingKey": "[null,"defender-for-iot-alerts","1.6.0","1.2.0","1.0.0","1.0.0"]", + "resultSignature": "["[\\"Synthetic Internet Connection\\",null,\\"warning\\"]","[\\"Synthetic Unauthorized PLC Programming\\",null,\\"warning\\"]","[\\"Synthetic Unused Protocol\\",null,\\"informational\\"]"]", + "runIds": [ + "run-protected-dummy-import", + ], + "score": undefined, + "toolId": "defender-for-iot-alerts", + "toolName": "Microsoft Defender for IoT Alerts", + }, + ], + "tables": [ + { + "columnCount": 11, + "hidden": undefined, + "index": 0, + "name": "CSV", + "rowCount": 3, + "rows": undefined, + }, + ], +} +`; + +exports[`protected scanner import contracts (synthetic data only) > 'defender-for-iot-vulnerabilities-csv' retains recognition, preview and approved canonical results in browser and backend 1`] = ` +{ + "approved": { + "recordCount": 3, + "selected": true, + }, + "definitions": [ + { + "canonicalKey": "defender-for-iot-vulnerabilities:cve-2099-1001", + "category": undefined, + "description": undefined, + "frameworkReferences": [], + "identity": { + "durable": true, + "evidence": [ + "Werkzeugkennung und gemappte native Regel-ID sind vorhanden.", + ], + "strategy": "tool-rule-id", + "version": 1, + }, + "recommendation": undefined, + "title": "CVE-2099-1001", + }, + { + "canonicalKey": "defender-for-iot-vulnerabilities:cve-2099-1002", + "category": undefined, + "description": undefined, + "frameworkReferences": [], + "identity": { + "durable": true, + "evidence": [ + "Werkzeugkennung und gemappte native Regel-ID sind vorhanden.", + ], + "strategy": "tool-rule-id", + "version": 1, + }, + "recommendation": undefined, + "title": "CVE-2099-1002", + }, + { + "canonicalKey": "defender-for-iot-vulnerabilities:cve-2099-1005", + "category": undefined, + "description": undefined, + "frameworkReferences": [], + "identity": { + "durable": true, + "evidence": [ + "Werkzeugkennung und gemappte native Regel-ID sind vorhanden.", + ], + "strategy": "tool-rule-id", + "version": 1, + }, + "recommendation": undefined, + "title": "CVE-2099-1005", + }, + ], + "evidence": [ + { + "finding": { + "canonicalRuleId": "CVE-2099-1001", + "originalRuleId": "CVE-2099-1001", + "originalTitle": "CVE-2099-1001", + "tool": { + "id": "defender-for-iot-vulnerabilities", + "name": "Microsoft Defender for IoT", + }, + }, + "kind": "object", + "sourceFieldKeys": [ + "source-observations", + ], + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 1, + }, + "value": [ + { + "additionalSourceFields": { + "CVSS Score": "9.1", + "Component": "Synthetic component", + "Device ID": "DEV-OT-001", + "Status": "Open", + }, + "cve": "CVE-2099-1001", + "description": "", + "ip": "192.0.2.21", + "name": "PLC-LINE1-01", + "sensor": "SENS-RB-01", + "severity": "Critical", + "site": "Riverbend Plant", + "source": { + "rowIndex": 1, + "tableIndex": 0, + }, + }, + ], + }, + { + "finding": { + "canonicalRuleId": "CVE-2099-1002", + "originalRuleId": "CVE-2099-1002", + "originalTitle": "CVE-2099-1002", + "tool": { + "id": "defender-for-iot-vulnerabilities", + "name": "Microsoft Defender for IoT", + }, + }, + "kind": "object", + "sourceFieldKeys": [ + "source-observations", + ], + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 2, + }, + "value": [ + { + "additionalSourceFields": { + "CVSS Score": "7.5", + "Component": "Synthetic component", + "Device ID": "DEV-OT-002", + "Status": "Open", + }, + "cve": "CVE-2099-1002", + "description": "", + "ip": "192.0.2.31", + "name": "HMI-LINE1-01", + "sensor": "SENS-RB-01", + "severity": "High", + "site": "Riverbend Plant", + "source": { + "rowIndex": 2, + "tableIndex": 0, + }, + }, + ], + }, + { + "finding": { + "canonicalRuleId": "CVE-2099-1005", + "originalRuleId": "CVE-2099-1005", + "originalTitle": "CVE-2099-1005", + "tool": { + "id": "defender-for-iot-vulnerabilities", + "name": "Microsoft Defender for IoT", + }, + }, + "kind": "object", + "sourceFieldKeys": [ + "source-observations", + ], + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 3, + }, + "value": [ + { + "additionalSourceFields": { + "CVSS Score": "8.2", + "Component": "Synthetic component", + "Device ID": "DEV-OT-004", + "Status": "Mitigation planned", + }, + "cve": "CVE-2099-1005", + "description": "", + "ip": "192.0.2.51", + "name": "HISTORIAN-01", + "sensor": "SENS-RB-01", + "severity": "High", + "site": "Riverbend Plant", + "source": { + "rowIndex": 3, + "tableIndex": 0, + }, + }, + ], + }, + ], + "findings": [ + { + "affectedAsset": { + "normalizedValue": "1", + "originalValue": 1, + }, + "fieldMappings": [ + { + "decisionSource": "automatic", + "normalizedValue": "CVE-2099-1001", + "sourceFieldKeys": [ + "cve", + ], + "targetRole": "rule-id", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "affected-device-count", + ], + "targetRole": "asset", + }, + { + "decisionSource": "automatic", + "normalizedValue": "Critical", + "sourceFieldKeys": [ + "severity", + ], + "targetRole": "severity", + }, + { + "decisionSource": "automatic", + "normalizedValue": "", + "sourceFieldKeys": [ + "description", + ], + "targetRole": "description", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "observation-count", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "| Name | IP Address | Scanner | Site | +| --- | --- | --- | --- | +| PLC-LINE1-01 | 192.0.2.21 | SENS-RB-01 | Riverbend Plant |", + "sourceFieldKeys": [ + "affected-devices", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "[{"name":"PLC-LINE1-01","ip":"192.0.2.21","site":"Riverbend Plant","sensor":"SENS-RB-01","cve":"CVE-2099-1001","severity":"Critical","description":"","additionalSourceFields":{"Device ID":"DEV-OT-001","CVSS Score":"9.1","Component":"Synthetic component","Status":"Open"},"source":{"tableIndex":0,"rowIndex":1}}]", + "sourceFieldKeys": [ + "source-observations", + ], + "targetRole": "evidence", + }, + ], + "originalFields": [ + { + "columnIndex": undefined, + "key": "cve", + "label": "CVE", + "path": undefined, + "rawValue": "CVE-2099-1001", + "value": "CVE-2099-1001", + }, + { + "columnIndex": undefined, + "key": "affected-device-count", + "label": "Affected Device Count", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "severity", + "label": "Severity", + "path": undefined, + "rawValue": "Critical", + "value": "Critical", + }, + { + "columnIndex": undefined, + "key": "description", + "label": "Description", + "path": undefined, + "rawValue": "", + "value": "", + }, + { + "columnIndex": undefined, + "key": "observation-count", + "label": "Observation Count", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "affected-devices", + "label": "Affected Devices", + "path": undefined, + "rawValue": "| Name | IP Address | Scanner | Site | +| --- | --- | --- | --- | +| PLC-LINE1-01 | 192.0.2.21 | SENS-RB-01 | Riverbend Plant |", + "value": "| Name | IP Address | Scanner | Site | +| --- | --- | --- | --- | +| PLC-LINE1-01 | 192.0.2.21 | SENS-RB-01 | Riverbend Plant |", + }, + { + "columnIndex": undefined, + "key": "source-observations", + "label": "Source Observations", + "path": undefined, + "rawValue": "[{"name":"PLC-LINE1-01","ip":"192.0.2.21","site":"Riverbend Plant","sensor":"SENS-RB-01","cve":"CVE-2099-1001","severity":"Critical","description":"","additionalSourceFields":{"Device ID":"DEV-OT-001","CVSS Score":"9.1","Component":"Synthetic component","Status":"Open"},"source":{"tableIndex":0,"rowIndex":1}}]", + "value": [ + { + "additionalSourceFields": { + "CVSS Score": "9.1", + "Component": "Synthetic component", + "Device ID": "DEV-OT-001", + "Status": "Open", + }, + "cve": "CVE-2099-1001", + "description": "", + "ip": "192.0.2.21", + "name": "PLC-LINE1-01", + "sensor": "SENS-RB-01", + "severity": "Critical", + "site": "Riverbend Plant", + "source": { + "rowIndex": 1, + "tableIndex": 0, + }, + }, + ], + }, + ], + "outcome": { + "normalizedValue": "warning", + "originalValue": "", + }, + "score": undefined, + "severity": { + "normalizedValue": "critical", + "originalValue": "Critical", + }, + "sourceIdentity": { + "canonicalRuleId": "CVE-2099-1001", + "originalRuleId": "CVE-2099-1001", + "originalTitle": "CVE-2099-1001", + "tool": { + "id": "defender-for-iot-vulnerabilities", + "name": "Microsoft Defender for IoT", + }, + }, + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 1, + }, + "status": { + "normalizedValue": "unknown", + "originalValue": "", + }, + "technicalExecutionState": { + "normalizedValue": "completed", + "originalValue": "", + }, + }, + { + "affectedAsset": { + "normalizedValue": "1", + "originalValue": 1, + }, + "fieldMappings": [ + { + "decisionSource": "automatic", + "normalizedValue": "CVE-2099-1002", + "sourceFieldKeys": [ + "cve", + ], + "targetRole": "rule-id", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "affected-device-count", + ], + "targetRole": "asset", + }, + { + "decisionSource": "automatic", + "normalizedValue": "High", + "sourceFieldKeys": [ + "severity", + ], + "targetRole": "severity", + }, + { + "decisionSource": "automatic", + "normalizedValue": "", + "sourceFieldKeys": [ + "description", + ], + "targetRole": "description", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "observation-count", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "| Name | IP Address | Scanner | Site | +| --- | --- | --- | --- | +| HMI-LINE1-01 | 192.0.2.31 | SENS-RB-01 | Riverbend Plant |", + "sourceFieldKeys": [ + "affected-devices", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "[{"name":"HMI-LINE1-01","ip":"192.0.2.31","site":"Riverbend Plant","sensor":"SENS-RB-01","cve":"CVE-2099-1002","severity":"High","description":"","additionalSourceFields":{"Device ID":"DEV-OT-002","CVSS Score":"7.5","Component":"Synthetic component","Status":"Open"},"source":{"tableIndex":0,"rowIndex":2}}]", + "sourceFieldKeys": [ + "source-observations", + ], + "targetRole": "evidence", + }, + ], + "originalFields": [ + { + "columnIndex": undefined, + "key": "cve", + "label": "CVE", + "path": undefined, + "rawValue": "CVE-2099-1002", + "value": "CVE-2099-1002", + }, + { + "columnIndex": undefined, + "key": "affected-device-count", + "label": "Affected Device Count", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "severity", + "label": "Severity", + "path": undefined, + "rawValue": "High", + "value": "High", + }, + { + "columnIndex": undefined, + "key": "description", + "label": "Description", + "path": undefined, + "rawValue": "", + "value": "", + }, + { + "columnIndex": undefined, + "key": "observation-count", + "label": "Observation Count", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "affected-devices", + "label": "Affected Devices", + "path": undefined, + "rawValue": "| Name | IP Address | Scanner | Site | +| --- | --- | --- | --- | +| HMI-LINE1-01 | 192.0.2.31 | SENS-RB-01 | Riverbend Plant |", + "value": "| Name | IP Address | Scanner | Site | +| --- | --- | --- | --- | +| HMI-LINE1-01 | 192.0.2.31 | SENS-RB-01 | Riverbend Plant |", + }, + { + "columnIndex": undefined, + "key": "source-observations", + "label": "Source Observations", + "path": undefined, + "rawValue": "[{"name":"HMI-LINE1-01","ip":"192.0.2.31","site":"Riverbend Plant","sensor":"SENS-RB-01","cve":"CVE-2099-1002","severity":"High","description":"","additionalSourceFields":{"Device ID":"DEV-OT-002","CVSS Score":"7.5","Component":"Synthetic component","Status":"Open"},"source":{"tableIndex":0,"rowIndex":2}}]", + "value": [ + { + "additionalSourceFields": { + "CVSS Score": "7.5", + "Component": "Synthetic component", + "Device ID": "DEV-OT-002", + "Status": "Open", + }, + "cve": "CVE-2099-1002", + "description": "", + "ip": "192.0.2.31", + "name": "HMI-LINE1-01", + "sensor": "SENS-RB-01", + "severity": "High", + "site": "Riverbend Plant", + "source": { + "rowIndex": 2, + "tableIndex": 0, + }, + }, + ], + }, + ], + "outcome": { + "normalizedValue": "warning", + "originalValue": "", + }, + "score": undefined, + "severity": { + "normalizedValue": "high", + "originalValue": "High", + }, + "sourceIdentity": { + "canonicalRuleId": "CVE-2099-1002", + "originalRuleId": "CVE-2099-1002", + "originalTitle": "CVE-2099-1002", + "tool": { + "id": "defender-for-iot-vulnerabilities", + "name": "Microsoft Defender for IoT", + }, + }, + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 2, + }, + "status": { + "normalizedValue": "unknown", + "originalValue": "", + }, + "technicalExecutionState": { + "normalizedValue": "completed", + "originalValue": "", + }, + }, + { + "affectedAsset": { + "normalizedValue": "1", + "originalValue": 1, + }, + "fieldMappings": [ + { + "decisionSource": "automatic", + "normalizedValue": "CVE-2099-1005", + "sourceFieldKeys": [ + "cve", + ], + "targetRole": "rule-id", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "affected-device-count", + ], + "targetRole": "asset", + }, + { + "decisionSource": "automatic", + "normalizedValue": "High", + "sourceFieldKeys": [ + "severity", + ], + "targetRole": "severity", + }, + { + "decisionSource": "automatic", + "normalizedValue": "", + "sourceFieldKeys": [ + "description", + ], + "targetRole": "description", + }, + { + "decisionSource": "automatic", + "normalizedValue": "1", + "sourceFieldKeys": [ + "observation-count", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "| Name | IP Address | Scanner | Site | +| --- | --- | --- | --- | +| HISTORIAN-01 | 192.0.2.51 | SENS-RB-01 | Riverbend Plant |", + "sourceFieldKeys": [ + "affected-devices", + ], + "targetRole": "additional", + }, + { + "decisionSource": "automatic", + "normalizedValue": "[{"name":"HISTORIAN-01","ip":"192.0.2.51","site":"Riverbend Plant","sensor":"SENS-RB-01","cve":"CVE-2099-1005","severity":"High","description":"","additionalSourceFields":{"Device ID":"DEV-OT-004","CVSS Score":"8.2","Component":"Synthetic component","Status":"Mitigation planned"},"source":{"tableIndex":0,"rowIndex":3}}]", + "sourceFieldKeys": [ + "source-observations", + ], + "targetRole": "evidence", + }, + ], + "originalFields": [ + { + "columnIndex": undefined, + "key": "cve", + "label": "CVE", + "path": undefined, + "rawValue": "CVE-2099-1005", + "value": "CVE-2099-1005", + }, + { + "columnIndex": undefined, + "key": "affected-device-count", + "label": "Affected Device Count", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "severity", + "label": "Severity", + "path": undefined, + "rawValue": "High", + "value": "High", + }, + { + "columnIndex": undefined, + "key": "description", + "label": "Description", + "path": undefined, + "rawValue": "", + "value": "", + }, + { + "columnIndex": undefined, + "key": "observation-count", + "label": "Observation Count", + "path": undefined, + "rawValue": "1", + "value": 1, + }, + { + "columnIndex": undefined, + "key": "affected-devices", + "label": "Affected Devices", + "path": undefined, + "rawValue": "| Name | IP Address | Scanner | Site | +| --- | --- | --- | --- | +| HISTORIAN-01 | 192.0.2.51 | SENS-RB-01 | Riverbend Plant |", + "value": "| Name | IP Address | Scanner | Site | +| --- | --- | --- | --- | +| HISTORIAN-01 | 192.0.2.51 | SENS-RB-01 | Riverbend Plant |", + }, + { + "columnIndex": undefined, + "key": "source-observations", + "label": "Source Observations", + "path": undefined, + "rawValue": "[{"name":"HISTORIAN-01","ip":"192.0.2.51","site":"Riverbend Plant","sensor":"SENS-RB-01","cve":"CVE-2099-1005","severity":"High","description":"","additionalSourceFields":{"Device ID":"DEV-OT-004","CVSS Score":"8.2","Component":"Synthetic component","Status":"Mitigation planned"},"source":{"tableIndex":0,"rowIndex":3}}]", + "value": [ + { + "additionalSourceFields": { + "CVSS Score": "8.2", + "Component": "Synthetic component", + "Device ID": "DEV-OT-004", + "Status": "Mitigation planned", + }, + "cve": "CVE-2099-1005", + "description": "", + "ip": "192.0.2.51", + "name": "HISTORIAN-01", + "sensor": "SENS-RB-01", + "severity": "High", + "site": "Riverbend Plant", + "source": { + "rowIndex": 3, + "tableIndex": 0, + }, + }, + ], + }, + ], + "outcome": { + "normalizedValue": "warning", + "originalValue": "", + }, + "score": undefined, + "severity": { + "normalizedValue": "high", + "originalValue": "High", + }, + "sourceIdentity": { + "canonicalRuleId": "CVE-2099-1005", + "originalRuleId": "CVE-2099-1005", + "originalTitle": "CVE-2099-1005", + "tool": { + "id": "defender-for-iot-vulnerabilities", + "name": "Microsoft Defender for IoT", + }, + }, + "sourcePosition": { + "cellReference": undefined, + "columnIndex": undefined, + "elementSelector": undefined, + "objectPath": undefined, + "rowIndex": 3, + }, + "status": { + "normalizedValue": "unknown", + "originalValue": "", + }, + "technicalExecutionState": { + "normalizedValue": "completed", + "originalValue": "", + }, + }, + ], + "observations": [], + "preview": { + "mappingsToConfirm": 0, + "recordCount": 3, + "selected": true, + }, + "recognition": "defender-for-iot-vulnerabilities", + "reportSections": [], + "scores": [ + { + "assessed": 3, + "assessmentId": "assessment-protected-dummy-import", + "comparisonKey": undefined, + "contentSha256": "cee1fb7363f1f065a89341b755a27ec9727866d60dc4bc46089561582eda1d8e", + "deviations": 3, + "eligible": 3, + "evaluationScopeKey": "["[\\"CVE-2099-1001\\",\\"evaluated\\"]","[\\"CVE-2099-1002\\",\\"evaluated\\"]","[\\"CVE-2099-1005\\",\\"evaluated\\"]"]", + "importFileIds": [ + "file-protected-dummy-import-0", + ], + "issues": [ + "Evidence present, not scoreable", + "Scan identity or time not verified", + "Scanner version unavailable", + ], + "method": "unavailable", + "nativeProperty": undefined, + "nativeValue": undefined, + "observedAt": undefined, + "occurrenceIds": [ + "occurrence-protected-dummy-import-0-0", + "occurrence-protected-dummy-import-0-1", + "occurrence-protected-dummy-import-0-2", + ], + "openOccurrenceIds": [ + "occurrence-protected-dummy-import-0-0", + "occurrence-protected-dummy-import-0-1", + "occurrence-protected-dummy-import-0-2", + ], + "passed": 0, + "policy": { + "defaultWeight": 1, + "version": "1.0.0", + "weights": { + "cis-cat": 1, + "group-policy": 1, + "maester": 1, + "pingcastle": 1, + "purple-knight": 1, + }, + }, + "processingKey": "[null,"defender-for-iot-vulnerabilities","1.2.0","1.0.0","1.1.0","1.0.0"]", + "resultSignature": "["[\\"CVE-2099-1001\\",\\"1\\",\\"warning\\"]","[\\"CVE-2099-1002\\",\\"1\\",\\"warning\\"]","[\\"CVE-2099-1005\\",\\"1\\",\\"warning\\"]"]", + "runIds": [ + "run-protected-dummy-import", + ], + "score": undefined, + "toolId": "defender-for-iot", + "toolName": "Microsoft Defender for IoT", + }, + ], + "tables": [ + { + "columnCount": 10, + "hidden": undefined, + "index": 0, + "name": "CSV", + "rowCount": 3, + "rows": undefined, + }, + ], +} +`; + exports[`protected scanner import contracts (synthetic data only) > 'generic-csv' retains recognition, preview and approved canonical results in browser and backend 1`] = ` { "approved": { diff --git a/tests/importers/import-contracts.json b/tests/importers/import-contracts.json index db38f20..359f6f8 100644 --- a/tests/importers/import-contracts.json +++ b/tests/importers/import-contracts.json @@ -196,5 +196,27 @@ "mediaType": "text/html", "sha256": "2a080e0facc21626b51b91305e0163382d63e6bc24a8d26d0312300d580afe56", "variant": "original" + }, + { + "id": "defender-for-iot-vulnerabilities-csv", + "fixture": "fixtures/defender-for-iot/cves.synthetic.csv", + "tool": "defender-for-iot-vulnerabilities", + "format": "CSV", + "findings": 3, + "fileName": "cves.csv", + "mediaType": "text/csv", + "sha256": "cee1fb7363f1f065a89341b755a27ec9727866d60dc4bc46089561582eda1d8e", + "variant": "original" + }, + { + "id": "defender-for-iot-alerts-csv", + "fixture": "fixtures/defender-for-iot-alerts/alerts.synthetic.csv", + "tool": "defender-for-iot-alerts", + "format": "CSV", + "findings": 3, + "fileName": "alerts.csv", + "mediaType": "text/csv", + "sha256": "c554f60079cf276f21c90b2b9c0b4b3fe6976945fd726b6d1ba1331391e3c184", + "variant": "original" } ] diff --git a/tests/server/assessor-http-server.test.ts b/tests/server/assessor-http-server.test.ts index 582106d..6088e4d 100644 --- a/tests/server/assessor-http-server.test.ts +++ b/tests/server/assessor-http-server.test.ts @@ -60,7 +60,7 @@ describe('local ASSESSOR HTTP server', () => { const health = await fetch(`${baseUrl}/api/v1/health`); expect(await health.json()).toEqual({ - status: 'ok', application: 'ASSESSOR', productVersion: '2.24.2', apiVersion: 1, repositoryRevision: 0, persistence: 'memory' + status: 'ok', application: 'ASSESSOR', productVersion: '2.25.0', apiVersion: 1, repositoryRevision: 0, persistence: 'memory' }); expect(health.headers.get('access-control-allow-origin')).toBeNull(); });