Implementation + policy Upadets codex

This commit is contained in:
Michael 2026-09-03 12:21:37 +02:00
parent a5331de771
commit e6d164e1ef
3 changed files with 200 additions and 7 deletions

View File

@ -1176,6 +1176,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:
@ -1349,6 +1351,7 @@ Aktualisierung nach dem Schritt-11-Orchestrierungsvertikalschnitt vom 2026-08-04
| Datum | Entscheidung | Begründung |
|---|---|---|
| 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. |
@ -1617,6 +1620,7 @@ Diese Entscheidungen werden erst getroffen, wenn die jeweilige Phase ansteht ode
| Risiko | Gegenmaßnahme |
|---|---|
| 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. |
@ -1807,6 +1811,7 @@ Diese Entscheidungen werden erst getroffen, wenn die jeweilige Phase ansteht ode
## 12. Änderungsprotokoll
| Datum | Änderung |
| 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.'
]));
});
});