Neuer Stand Assessor #1

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

View File

@ -1198,6 +1198,8 @@ Abnahmekriterien:
Status: in Arbeit
Aktueller verifizierter Zusatzstatus vom 2026-08-26: Die Freigabegrenze des weiterhin fachlich leeren Policy-Evaluation-Regelkatalogs validiert nun alle Runtime-Enumwerte fuer Auslieferungsmechanismus, Baseline-Disposition, Comparator und Severity, blockiert ueberlappende Selektoren beziehungsweise mehrdeutige Regeln und lehnt widerspruechlich wiederverwendete Risiko- oder Abhaengigkeits-IDs fail-closed ab. Es wurden keine Policyempfehlung und keine Severity erfunden oder freigegeben. Der lokale private Artefaktspeicher wurde vom veralteten fremden Benutzerpfad auf den aktuellen LocalAppData-Pfad ausserhalb des Webroots korrigiert; Backend, App und Speicherroot sind inhaltsfrei technisch erreichbar. Neun neue Katalogregressionen, 25 fokussierte GPO-/Katalogtests, die Gesamtsuite mit 764 bestandenen und 8 uebersprungenen optionalen PostgreSQL-Tests, Local-only- und Designpruefung sowie Client-/Server-Produktionsbuild sind erfolgreich. Keine API-, SQL- oder Persistenzaenderung; Phase 13B bleibt in Arbeit.
Aktueller verifizierter Zusatzstatus vom 2026-08-22: Der globale Microsoft-Security-Baselinekatalog besitzt nun einen bestaetigungspflichtigen Delete-Pfad durch UI, Same-Origin-API, Service sowie Local- und PostgreSQL-Repository. Nur nicht von Policy-Evaluationen referenzierte Pakete duerfen geloescht werden; Klassifikationen und gespeicherte Analysen werden zusammen mit dem Katalogeintrag entfernt, das private Originalobjekt anschliessend best-effort bereinigt. Keine SQL-Migration; Phase 13B bleibt in Arbeit.
Ziele:
@ -1374,6 +1376,7 @@ Aktualisierung nach dem Schritt-11-Orchestrierungsvertikalschnitt vom 2026-08-04
| 2026-09-11 | ASSESSOR erweitert den unveraenderten externen `result_type`-Katalog additiv um `vulnerability_finding`. Defender-for-IoT-CVEs erhalten bei fehlendem Quellstatus deklarativ `outcome=warning`; sie sind damit Abweichungen, bleiben aber `is_ioe=false`. `environment-observation` bleibt eine Herkunftsart mit `result_type=informational`. | Eine konkrete CVE auf betroffenen Devices ist weder Compliance-Check noch Policy-Abweichung oder blosse Inventarinformation. `exposure_finding` wuerde die vorhandene Exposure-State-/IOE-Semantik unnoetig auf Schwachstellen ausdehnen. Der eigene Typ trennt Schwachstellenbefund, Dringlichkeit und bestaetigte Exposure weiterhin sauber. |
| 2026-09-10 | NVD `configurations` bleiben als top-level Konfigurationsgruppen erhalten. Vulnerable CPE-Matches bilden die betroffene Software je Gruppe; nicht vulnerable Matches derselben Gruppe werden getrennt als `Running on/with` gespeichert und dargestellt. Das Enrichment-Recordformat ist dafuer Version 2; Version-1-Flachsaetze bleiben lesbar, muessen fuer die verlustfreie Gruppierung aber erneut von NVD abgerufen werden. | Die NVD-Oberflaeche und API beschreiben eine logische Konfiguration aus betroffener Software und ihren Laufzeit-/Hardwarebedingungen. Ein flaches Match-Array ueberzaehlt Konfigurationen und verliert genau diese Beziehung. Die Gruppierung muss deshalb vor der Persistenz im externen Referenzadapter erhalten werden. |
| 2026-09-10 | CVE-Enrichment ist ein globaler, nach normalisierter CVE-ID identifizierter Referenzbestand und weder Bestandteil der tenant-/timeslotgebundenen FindingOccurrence noch der werkzeuggebundenen, durch Reimport ersetzbaren FindingDefinition. Defender-Import und NVD-Enrichment bleiben getrennt; nur eine ausdrueckliche Aktion fuer eine bereits gespeicherte Defender-CVE darf den festen NVD-Endpunkt abfragen. | Beschreibung, CVSS-Metriken, CWE, Referenzen und betroffene Konfigurationen beschreiben die CVE selbst. Ein gemeinsamer Datensatz vermeidet redundante, auseinanderlaufende Kopien und macht vorhandene Anreicherung fuer spaetere Mandanten automatisch wiederverwendbar, waehrend der Import ohne Netzabhaengigkeit reproduzierbar bleibt. |
| 2026-08-26 | Ein Policy-Evaluation-Regelkatalog darf den Status `released` nur tragen, wenn seine Runtime-Vertraege geschlossen validiert sind und fuer dieselbe technische Identitaet, denselben Auslieferungsmechanismus und ueberlappende Produkt-/Versions-/Rollen-/Scope-Kontexte hoechstens eine Regel aufloesbar ist. Wiederverwendete Risiko- und Abhaengigkeits-IDs muessen katalogweit dieselbe Definition besitzen. | Der kuenftige fachlich kuratierte Katalog wird an einer sicherheitsrelevanten Finding-Grenze ausgefuehrt. TypeScript-Typen allein schuetzen weder geladene noch historisch persistierte Runtime-Daten; Mehrdeutigkeit oder unbekannte Enumwerte muessen deshalb vor jeder Freigabe fail-closed blockieren. |
| 2026-08-25 | Die appweite persistente Markierungs- und Auswahlfarbe ist auf ausdrueckliche Nutzerfestlegung immer Gelb und wird in beiden Themes ueber `--marking-color` gefuehrt. Kachelauswahl, Auswahlrahmen/-marker, gemerkte Eintraege und Graphauswahl verwenden diesen Token. Blau bleibt Tastaturfokus; Gruen, Rot, Orange und Severitytoene bleiben fachliche Statussignale. | Ein einzelner semantischer Markierungston verhindert violette oder skinabhaengig wechselnde Auswahlkonturen wie im Datahub-Screenshot und bewahrt gleichzeitig die Bedeutung bestehender Statusfarben. |
| 2026-08-25 | Die unmittelbar zuvor fuer Client Info festgelegte flache SOC-Kachel ist auf ausdrueckliche Nutzerfestlegung der neue appweite Kachelstandard: keine sichtbare Normalzustandskontur, kein Radius und kein Schatten; Hover veraendert nur die Flaeche. Fokus, Auswahl und kleine Statusakzente bleiben funktionale Ausnahmen. Die gemeinsamen `--tile-*`-Tokens und Projektregeln tragen diesen Vertrag. | Ein zentraler Standard verhindert, dass neue oder bestehende Ansichten erneut weisse Konturen, Rundungen oder schwebende Schatten einfuehren. Flaechenkontrast und gemeinsamer Abstand erzeugen die gewuenschte ruhige, dichte SOC-Geometrie. |
| 2026-08-25 | Das quadratische Client-Info-Raster uebernimmt die flache Kartenoberflaeche der Nutzerreferenz: regulaere Kennzahl- und Inhaltskacheln haben keine Aussenkontur, keinen Schatten, den zentralen Nullradius und keine dekorative Trennlinie unter dem Kartentitel. Farbige Fokus- und Auswahlkonturen bleiben als funktionale Zustaende erhalten. | In der Referenz werden Karten allein durch Flaechenkontrast und dunkle Zwischenraeume getrennt. Die bisherige helle Kontur, Rundung und Schattenkante erzeugten eine abweichende, unruhige zweite Geometrieebene. |
@ -1646,6 +1649,7 @@ Diese Entscheidungen werden erst getroffen, wenn die jeweilige Phase ansteht ode
| NVD kann nicht erreichbar oder rate-limitiert sein, unerwartet grosse Antworten liefern oder externe Referenz-URLs enthalten; ein API-Schluessel darf nicht an Browser oder Logs gelangen. Eine automatische Anreicherung waehrend des Imports wuerde zudem dessen Reproduzierbarkeit und Verfuegbarkeit an einen externen Dienst koppeln. | Enrichment ausschliesslich separat und nutzerausgeloest im Backend ausfuehren, nur exakt gespeicherte Defender-CVE-IDs akzeptieren, Zielorigin und API-Pfad fest verdrahten sowie Timeout und 8-MiB-Antwortgrenze erzwingen. Den optionalen Schluessel nur aus `NVD_API_KEY` als Serverheader senden, Antwortfelder normalisieren, Referenzen nur als HTTP(S)-Links darstellen und Importfehler strikt vom Enrichment-Ergebnis trennen. |
| Defender-for-IoT-Summarys koennen Severity-Zahlen ohne einzelne CVEs enthalten; wiederholte Sensorzeilen koennen dieselbe Device-CVE-Beziehung mehrfach melden, und pseudonymisierte oder wiederverwendete Adressen koennen Identitaeten mehrdeutig machen. | Aggregate ohne Detail niemals in Findings umwandeln, Device-mal-CVE vor der kanonischen Projektion deduplizieren, alle Sensorzeilen als Evidenz erhalten, Identitaet nach der dokumentierten MAC-/IP-/Name-Site-Reihenfolge bilden und Konflikte beziehungsweise Reconciliation-Abweichungen diagnostizieren statt still zusammenzufuehren. |
| Ein gemischtes All-Records-Blatt kann den generischen Titel `Name` auf alle Scanner anwenden und dadurch bei Defender die Device-IP statt der CVE anzeigen; zugleich kann eine rein profilgebundene Client-Info-Sonderansicht in gemischten Timeslots verschwinden. | Titel im gemischten Blatt je Datensatz aus dessen `Source: Scan`-Provenienz und deklarativem Werkzeugprofil bestimmen. Die allgemeine Client-Info-Uebersicht beibehalten und die Defender-Projektion bei vorhandenen Defender-Quellblaettern als eigenen Tab anbieten. |
| Ein formal als `released` markierter Policy-Regelkatalog enthaelt unbekannte Runtime-Enumwerte, ueberlappende Kontextselektoren, mehrdeutige Regeln oder widerspruechlich wiederverwendete Risiko-/Abhaengigkeits-IDs und erzeugt dadurch nicht reproduzierbare beziehungsweise fachlich falsche Evaluationen oder Findings. | Die zentrale Katalogvalidierung prueft Auslieferungsmechanismen, Dispositionen, Comparatorvarianten und -modi, Severity, Kontextueberlappung sowie katalogweite ID-Definitionen geschlossen und fail-closed. Jede spaetere fachliche Katalogerweiterung benoetigt positive und negative synthetische Freigaberegressionen; der produktive Katalog bleibt bis zur fachlichen Kuratierung leer. |
| Auswahl, Fokus und fachlicher Status koennen erneut farblich vermischt werden, wenn Komponenten direkt Akzent-, Fokus- oder Severitytokens fuer eine persistente Markierung verwenden. | Persistente Auswahl ausschliesslich an `--marking-color` binden und in beiden Themes auf denselben Gelbwert regressionspruefen. `--focus-ring` bleibt nur fuer Tastaturfokus; Online-, Fehler-, Workflow- und Severityfarben bleiben fachlich getrennt. |
| Einzelansichten koennten trotz flacher gemeinsamer Tokens im Hover erneut dekorative Konturen setzen oder den Standard mit lokalen Radien und Schatten umgehen. | Appweite Projektregel und Regression an die flachen `--tile-*`-Tokens binden; normale und Hover-Zustaende getrennt von Fokus, Auswahl und Statusakzenten pruefen. Lokale Kachelgeometrie bleibt unzulaessig. |
| Kontur- und schattenlose Client-Info-Kacheln koennen bei spaeteren Themeaenderungen ihren Flaechenkontrast zum Dashboardgrund verlieren; interaktive Kacheln koennten ohne eigene Zustandsmarkierung schwer erkennbar werden. | Karten- und Grundflaeche ueber die bestehenden Theme-Tokens getrennt halten, Hover weiterhin ueber die Hoverflaeche zeigen und Fokus sowie Auswahl mit den vorhandenen farbigen Konturen sichern. Die Dashboardregression prueft den flachen Normalzustand getrennt von diesen Funktionszustaenden. |
@ -1853,6 +1857,7 @@ Diese Entscheidungen werden erst getroffen, wenn die jeweilige Phase ansteht ode
| 2026-09-10 | Phasen 7/13A fuer gemischte Defender-Timeslots korrigiert: Work bestimmt den Titel jedes Datensatzes anhand seiner Scannerprovenienz und des deklarativen Titel-Headers, sodass Defender-Vorkommen im gemeinsamen All-Records-Blatt die CVE statt Device-Name oder IP zeigen. Client Info behaelt bei mehreren Scannern die allgemeine Uebersicht und bietet Defender zusaetzlich als eigenen Tab mit den vorhandenen CVE-, Device- und Site-Sichten an. 19 fokussierte Dashboard-, Titel- und Graphregressionen erfolgreich. Keine API-, SQL-, Profil- oder Persistenzmigration; Phasen 7 und 13A bleiben abgeschlossen. |
| 2026-09-10 | Phase-13A-Verarbeitungsvertrag fuer Defender-Importe korrigiert: Die Kompatibilitaetspruefung verwendet nun den tatsaechlich persistierten `processedWith.profileId` statt einer eventuell abweichenden erkannten Werkzeug-ID. Dadurch werden registrierte Defender-Profile 1.0.0 als aktuell bewertet; unbekannte Profil-/Versionspaare bleiben fail closed. Die Korrektur ist read-only fuer vorhandene Importdaten und benoetigt keinen Reimport oder Datenbankeingriff. 17 fokussierte Import-/Kompatibilitaetstests sowie der Gesamtlauf mit 766 bestandenen und 8 uebersprungenen optionalen PostgreSQL-Tests, Local-only-, Designpruefung und Client-/Server-Produktionsbuild erfolgreich; Phase 13A bleibt abgeschlossen. |
| 2026-09-10 | Microsoft Defender for IoT vulnerability imports and views umgesetzt: Profil und Source Extractor 1.0.0 erweitern die generische Excel-/CSV-Pipeline; Device-Identitaeten, CVE-Definitionen, Device-mal-CVE-Vorkommen, rohe Sensorbeobachtungen und Aggregate-only-Zahlen bleiben getrennt. Client Info bietet CVE-, Device- und Site-Sichten mit Evidenz- und Reconciliation-Drill-down. Summary-only-Quellen bleiben ohne erfundene Findings viewable. 33 fokussierte synthetische Tests sowie 764 Standardtests, 8 uebersprungene optionale PostgreSQL-Tests, Local-only-, Designpruefung und Client-/Server-Produktionsbuild erfolgreich. Keine API-, SQL- oder Persistenzmigration; Phasen 6, 7 und 13A bleiben abgeschlossen. |
| 2026-08-26 | Phase-13B-Regelkatalog-Freigabegrenze gehaertet: Runtimevalidierung fuer geschlossene Auslieferungsmechanismen, Baseline-Dispositionen, Comparatorvarianten/-modi und Severitywerte ergaenzt; ueberlappende Selektoren, mehrdeutige kontextspezifische Regeln sowie widerspruechliche Risiko- und Abhaengigkeitsdefinitionen werden vor Serviceaktivierung fail-closed abgelehnt. Der fachliche Produktivkatalog bleibt bewusst leer. Der lokale private Artefaktspeicher zeigt wieder auf den aktuellen LocalAppData-Pfad ausserhalb des Webroots; Backend `ok`, App HTTP 200 und vorhandener Speicherroot wurden ohne Inhaltszugriff bestaetigt. Neun neue synthetische Katalogtests, 25 fokussierte GPO-/Katalogtests, 764 bestandene Standardtests, 8 erwartungsgemaess uebersprungene optionale PostgreSQL-Tests, Local-only- und Designpruefung sowie Client-/Server-Produktionsbuild erfolgreich. Keine API-, SQL- oder Persistenzaenderung; Phase 13B bleibt in Arbeit. |
| 2026-08-25 | Phase-7-Markierungsfarbe appweit auf Gelb vereinheitlicht: Der neue Theme-Token `--marking-color` traegt in Dark und Light denselben Wert und steuert Kachelauswahl, allgemeine Auswahlrahmen/-marker, gemerkte Eintraege und Graphauswahl. Die violette Datahub-Auswahlkontur entfaellt. Tastaturfokus bleibt blau, Scanner-/Workflow-/Severitystatus behalten ihre fachlichen Farben. Projektregel und Kachelregression wurden aktualisiert. 24 fokussierte Auswahl-/Dashboard-/Graphtests, Designcheck und Client-Produktionsbuild erfolgreich. Keine Fachlogik-, API-, Persistenz- oder Schemaaenderung; Phase 7 bleibt abgeschlossen. |
| 2026-08-25 | Phase-7-Kachelstandard appweit auf die freigegebene flache Client-Info-Geometrie umgestellt: `--tile-border` ist in beiden Themes transparent, `--tile-radius` verwendet den zentralen Nullradius und `--tile-card-shadow` ist deaktiviert. Datahub-, Dashboard-, Sidebar-, Work- und Pathfinder-Hover veraendern die Flaeche ohne dekorative Kontur; Fokus, Auswahl und Statusmarkierungen bleiben sichtbar. `AGENTS.md` und die gemeinsamen Kachelregressionen wurden auf den neuen verbindlichen Standard aktualisiert. 24 fokussierte Kachel-/Dashboardtests, Designcheck und Client-Produktionsbuild erfolgreich. Keine Fachlogik-, API-, Persistenz- oder Schemaaenderung; Phase 7 bleibt abgeschlossen. |
| 2026-08-25 | Phase-7-Client-Info-Kacheldesign nach erneutem Referenzabgleich korrigiert: Die quadratischen Kennzahl- und Inhaltskacheln verwenden im Normalzustand keine helle Aussenkontur, keinen Kartenschatten und keine Rundung; auch die dekorative Linie unter Inhaltskartentiteln entfaellt. Flaechenkontrast und gemeinsamer Rasterabstand trennen die Kacheln wie im Sollbild, waehrend Hover, Tastaturfokus und aktive Filter funktional sichtbar bleiben. Neun fokussierte Dashboardtests, Designcheck und Client-Produktionsbuild erfolgreich. Keine Fachlogik-, API-, Persistenz- oder Schemaaenderung; Phase 7 bleibt abgeschlossen. |

