503 lines
29 KiB
Markdown
503 lines
29 KiB
Markdown
# RESULT_CONTRACT
|
||
|
||
```
|
||
schema_version : 1.0.0
|
||
status : verbindlicher Datenvertrag
|
||
zielort : docs/RESULT_CONTRACT.md
|
||
gilt_fuer : Purple Knight, PingCastle, Maester, CIS-CAT Pro, GPO-/Baseline-Analyse
|
||
konsumenten : ASSESSOR UI, lokales LLM, Exporte, weitere Tools
|
||
```
|
||
|
||
Dieser Vertrag definiert die oberste Klassifikationsschicht und die Graph-Grundlage des Canonical Result Store. Er ist ein versionierter Integrationsvertrag, kein reiner UI-Index. Alle Konsumenten greifen auf dieselbe Semantik zu und müssen nicht wissen, welche native Spalte ein Tool verwendet hat.
|
||
|
||
Der Vertrag ersetzt IOE als Organisationsprinzip durch `result_type`. IOE bleibt vollständig erhalten, wird aber zu einer abgeleiteten, indizierten Facette. Alle Zusagen des PingCastle-Arbeitspakets (Dokument 2) für Purple Knight, PingCastle und Maester bleiben unverändert gültig.
|
||
|
||
> **Realisierungshinweis:** Dieser Vertrag wurde additiv auf das bereits bestehende
|
||
> ASSESSOR-Datenmodell abgebildet (Pfad A, Integrationsentscheidung 2026-07-23).
|
||
> Siehe Abschnitt „Abweichungen der ASSESSOR-Realisierung" am Ende dieses Dokuments
|
||
> für die Stellen, an denen die konkrete Umsetzung von diesem Vertrag abweicht, und
|
||
> warum. Der fachlich verbindliche Umsetzungsstand ist
|
||
> [`docs/CANONICAL_MODEL.md`](CANONICAL_MODEL.md); dieses Dokument bleibt der
|
||
> unveränderte externe Vertragstext.
|
||
|
||
## Grundprinzip: zwei orthogonale Achsen
|
||
|
||
Der bisherige Konflikt entsteht, weil IOE zwei verschiedene Fragen vermischt hat. Der Vertrag trennt sie strikt.
|
||
|
||
`result_type` beschreibt die Art der Aussage, also die Quellsemantik. Geschlossenes, versioniertes Enum. Stabil.
|
||
|
||
`result_state` beschreibt den Ausgang der Aussage, normalisiert und typübergreifend. Dies ist die eine Achse, auf der toolübergreifend sicher gefiltert wird.
|
||
|
||
Jede weitere Dimension (Severity, IOE, Score) hängt an diesen beiden Achsen, ersetzt sie aber nicht.
|
||
|
||
## result_type (geschlossenes Enum)
|
||
|
||
```
|
||
exposure_finding Exposure-Scanner. Aussage: Exposure vorhanden oder nicht.
|
||
Quellen: Purple Knight, PingCastle RiskRules, Maester Security Tests.
|
||
Nur dieser Typ kann per Default zu IOE werden.
|
||
|
||
compliance_check Benchmark-Pruefung gegen eine Empfehlung. Aussage: konform oder nicht.
|
||
Quelle: CIS-CAT (XCCDF).
|
||
|
||
policy_deviation Setting-Vergleich gegen eine Baseline. Aussage: Sollwert vs. Istwert plus Verdikt.
|
||
Quelle: GPO-Backup vs. MS Security Baseline.
|
||
|
||
informational Nicht-assertive Kontext- oder Inventardaten ohne Pass/Fail-Semantik.
|
||
|
||
unknown Rueckfallwert, wenn die Quellsemantik nicht klassifizierbar ist.
|
||
```
|
||
|
||
Bewusst kein eigener Typ: `technical_error` ist ein Zustand (`result_state = error`), keine Art von Aussage. Eine CIS-Prüfung, die nicht ausgeführt werden konnte, bleibt `result_type = compliance_check` mit `result_state = error`. Das hält `result_type` stabil.
|
||
|
||
## result_state (normalisiert, typübergreifend)
|
||
|
||
```
|
||
fail negativer Ausgang (Exposure erkannt / Check nicht bestanden / Setting weicht ab)
|
||
pass positiver Ausgang
|
||
warning
|
||
error Auswertung oder Ausfuehrung fehlgeschlagen
|
||
not_applicable
|
||
not_evaluated
|
||
informational
|
||
unknown
|
||
```
|
||
|
||
Dies ist die einzige Achse, auf der ein typübergreifender „alle Fails"-Filter zuverlässig arbeitet. Nativwerte bleiben parallel im typspezifischen Block erhalten und werden nie durch die Normalisierung überschrieben.
|
||
|
||
## is_ioe (abgeleitete Facette)
|
||
|
||
`is_ioe` wird nicht aus der Quelle übernommen, sondern per expliziter, versionierter Regel abgeleitet und als materialisierte, indizierte boolesche Spalte gespeichert. Nicht in JSONB.
|
||
|
||
Ableitungsregeln:
|
||
|
||
```
|
||
exposure_finding + exposure_state = detected -> is_ioe = true (Default)
|
||
compliance_check -> is_ioe = false (Default)
|
||
policy_deviation -> is_ioe = false (Default)
|
||
```
|
||
|
||
Nur eine explizite, versionierte Elevationsregel darf einen einzelnen Compliance-Check oder eine Policy-Abweichung auf IOE heben. Niemals per Default.
|
||
|
||
Jede Ableitung trägt `ioe_rule_id`, `ioe_rule_version`, `ioe_derivation_method`.
|
||
|
||
Äquivalenz zum bisherigen Filter aus Dokument 2: `findingType = ioe AND exposureState = detected` entspricht jetzt `is_ioe = true`. Der Feinfilter über `not_detected`, `not_evaluated`, `execution_error` läuft weiterhin über `exposure_state`, das nun im `exposure`-Block sitzt und nur für `exposure_finding` existiert.
|
||
|
||
## Quellen-Mapping
|
||
|
||
| Quelle | Nativwert | result_type | Zustandsfeld (Block) | result_state | is_ioe |
|
||
|---|---|---|---|---|---|
|
||
| Purple Knight | IOEs found | exposure_finding | exposure_state = detected | fail | true |
|
||
| Purple Knight | No IOE found | exposure_finding | exposure_state = not_detected | pass | false |
|
||
| PingCastle | RiskRule Points > 0 | exposure_finding | exposure_state = detected | fail | true |
|
||
| PingCastle | RiskRule Points = 0 | exposure_finding | exposure_state = not_detected | pass | false |
|
||
| PingCastle | nicht auswertbar | exposure_finding | exposure_state = not_evaluated | not_evaluated | false |
|
||
| PingCastle | Ausfuehrungsfehler | exposure_finding | exposure_state = execution_error | error | false |
|
||
| Maester | Security Test Failed | exposure_finding | exposure_state = detected | fail | true |
|
||
| Maester | Passed | exposure_finding | exposure_state = not_detected | pass | false |
|
||
| Maester | Diagnose / nicht ausgefuehrt | informational | – | not_evaluated | false |
|
||
| CIS-CAT | XCCDF fail | compliance_check | xccdf_result = fail | fail | false |
|
||
| CIS-CAT | XCCDF pass | compliance_check | xccdf_result = pass | pass | false |
|
||
| CIS-CAT | XCCDF error | compliance_check | xccdf_result = error | error | false |
|
||
| CIS-CAT | XCCDF notapplicable | compliance_check | xccdf_result = notapplicable | not_applicable | false |
|
||
| CIS-CAT | XCCDF notchecked / notselected | compliance_check | xccdf_result = notchecked | not_evaluated | false |
|
||
| CIS-CAT | XCCDF informational | compliance_check | xccdf_result = informational | informational | false |
|
||
| GPO vs. Baseline | Abweichung | policy_deviation | verdict = deviation | fail | false |
|
||
| GPO vs. Baseline | Uebereinstimmung | policy_deviation | verdict = match | pass | false |
|
||
| GPO vs. Baseline | Setting fehlt beim Subjekt | policy_deviation | verdict = missing_on_subject | informational | false |
|
||
| GPO vs. Baseline | Setting nur beim Subjekt | policy_deviation | verdict = extra_on_subject | warning | false |
|
||
| GPO vs. Baseline | nicht vergleichbar | policy_deviation | verdict = not_comparable | unknown | false |
|
||
|
||
Vorbehalte: Welche Maester-Tests als Exposure zählen, entscheidet das versionierte Profil (Dokument 2, Abschnitt 8). Ob ein fehlendes Setting als `fail` oder `not_applicable` gilt, ist settingabhängig und wird im GPO-Submodell festgelegt, nicht hier. Die XCCDF-Zustände `notchecked`, `notapplicable` und `error` dürfen nicht kollabiert werden.
|
||
|
||
## Typspezifische Detailblöcke
|
||
|
||
Der Nativreichtum jeder Quelle bleibt in einem typspezifischen Block erhalten. Die normalisierten Top-Level-Felder sind der Vertrag; die Blöcke sind die Wahrheit der Quelle.
|
||
|
||
```
|
||
exposure exposure_state, score_original, score_unit, native_severity
|
||
compliance xccdf_result, benchmark_id, rule_id, profile_id, remediation
|
||
deviation setting_key (kanonisch), baseline_value, subject_value, verdict,
|
||
baseline_ref, baseline_version
|
||
```
|
||
|
||
Der `deviation`-Block ist hier nur in seiner Rolle definiert. Sein vollständiges Vergleichs-Submodell inklusive Setting-Identität und Verdikt-Logik ist noch offen (siehe Abschnitt Offene Punkte).
|
||
|
||
## Kanonische Asset-Schicht: CANONICAL_ASSET
|
||
|
||
Ein Knoten wird ausschließlich über einen autoritativen, stabilen Schlüssel identifiziert. Anzeigenamen führen niemals zu einer Knotenzusammenführung. Dies ist die „keine Textähnlichkeit"-Regel aus Dokument 2, angewandt auf die Knotenidentität. Ohne diese Regel zerfällt der Graph in Dubletten oder verschmilzt falsch. Beides zerstört jede Blast-Radius-Aussage.
|
||
|
||
```
|
||
CANONICAL_ASSET
|
||
────────────────────────────────
|
||
asset_id internes Surrogat-PK
|
||
asset_key deterministischer Dedup-Schluessel, namespaced
|
||
asset_key_kind sid | object_guid | gpo_guid | domain_sid | trust_key | synthetic
|
||
asset_type user | group | computer | domain_controller | domain
|
||
| ou | gpo | trust | tenant | service_principal | unknown
|
||
primary_name Anzeigename, nicht autoritativ
|
||
domain
|
||
key_confidence 1.0 bei autoritativem Schluessel, < 1.0 bei synthetic
|
||
resolution_method wie der Schluessel gewonnen wurde
|
||
source_tools welche Tools diese Entitaet gemeldet haben
|
||
first_seen_at
|
||
last_seen_at
|
||
original_fields jsonb
|
||
extensions jsonb
|
||
```
|
||
|
||
Schlüsselpriorität, absteigend autoritativ:
|
||
|
||
```
|
||
objectSID Security Principals (user, group, computer, DC) autoritativ
|
||
objectGUID alle AD-Objekte, stabil ueber Umbenennung autoritativ
|
||
gpo_guid GPOs (der {GUID}-Ordner im Backup, gpreport) autoritativ
|
||
domain_sid Domaenenknoten autoritativ
|
||
trust_key Trust = (Quell-Domaenen-SID, Ziel-Domaenen-SID) autoritativ
|
||
synthetic nur-Name-Meldung ohne SID/GUID NICHT autoritativ
|
||
```
|
||
|
||
Merge-Regel (hart): Zwei Zeilen werden nur zusammengeführt, wenn sie denselben autoritativen Schlüssel teilen. Namensgleichheit löst niemals einen Merge aus.
|
||
|
||
Konservative Wahl für `synthetic` (in Version 1.0.0 verbindlich): Ein synthetischer Knoten verschmilzt nie automatisch mit einem autoritativen Knoten, auch nicht bei exaktem Namens- und Domänen-Match. Eine Bindung erfolgt ausschließlich über eine explizite, versionierte, protokollierte Rekonziliationsregel. Das erzeugt sichtbare Dubletten, bis eine solche Regel greift; diese Dubletten sind auditierbar und werden einer stillen Fehlverschmelzung vorgezogen. Kein erfundener SID.
|
||
|
||
## Kantenschicht: ASSET_EDGE
|
||
|
||
Kanten sind eine eigenständige Entität, getrennt von Findings. Ein Finding ist ein Knoten-Attribut; eine Kante ist eine Beziehung zwischen zwei Knoten. Diese Trennung ist die Voraussetzung dafür, dass ein Attack Path überhaupt berechenbar wird.
|
||
|
||
```
|
||
ASSET_EDGE
|
||
────────────────────────────────
|
||
edge_id
|
||
source_asset_id FK CANONICAL_ASSET
|
||
target_asset_id FK CANONICAL_ASSET
|
||
edge_type siehe Katalog
|
||
direction gerichtet: source -> target
|
||
edge_source Tool + Import-Artefakt, das die Kante erzeugt hat
|
||
collection_method z. B. pingcastle-html-trust-table, gpo-backup-dacl
|
||
collector_version
|
||
source_locator
|
||
mapping_method
|
||
mapping_rule_version
|
||
confidence
|
||
first_seen_at
|
||
last_seen_at
|
||
properties jsonb kantentyp-spezifische Attribute
|
||
original_fields jsonb
|
||
```
|
||
|
||
`edge_source`, `collection_method` und `collector_version` sind verpflichtend. Sie sind die Grundlage der ehrlichen Blast-Radius-Deklaration: Nur wenn jede Kante ihre Herkunft trägt, ist ableitbar, welche Kantentypen erhoben wurden und welche fehlen.
|
||
|
||
## Kantentyp-Katalog
|
||
|
||
Der Katalog ist selbst versioniert und Teil des Schemas. „Nicht erhoben" ist damit wohldefiniert: Katalog minus erhobene Typen.
|
||
|
||
| edge_type | Quelle im Bestand | Stufe | Status |
|
||
|---|---|---|---|
|
||
| MemberOf | PingCastle / PK Detailtabellen | A | extrahierbar |
|
||
| Trusts | PingCastle Trust-Bericht | A | extrahierbar |
|
||
| AllowedToDelegate | PingCastle (Constrained Delegation) | A | extrahierbar |
|
||
| AllowedToActOnBehalfOf | PingCastle (RBCD) | A | extrahierbar |
|
||
| GpLink | GPO-Backup / gpreport | A | extrahierbar |
|
||
| Contains (OU -> Objekt) | GPO-Backup / gpreport | A | teilweise |
|
||
| GPO-DACL (WriteDacl, WriteOwner, GenericAll, GenericWrite) | GPO-Backup ACL | A | extrahierbar |
|
||
| HasSession | – | B | nicht in den Quellen |
|
||
| AdminTo (lokaler Admin) | – | B | nicht in den Quellen |
|
||
| CanRDP / CanPSRemote / ExecuteDCOM | – | B | nicht in den Quellen |
|
||
| Objekt-DACL (alle Objekttypen) | – | B | nicht in den Quellen |
|
||
|
||
Die Stufe-B-Zeilen sind die BloodHound-/SharpHound-Kanten, aus denen die meisten realen Pfade entstehen. Sie sind der Grund, warum Stufe A eine Angriffsflächen-Topologie liefert, aber noch keinen vollständigen Attack Path. Ihre Ingestion ist eine eigenständige, noch nicht eingeplante Datenerfassung.
|
||
|
||
## Finding zu Graph-Rolle
|
||
|
||
Nicht jedes Finding ist eine Kante. Die Abbildung erfolgt über eine versionierte Regel-Tabelle in genau drei Rollen und wird nie per Import erfunden.
|
||
|
||
Knoten-Eigenschaft: AS-REP-Roastable (`DONT_REQ_PREAUTH`), Kerberoastable (SPN gesetzt), Unconstrained Delegation, „Passwort läuft nie ab". Das sind Fähigkeiten eines Knotens, keine Beziehungen. Sie landen als Property am `CANONICAL_ASSET`, referenziert auf das erzeugende Finding.
|
||
|
||
Kante: RBCD erzeugt eine `AllowedToActOnBehalfOf`-Kante, Constrained Delegation eine `AllowedToDelegate`-Kante, eine GPO-ACL-Berechtigung eine `GPO-DACL`-Kante. Diese Findings haben strukturelle Bedeutung im Graphen.
|
||
|
||
Reines Overlay: Ein CIS-Härtungs-Fail oder eine Policy-Abweichung sitzt auf einem Knoten (etwa einem DC oder GPO), erzeugt aber weder Kante noch angriffsrelevante Eigenschaft. Kontext auf dem Knoten.
|
||
|
||
Die Zuordnung Finding-Typ zu Graph-Rolle trägt `mapping_rule_version` und bleibt damit prüfbar und austauschbar, analog zur Score-to-Severity-Sperre.
|
||
|
||
## EDGE_COVERAGE und ehrlicher Blast Radius
|
||
|
||
Ein Blast Radius über eine unvollständige Kantenmenge unterschätzt die reale Erreichbarkeit. Ihn als vollständig darzustellen wäre ein Prüffehler. Deshalb wird die Kantenabdeckung explizit modelliert.
|
||
|
||
```
|
||
EDGE_COVERAGE
|
||
────────────────────────────────
|
||
assessment_id / scan_run_id
|
||
edge_type
|
||
edge_source
|
||
collected true | false
|
||
collector_version
|
||
```
|
||
|
||
Jede Erreichbarkeits- oder Blast-Radius-Berechnung läuft über einen deklarierten Kantentyp-Filter und liefert das Ergebnis mit zwei Pflichtangaben: `computed_over_edge_types` und `uncollected_edge_types` (Katalog minus erhoben). Jede Graph-Aussage trägt damit sichtbar, worauf sie beruht und was ihr fehlt.
|
||
|
||
## Deterministische Graph-Operationen
|
||
|
||
Diese Operationen sind reine Graph-Algorithmen, damit assertionssicher und ohne Kennzeichnungspflicht.
|
||
|
||
```
|
||
Erreichbarkeit erreichbare Knotenmenge von X unter Kantenfilter E
|
||
Kuerzester Pfad zwischen Knoten A und B unter E
|
||
Transitive Huelle Blast Radius = erreichbare Menge von X unter E
|
||
Finding-Overlay welches Finding sitzt auf welchem Knoten
|
||
```
|
||
|
||
Nicht Teil dieser Schicht, weil heuristisch und kennzeichnungspflichtig: Pfad-Ranking, Risikobewertung eines Pfades, Blast Radius als Impact-Score statt roher Erreichbarkeitsmenge, das Verketten von ATT&CK-Techniken zu einer Angriffserzählung, sowie jede Cross-Tool-Knotenidentität, die auf etwas anderem als SID oder GUID beruht.
|
||
|
||
## Enrichment: ATT&CK und ANSSI
|
||
|
||
MITRE ATT&CK und ANSSI-Härtungsstufen sind eine Taxonomie der Findings, kein Graph der Umgebung. Als Knoten- und Finding-Annotation sowie als Filter- und Gruppierungsachse sind sie deterministisch und zulässig. Als Pfadgenerator sind sie unzulässig, da das Verketten von Techniken eine generierte Angriffserzählung wäre.
|
||
|
||
## Was nicht Knoten ist
|
||
|
||
Registry-Werte, einzelne Policy-Settings und CIS-Regeln sind keine Graph-Knoten. Sie bleiben Inhalt von Findings beziehungsweise des `deviation`-Blocks. Ein Policy-Deviation-Finding kann einen GPO referenzieren; der GPO ist ein Knoten, das Setting darin nicht.
|
||
|
||
## Invarianten
|
||
|
||
```
|
||
1. result_type ist geschlossen und versioniert. Keine Quelle fuehrt Ad-hoc-Typen ein.
|
||
2. result_state wird abgeleitet, nie roh aus einem Quell-String oder Spaltennamen uebernommen.
|
||
3. is_ioe wird per expliziter, versionierter Regel abgeleitet; nie aus der Quelle uebernommen;
|
||
nie fuer compliance_check oder policy_deviation per Default true.
|
||
4. Nativwerte bleiben parallel vollstaendig erhalten (typspezifischer Block plus original_fields).
|
||
5. Jede Ableitung traegt mapping_method und mapping_rule_version.
|
||
6. Fehlend ist NULL bzw. unknown, nie erfunden.
|
||
7. Tooluebergreifend vergleichbar sind nur result_type, result_state, severity_normalized, is_ioe.
|
||
score ist tooluebergreifend nicht vergleichbar und darf typuebergreifend nicht sortiert
|
||
oder schwellwertgefiltert werden.
|
||
8. Knotenidentitaet nur ueber autoritativen Schluessel; Name fuehrt nie zu Merge.
|
||
9. synthetic-Knoten verschmelzen nie automatisch mit autoritativen Knoten.
|
||
10. Kanten sind erste Klasse, getrennt von Findings.
|
||
11. Jede Kante traegt edge_source, collection_method, collector_version.
|
||
12. Erreichbarkeit nur ueber deklarierten Kantentyp-Filter; Ergebnis nennt uncollected_edge_types.
|
||
13. Kanten sind gerichtet; Richtung ist semantisch.
|
||
14. Finding zu Graph-Rolle nur ueber versionierte Regel, nie per Import erfunden.
|
||
```
|
||
|
||
## Filter-Semantik
|
||
|
||
```
|
||
[ IOE ] primaer: is_ioe = true. Semantisch unveraendert gegenueber Dokument 2.
|
||
[ Abweichung ] ASSESSOR-Ergaenzung, nicht Teil des Vertrags: is_ioe OR outcome = warning.
|
||
[ result_type ] oberste Facette: Exposure / Compliance / Policy / Informational.
|
||
[ result_state ] typuebergreifend: Fail / Pass / Warning / Error / N/A / Not evaluated.
|
||
[ Severity ] orthogonal, kategorial, typuebergreifend.
|
||
```
|
||
|
||
Typspezifische Facetten: `exposure_state` nur bei Exposure, `xccdf_result` nur bei Compliance, `verdict` nur bei Policy. Alle Facetten sind kombinierbar.
|
||
|
||
Beispiele:
|
||
|
||
```
|
||
is_ioe = true
|
||
is_ioe = true AND source_tool = pingcastle
|
||
is_ioe = true AND severity_normalized = high
|
||
result_type = compliance_check AND result_state = fail
|
||
result_type = policy_deviation AND verdict = deviation
|
||
result_state = fail
|
||
```
|
||
|
||
## Verhältnis zu bestehenden Dokumenten
|
||
|
||
Additiv. `result_type`, `result_state` und `is_ioe` ergänzen die dokumentierte Trennung von Originalwert und normalisiertem Wert (CANONICAL_MODEL.md). `RESULT_ASSET` referenziert künftig `canonical_asset_id` statt Namen inline zu speichern; dies ist der einzige Eingriff in die bestehende Struktur und zugleich der Punkt, an dem Deduplizierung entsteht. Der gesamte PingCastle-Extraktions- und Verknüpfungspfad aus Dokument 2 bleibt unverändert und speist Knoten und Stufe-A-Kanten aus den bereits extrahierten Detailtabellen.
|
||
|
||
## Offene Punkte (nicht Teil von 1.0.0)
|
||
|
||
```
|
||
- GPO-/Baseline-Vergleichs-Submodell: Setting-Identitaet, Verdikt-Logik,
|
||
Behandlung von "nicht konfiguriert" vs. "abwesend", baseline_ref-Versionierung.
|
||
- CIS-CAT-Adapter: Festlegung XCCDF/ARF vs. JSON nach Verifikation der Lizenzausgabe.
|
||
- Elevationsregeln, die einzelne Compliance-/Policy-Befunde auf is_ioe = true heben.
|
||
- Rekonziliationsregeln fuer synthetic-Knoten (Bindung an autoritative Knoten).
|
||
- Stufe B: Ingestion von BloodHound-/SharpHound-Klasse-Kanten (Session, AdminTo, DACL).
|
||
- Mandanten-/Isolationsmodell ueber project_id hinaus (Row-Level Security o. ae.).
|
||
```
|
||
|
||
## Changelog
|
||
|
||
```
|
||
1.0.0 Erstfassung. result_type als oberster Diskriminator, result_state normalisiert,
|
||
is_ioe als abgeleitete indizierte Facette. Typspezifische Bloecke (exposure,
|
||
compliance, deviation). Asset-Schicht (CANONICAL_ASSET) mit autoritativer
|
||
Schluesselprioritaet und konservativer Merge-Regel. Kantenschicht (ASSET_EDGE),
|
||
Kantentyp-Katalog, EDGE_COVERAGE, deterministische Graph-Operationen.
|
||
```
|
||
|
||
## Abweichungen der ASSESSOR-Realisierung
|
||
|
||
Dieser Abschnitt ist kein Teil des externen Vertrags, sondern dokumentiert, wo und
|
||
warum die konkrete Umsetzung in ASSESSOR von diesem Text abweicht. Grundlage ist
|
||
die Integrationsentscheidung „Pfad A" vom 2026-07-23: additive Abbildung auf das
|
||
bestehende ASSESSOR-Datenmodell statt einer zweiten, parallelen Resulttabelle.
|
||
|
||
### Abweichung: eigenes Praedikat neben is_ioe, nicht statt is_ioe
|
||
|
||
`is_ioe` bleibt exakt wie im Vertrag definiert: `exposure_finding` mit
|
||
`exposure_state = detected`, ohne Elevationsregel. Die Arbeitsansichten brauchen
|
||
darueber hinaus eine breitere Frage — „verlangt dieses Ergebnis Aufmerksamkeit?" —,
|
||
weil ein Werkzeug ein Ergebnis melden kann, das weder bestanden noch fehlgeschlagen
|
||
ist. Maester meldet dafuer `Investigate`, ASSESSOR normalisiert das auf
|
||
`outcome_normalized = warning`.
|
||
|
||
ASSESSOR fuehrt dafuer ein eigenes versioniertes Praedikat
|
||
(`src/domain/deviation-predicate.ts`, Version 1.0.0):
|
||
|
||
```
|
||
is_deviation := is_ioe OR outcome_normalized = 'warning'
|
||
```
|
||
|
||
Es ist nicht persistiert, sondern wird bei der Projektion abgeleitet; die
|
||
PostgreSQL-Vorfilterung bildet dieselbe Formel ab. Es liest ausschliesslich bereits
|
||
normalisierte Achsen und gilt damit ohne Aenderung fuer jedes weitere Werkzeug.
|
||
Ausdruecklich nicht enthalten sind `pass`, `not_run`, `not_applicable` und
|
||
`execution_error`; ein Ausfuehrungsfehler ist ein technisches Problem des Laufs und
|
||
hat mit `technical_execution_state` eine eigene Achse.
|
||
|
||
Offen bleibt, dass `deriveExposureState` fuer `outcome = warning` weiterhin
|
||
`unknown` liefert. Ein eigener Zustand (etwa `indeterminate`) wuerde den
|
||
Vertragsenum, die Check-Constraint aus Migration 007 und damit eine Migration samt
|
||
Versionssprung beruehren; er ist bewusst noch nicht umgesetzt.
|
||
|
||
### result_state statt eigener Spalte: abgeleitete Projektion
|
||
|
||
ASSESSOR trennt seit Migration `007_unified_result_read_model.sql` bewusst zwei
|
||
Dimensionen, die dieser Vertrag in einer Spalte `result_state` zusammenführt:
|
||
|
||
- `outcome_normalized` — das fachliche Ergebnis (`fail`, `pass`, `warning`,
|
||
`informational`, `execution_error`, `not_run`, `not_applicable`,
|
||
`not_evaluated`, `unknown`)
|
||
- `technical_execution_state` — ob die Prüfung technisch überhaupt durchlief
|
||
(`completed`, `failed`, `not-run`, `unknown`)
|
||
|
||
Begründung im Umsetzungsplan: „Ein technischer Parser-/Toolfehler ist kein
|
||
fachlich fehlgeschlagener Check." Ein `result_state = error` würde diese
|
||
Unterscheidung wieder aufheben. ASSESSOR führt deshalb **keine physische
|
||
`result_state`-Spalte**. Wo ein Konsument den Vertragswert braucht, wird er per
|
||
versionierter Regel aus beiden Bestandsachsen projiziert (`projectResultState` in
|
||
`src/domain/result-contract-mapping.ts`, `RESULT_CONTRACT_MAPPING_VERSION`).
|
||
Die Bestandsachsen bleiben die persistierte Wahrheit.
|
||
|
||
### source_tool bleibt offen statt geschlossenes Enum
|
||
|
||
`tool_id` ist in ASSESSOR ein freier Wert, kein geschlossenes Enum. Grund ist der
|
||
generische Fallback (Leitprinzip „unbekannte Quellen müssen importierbar
|
||
bleiben"): ein geschlossenes `source_tool`-Enum würde jede noch nicht
|
||
katalogisierte Quelle auf `unknown` zwingen und die tatsächliche Tool-Kennung
|
||
verlieren. `source_tool` im Sinne dieses Vertrags ist daher eine Projektion über
|
||
`tool_id`, kein Speicherformat.
|
||
|
||
### review_status bleibt zweidimensional
|
||
|
||
ASSESSOR trennt `reviewStatus` (Workflow-Zustand) und `reviewDisposition`
|
||
(Entscheidung bei Abschluss) mit eigener `ReviewWorkflowStatus`-Achse (7 Werte,
|
||
inkl. optimistischer Revision je Review). Der einwertige `review_status` dieses
|
||
Vertrags wird bei Bedarf daraus abgeleitet, nicht umgekehrt gespeichert.
|
||
|
||
### Keine eigene canonical_result-Tabelle
|
||
|
||
`result_type`, `is_ioe`, `ioe_rule_id`, `ioe_rule_version`,
|
||
`ioe_derivation_method` sowie `exposure_state` sind additive Spalten auf der
|
||
bestehenden Occurrence-Tabelle (`assessor_finding_occurrences`,
|
||
Migration `008_result_contract_facets.sql`), nicht Spalten einer neuen
|
||
Haupttabelle. `result_compliance` und `result_deviation` wurden nicht als neue
|
||
Haupttabellen angelegt. GPO-/Baseline-Vergleiche stammen aus den versionierten
|
||
Policy-Evaluation-Entitäten und werden nur im Read-Modell vereinheitlicht.
|
||
|
||
### Datenschutzgrenze der Result DB
|
||
|
||
Die Result DB ist eine fail-closed, LLM-faehige Offenlegungsprojektion und kein
|
||
Rohdaten- oder Kundenkontextspeicher. Seit Disclosure Policy 2.1.0 duerfen
|
||
regulaere kanonische Findings und GPO-Vergleiche keinen personen- oder
|
||
kundenidentifizierenden Inhalt transportieren. Bei den explizit bekannten
|
||
Scannerprofilen CIS-CAT, Maester, PingCastle und Purple Knight bleiben jedoch
|
||
regelbezogene Titel, Beschreibungen und Empfehlungen erhalten, damit die
|
||
Result DB fachlich und fuer ein LLM nutzbar bleibt. Vor der Ausgabe ersetzt die
|
||
Projektion exakte private Begleitwerte und feste Identifierklassen durch
|
||
typisierte Platzhalter und wandelt HTML-Regelprosa in Klartext um. Assets,
|
||
Personen-/Reviewfelder, Tenant-/Host-/GPO-/Backupidentitaeten, Locators,
|
||
Quellzeitpunkte und interne Artefaktprovenienz bleiben ausschliesslich im
|
||
internen Analysepfad. Unbekannte Werkzeuge und generische Quellen fallen
|
||
weiterhin auf kontrollierte Bezeichnungen ohne Quellprosa zurueck.
|
||
|
||
Disclosure Policy 2.5.0 erweitert die freigegebenen PingCastle-Rule-IDs auf den
|
||
oeffentlich dokumentierten Regelkatalog. Die Validierung ist
|
||
gross-/kleinschreibungsunabhaengig, die Ausgabe behaelt die native Schreibweise;
|
||
unbekannte IDs bleiben aus der LLM-faehigen Result DB ausgeschlossen.
|
||
|
||
Disclosure Policy 2.6.0 nimmt Microsoft Defender for IoT als bekanntes Werkzeug
|
||
auf. Freigegeben wird nur eine formal gueltige oeffentliche CVE-ID
|
||
(`CVE-JJJJ-NNNN…`) als Rule ID und zugleich als Titel; Geraete-, Site-, Sensor-
|
||
und Beschreibungswerte bleiben wie bisher ausgeschlossen.
|
||
|
||
Disclosure Policy 2.5.0 zieht fuer GPO-Vergleiche die verbindliche Grenze
|
||
zwischen technischer Abweichung und Quellenidentitaet. Titel, technische
|
||
Setting-ID, Policy-Pfad, Value Name, Operation, Registry-Typ, konfigurierter
|
||
Wert, erwarteter Wert, Comparator-Relation, Status, oeffentliche
|
||
Baselinebezeichnung/-version, oeffentliche Beschreibung/Empfehlung, belegte
|
||
Diagnosen, Verarbeitungsstaende und die Aussagegrenze des Backupvergleichs
|
||
duerfen vollstaendig erscheinen. Kunden-/Projektname, Domainlabel,
|
||
kundenspezifischer GPO-Name, GPO-/Backup-GUID, Datei-/Sharepfad, interne
|
||
Projekt-/Assessment-/Run-/Import-/Artefaktidentitaet, Source Locator,
|
||
Zeitpunkte und Review-/Risikoakzeptanzdaten bleiben ausgeschlossen.
|
||
`policyIdentityKey` und interne Baseline-IDs werden nicht ausgegeben; die
|
||
technische Setting-ID steht ausschliesslich als kontrollierte `Source Rule ID`.
|
||
Automatisch erzeugte `gpo-policy-identity-*`-Hashes sind niemals oeffentliche
|
||
Regelidentitaeten.
|
||
|
||
Wohlbekannte oeffentliche Principals wie `S-1-5-18`, `S-1-5-32-544` und
|
||
`BUILTIN\\Administrators` bleiben technisch lesbar. Domain-SIDs verlieren ihre
|
||
eindeutigen Domainkomponenten (`S-1-5-21-[DOMAIN]-<RID>`), bekannte
|
||
Domainrollen werden als `[DOMAIN]\\<Rolle>` dargestellt und sonstige konkrete
|
||
Benutzer/Gruppen erhalten stabile, nur innerhalb des einzelnen Resultats
|
||
gueltige Platzhalter `[DOMAIN_PRINCIPAL_n]`. Dieselbe Reduktion gilt rekursiv
|
||
fuer Baselinevarianten. Die Originalzuordnung bleibt ausschliesslich in Work
|
||
und GPO Analysis erhalten.
|
||
|
||
Die fachlichen Bewertungsachsen bleiben erhalten: normalisierte Severity und
|
||
Outcome, Result-Typ, Abweichung/IOE, kontrollierte GPO-Klassifikation,
|
||
Comparator-Relation sowie nicht identifizierende Aggregate und technische
|
||
Versionsmetadaten. Damit bleibt die Sicherheitsbewertung fuer ein LLM nutzbar,
|
||
ohne Kunden- oder konkrete GPO-Quellenidentitaet weiterzugeben. Die Aussage bezieht sich auf
|
||
Identifizierbarkeit; ein Finding beschreibt weiterhin den anonymisierten
|
||
Sicherheitszustand des aktiv ausgewaehlten Scopes.
|
||
|
||
Ergaenzende Scannerbeobachtungen durchlaufen vor der Unified-Result-Projektion
|
||
die versionierte, fail-closed Disclosure Policy
|
||
`result-db-supplemental-disclosure` 1.0.0. Eine Beobachtung wird nur ausgegeben,
|
||
wenn Werkzeug, Property und ein enger Datentyp-/Wertebereich ausdruecklich
|
||
freigegeben sind. Titel, Subject-Klasse und Property werden aus der kontrollierten
|
||
Regel neu aufgebaut. Subject-Identitaet, Originalwert, Attribute, betroffener
|
||
Assetname, Abschnitt und Source Locator werden niemals aus der Beobachtung in
|
||
die Result DB uebernommen. Unbekannte Werkzeuge und neue Zusatzfelder bleiben
|
||
vollstaendig in der internen Persistenz, sind aber bis zu einer bewussten
|
||
Klassifikation nicht Teil der Result DB. Das betrifft nicht ihre regulaeren
|
||
kanonischen Findings.
|
||
|
||
Rohe GPO-Policy-Beobachtungen sind ebenfalls kein Result-DB-Inhalt. Sie bleiben
|
||
im expliziten `internal-analysis`-Lesemodus fuer Work und GPO Analysis erhalten;
|
||
versioniert evaluierte Soll-/Ist-Vergleiche erreichen die normale Result DB als
|
||
`policy_deviation`. Ohne freigegebene Risikoregel bleibt die Severity `unknown`;
|
||
die Finding Factory darf nur belegte Ergebnisse anreichern und erzeugt keine
|
||
doppelte Result-Zeile. Der JSONL-Export verwendet Schema 2.0.0 und Disclosure
|
||
Policy 2.5.0. Er wendet die allgemeine Projektion nochmals an, verlangt fuer
|
||
jede Environment-Zeile weiterhin eine Disclosure-Klassifikation und entfernt
|
||
zusaetzlich interne Result-, Projekt-, Assessment-, Run-, Snapshot-, Evaluation-
|
||
und Artefaktidentitaeten sowie Zeitpunkte. Manifest-Scope und Filter werden nur
|
||
als Boolesche beziehungsweise Mengenangaben ausgegeben; Suchtext, Assetfilter
|
||
und konkrete Scope-IDs werden nie serialisiert.
|
||
|
||
### Asset-/Kantenschicht: Struktur ohne Producer
|
||
|
||
`CANONICAL_ASSET`, `ASSET_EDGE`, `ASSET_PROPERTY` und `EDGE_COVERAGE` wurden
|
||
strukturgetreu als `assessor_canonical_assets`, `assessor_asset_edges`,
|
||
`assessor_asset_properties` und `assessor_edge_coverage` übernommen
|
||
(Migration `009_asset_graph.sql`), mit `text`-Primärschlüsseln statt `uuid`
|
||
gemäß Bestandskonvention. In diesem Integrationsschritt existiert noch kein
|
||
Producer, kein Merge- und kein Rekonziliationscode — die Tabellen sind bewusst
|
||
leer nutzbar angelegt, nicht befüllt.
|