View File

@ -74,13 +74,50 @@ function unique(values: string[]): boolean {
return new Set(values).size === values.length && values.every(validText);
}
const deliveryMechanisms = new Set<GpoPolicyObservation['deliveryMechanism']>([
'registry-policy',
'security-template',
'advanced-audit-policy',
'group-policy-preference'
]);
const baselineDispositions = new Set<PolicyBaselineDisposition>([
'explicit-recommendation',
'explicitly-not-applicable',
'no-recommendation'
]);
const riskSeverities = new Set<PolicyRiskRule['severity']>([
'info', 'low', 'medium', 'high', 'critical'
]);
function comparatorIsValid(comparator: PolicyComparatorDefinition | undefined): boolean {
if (!comparator) return false;
if (!comparator || typeof comparator !== 'object') return false;
if (comparator.kind === 'enum') {
return comparator.orderedValues.length > 0 && comparator.orderedValues.length <= 256
&& unique(comparator.orderedValues);
return Array.isArray(comparator.orderedValues) && comparator.orderedValues.length > 0
&& comparator.orderedValues.length <= 256 && unique(comparator.orderedValues)
&& ['higher-is-stronger', 'lower-is-stronger'].includes(comparator.direction);
}
return true;
if (comparator.kind === 'bitmask' || comparator.kind === 'audit-flags') {
return ['contains-baseline', 'exact'].includes(comparator.mode);
}
if (comparator.kind === 'set') return ['superset', 'subset', 'exact'].includes(comparator.mode);
return ['equality', 'minimum', 'maximum'].includes(comparator.kind);
}
function selectorsOverlap(left: PolicyEvaluationContextSelector, right: PolicyEvaluationContextSelector): boolean {
if (normalized(left.productFamily) !== normalized(right.productFamily)
|| normalized(left.productVersion) !== normalized(right.productVersion)
|| !left.scopes.some(scope => right.scopes.includes(scope))) return false;
const leftRoles = left.roles.map(normalized);
const rightRoles = right.roles.map(normalized);
return leftRoles.includes('any') || rightRoles.includes('any')
|| leftRoles.some(role => rightRoles.includes(role));
}
function rulesOverlap(left: PolicyEvaluationRule, right: PolicyEvaluationRule): boolean {
return left.identityId === right.identityId && left.deliveryMechanism === right.deliveryMechanism
&& left.contexts.some(leftContext => right.contexts.some(rightContext => selectorsOverlap(leftContext, rightContext)));
}
export function policyEvaluationRuleCatalogValidationErrors(catalog: PolicyEvaluationRuleCatalog): string[] {
@ -91,16 +128,25 @@ export function policyEvaluationRuleCatalogValidationErrors(catalog: PolicyEvalu
errors.push('Der Policy-Evaluation-Regelkatalog besitzt ungueltige Identitaet oder Provenienz.');
}
const ruleIds = new Set<string>();
const dependencyDefinitions = new Map<string, string>();
const riskDefinitions = new Map<string, PolicyRiskRule['severity']>();
for (const rule of catalog.rules) {
if (rule.version !== 1 || !validText(rule.id) || ruleIds.has(rule.id) || !validText(rule.identityId)
|| !rule.contexts.length || !Array.isArray(rule.dependencies)) {
|| !deliveryMechanisms.has(rule.deliveryMechanism) || !baselineDispositions.has(rule.baselineDisposition)
|| !Array.isArray(rule.contexts) || !rule.contexts.length || !Array.isArray(rule.dependencies)) {
errors.push('Der Policy-Evaluation-Regelkatalog enthaelt eine ungueltige oder doppelte Regel.');
}
ruleIds.add(rule.id);
if (rule.baselineDisposition === 'explicit-recommendation') {
if (!comparatorIsValid(rule.comparator) || !rule.riskRule || rule.riskRule.version !== 1
|| !validText(rule.riskRule.id)) {
|| !validText(rule.riskRule.id) || !riskSeverities.has(rule.riskRule.severity)) {
errors.push('Eine ausdrueckliche Baselineempfehlung benoetigt Comparator und Risikoregel.');
} else {
const previousSeverity = riskDefinitions.get(rule.riskRule.id);
if (previousSeverity && previousSeverity !== rule.riskRule.severity) {
errors.push('Eine Risikoregel besitzt widerspruechliche Definitionen.');
}
riskDefinitions.set(rule.riskRule.id, rule.riskRule.severity);
}
} else if (rule.comparator || rule.riskRule) {
errors.push('Nichtempfehlungen duerfen weder Comparator noch Risikoregel tragen.');
@ -113,14 +159,34 @@ export function policyEvaluationRuleCatalogValidationErrors(catalog: PolicyEvalu
errors.push('Eine Policy-Evaluation-Regel besitzt einen ungueltigen Kontextselektor.');
}
}
for (let left = 0; left < rule.contexts.length; left += 1) {
for (let right = left + 1; right < rule.contexts.length; right += 1) {
if (selectorsOverlap(rule.contexts[left], rule.contexts[right])) {
errors.push('Eine Policy-Evaluation-Regel besitzt ueberlappende Kontextselektoren.');
}
}
}
const dependencyIds = new Set<string>();
for (const dependency of rule.dependencies) {
if (dependency.version !== 1 || !validText(dependency.id) || dependencyIds.has(dependency.id)
|| !validText(dependency.identityId)
|| !validText(dependency.identityId) || !deliveryMechanisms.has(dependency.deliveryMechanism)
|| !['prerequisite', 'mutual-exclusion', 'override'].includes(dependency.kind)) {
errors.push('Eine Policy-Evaluation-Regel besitzt eine ungueltige oder doppelte Abhaengigkeit.');
}
dependencyIds.add(dependency.id);
const definition = [dependency.kind, dependency.identityId, dependency.deliveryMechanism].join('\u0000');
const previousDefinition = dependencyDefinitions.get(dependency.id);
if (previousDefinition && previousDefinition !== definition) {
errors.push('Eine Abhaengigkeitsregel besitzt widerspruechliche Definitionen.');
}
dependencyDefinitions.set(dependency.id, definition);
}
}
for (let left = 0; left < catalog.rules.length; left += 1) {
for (let right = left + 1; right < catalog.rules.length; right += 1) {
if (rulesOverlap(catalog.rules[left], catalog.rules[right])) {
errors.push('Der freigegebene Policy-Evaluation-Regelkatalog enthaelt mehrdeutige Kontextregeln.');
}
}
}
return [...new Set(errors)];

View File

@ -0,0 +1,122 @@
import { describe, expect, it } from 'vitest';
import {
POLICY_EVALUATION_RULE_CATALOG,
policyEvaluationRuleCatalogValidationErrors,
type PolicyEvaluationRule,
type PolicyEvaluationRuleCatalog
} from '../../src/domain/policy-evaluation-rules';
function rule(overrides: Partial<PolicyEvaluationRule> = {}): PolicyEvaluationRule {
return {
version: 1,
id: 'synthetic-rule',
identityId: 'synthetic-identity',
deliveryMechanism: 'registry-policy',
contexts: [{
version: 1,
productFamily: 'Windows Server',
productVersion: '2022',
roles: ['Member Server'],
scopes: ['computer']
}],
baselineDisposition: 'explicit-recommendation',
comparator: { kind: 'equality' },
riskRule: { version: 1, id: 'synthetic-risk', severity: 'medium' },
dependencies: [],
...overrides
};
}
function catalog(rules: PolicyEvaluationRule[]): PolicyEvaluationRuleCatalog {
return {
version: 1,
id: 'synthetic-released-catalog',
catalog: POLICY_EVALUATION_RULE_CATALOG,
status: 'released',
rules
};
}
describe('policy evaluation rule catalog release validation', () => {
it('accepts distinct, fully typed released rules', () => {
expect(policyEvaluationRuleCatalogValidationErrors(catalog([
rule(),
rule({
id: 'domain-controller-rule',
contexts: [{
version: 1,
productFamily: 'Windows Server',
productVersion: '2022',
roles: ['Domain Controller'],
scopes: ['computer']
}]
})
]))).toEqual([]);
});
it.each([
[{ kind: 'enum', orderedValues: ['on'], direction: 'sideways' }],
[{ kind: 'bitmask', mode: 'contains-any' }],
[{ kind: 'set', mode: 'overlap' }],
[{ kind: 'audit-flags', mode: 'subset' }],
[{ kind: 'invented' }]
])('rejects an unsupported comparator contract', (comparator) => {
const errors = policyEvaluationRuleCatalogValidationErrors(catalog([
rule({ comparator: comparator as PolicyEvaluationRule['comparator'] })
]));
expect(errors).toContain('Eine ausdrueckliche Baselineempfehlung benoetigt Comparator und Risikoregel.');
});
it('rejects unsupported runtime enum values at the release boundary', () => {
const invalid = rule({
deliveryMechanism: 'invented' as PolicyEvaluationRule['deliveryMechanism'],
riskRule: { version: 1, id: 'synthetic-risk', severity: 'urgent' as 'medium' }
});
const errors = policyEvaluationRuleCatalogValidationErrors(catalog([invalid]));
expect(errors).toEqual(expect.arrayContaining([
'Der Policy-Evaluation-Regelkatalog enthaelt eine ungueltige oder doppelte Regel.',
'Eine ausdrueckliche Baselineempfehlung benoetigt Comparator und Risikoregel.'
]));
});
it('rejects selectors and separate rules that can match the same context', () => {
const overlappingContext = {
version: 1 as const,
productFamily: 'windows-server',
productVersion: '2022',
roles: ['any'],
scopes: ['computer'] as Array<'computer' | 'user'>
};
const errors = policyEvaluationRuleCatalogValidationErrors(catalog([
rule({ contexts: [rule().contexts[0], overlappingContext] }),
rule({ id: 'ambiguous-rule', contexts: [overlappingContext] })
]));
expect(errors).toEqual(expect.arrayContaining([
'Eine Policy-Evaluation-Regel besitzt ueberlappende Kontextselektoren.',
'Der freigegebene Policy-Evaluation-Regelkatalog enthaelt mehrdeutige Kontextregeln.'
]));
});
it('rejects conflicting reusable risk and dependency definitions', () => {
const dependency = {
version: 1 as const,
id: 'shared-dependency',
identityId: 'dependency-target',
deliveryMechanism: 'registry-policy' as const,
kind: 'prerequisite' as const
};
const errors = policyEvaluationRuleCatalogValidationErrors(catalog([
rule({ dependencies: [dependency] }),
rule({
id: 'separate-context-rule',
contexts: [{ ...rule().contexts[0], roles: ['Domain Controller'] }],
riskRule: { version: 1, id: 'synthetic-risk', severity: 'high' },
dependencies: [{ ...dependency, kind: 'override' }]
})
]));
expect(errors).toEqual(expect.arrayContaining([
'Eine Risikoregel besitzt widerspruechliche Definitionen.',
'Eine Abhaengigkeitsregel besitzt widerspruechliche Definitionen.'
]));
});
});