26 KiB
Hinweis: Dies ist der unveränderte externe Vertragstext „CANONICAL_MODEL 1.0.0", geliefert zusammen mit
RESULT_CONTRACT.mdund validiert gegen ein eigenständiges PostgreSQL-16-DDL. Er beschreibt ein vollständiges eigenständiges Datenmodell (u. a. eine eigenecanonical_result- Haupttabelle, typspezifische Blöcke, Asset-/Kantenschicht) und ist nicht identisch mit dem tatsächlich implementierten ASSESSOR-Modell.Der fachlich verbindliche, implementierte Stand ist
docs/CANONICAL_MODEL.md(Schema-Version 1,src/domain/canonical-model.ts). Die Integrationsentscheidung vom 2026-07-23 (Pfad A, additiv) bildet die Neuerungen dieses Vertrags —result_type,is_ioe, die Asset-/Kantenschicht — auf das bestehende Modell ab, statt es zu ersetzen. Abweichungen und Begründungen stehen inRESULT_CONTRACT.md.Diese Datei wird nicht mit
docs/CANONICAL_MODEL.mdzusammengeführt und dient ausschließlich als unveränderte Referenz auf den gelieferten Vertragsstand.
CANONICAL_MODEL
model_version : 1.0.0
realisiert : RESULT_CONTRACT 1.0.0
status : vollstaendiges logisches Datenmodell, implementierungsreif
zielort : docs/CANONICAL_MODEL.md
gilt_fuer : Purple Knight, PingCastle, Maester, CIS-CAT Pro, GPO-/Baseline-Analyse
konsumenten : ASSESSOR UI, lokales LLM, Exporte, Graph, weitere Tools
Dies ist die eine kanonische, filterbare Datenbasis. Sie ist so geschnitten, dass alle toolübergreifenden Filter ohne Join in typspezifische Blöcke oder JSONB funktionieren, und dass Graph- und GPO-Schicht ohne Vertragsänderung darauf aufsetzen können.
Das Modell trennt drei Verantwortungen: normalisierte Filterschicht (Haupttabelle), toolspezifische Wahrheit (typspezifische Blöcke plus original_fields), und Graph-Grundlage (Asset- und Kantenschicht). Fehlende Information bleibt NULL beziehungsweise unknown; sie wird nie erfunden oder aus ungeeigneten Feldern abgeleitet.
Überblick
PROJECT
└─ ASSESSMENT
└─ SCAN_RUN
├─ IMPORT_ARTIFACT (Quelldatei, SHA-256, Tool, Version)
└─ CANONICAL_RESULT (Haupttabelle, normalisierte Filterschicht)
├─ RESULT_EXPOSURE (1:1, nur bei result_type = exposure_finding)
├─ RESULT_COMPLIANCE (1:1, nur bei result_type = compliance_check)
├─ RESULT_DEVIATION (1:1, nur bei result_type = policy_deviation)
├─ RESULT_CATEGORY (1:N)
├─ RESULT_ASSET (1:N, verknuepft mit CANONICAL_ASSET)
├─ RESULT_EVIDENCE (1:N)
├─ RESULT_REMEDIATION_STEP (1:N)
├─ RESULT_REFERENCE (1:N)
└─ RESULT_REVIEW (1:1, aktueller Stand)
Graph-Schicht (assessmentweit, referenziert Results):
CANONICAL_ASSET (deduplizierte Knoten)
├─ ASSET_PROPERTY (finding-abgeleitete Knoten-Eigenschaften)
ASSET_EDGE (gerichtete Kanten zwischen Knoten)
EDGE_COVERAGE (welche Kantentypen erhoben wurden)
Notation in den Feldlisten: PK Primärschlüssel, FK Fremdschlüssel, NN NOT NULL, IX indiziert (Filter- oder Join-relevant), enum X verweist auf den Enum-Katalog am Ende.
Scope-Tabellen
project
────────────────────────────────
id uuid PK
key text NN fachlicher Kundenschluessel
name text
created_at timestamptz NN
assessment
────────────────────────────────
id uuid PK
project_id uuid FK NN IX
name text
period text
created_at timestamptz NN
scan_run
────────────────────────────────
id uuid PK
assessment_id uuid FK NN IX
label text
run_kind text NN import | evaluation
started_at timestamptz
completed_at timestamptz
created_at timestamptz NN
Import-Provenienz
import_artifact
────────────────────────────────
id uuid PK
scan_run_id uuid FK NN IX
source_tool enum source_tool NN IX
source_tool_version text
source_format enum source_format NN html | xml | json | xccdf | arf | gpo_backup | unknown
file_name text
file_sha256 text NN IX Dublettenerkennung
byte_size bigint
extractor_id text
extractor_version text
profile_id text
profile_version text
imported_at timestamptz NN
parser_warnings jsonb Importdiagnostik
original_fields jsonb
file_sha256 trägt die Dublettenerkennung. Ein bewusster Reimport erzeugt einen neuen scan_run-Kontext; bestehende Results werden nicht überschrieben.
Haupttabelle: CANONICAL_RESULT
Die normalisierte Filterschicht. Jede toolübergreifend vergleichbare Achse ist hier eine reale, indizierte Spalte. Kein Konsument muss in einen typspezifischen Block oder nach JSONB greifen, um zu filtern.
canonical_result
──────────────────────────────────────────────────────────
Identität
id uuid PK
model_version text NN
project_id uuid FK NN IX
assessment_id uuid FK NN IX
scan_run_id uuid FK NN IX
import_artifact_id uuid FK IX
finding_definition_id text IX stabile Regelidentitaet (z. B. RiskId)
finding_occurrence_id text IX konkretes Auftreten
parent_result_id uuid FK IX optionale Hierarchie
Herkunft
source_tool enum source_tool NN IX
source_tool_version text
source_rule_id text IX RiskId / PK-ID / XCCDF rule-id / setting_key
source_rule_name text
source_locator text HTML-Selektor / XML-Pfad / Dateipfad
source_original_status text Nativwert, unveraendert
source_original_severity text Nativwert, unveraendert
source_original_category text Nativwert, unveraendert
source_original_score text Nativwert, unveraendert
Klassifikation (Achse 1: Art der Aussage)
result_type enum result_type NN IX
is_ioe boolean NN IX abgeleitet, materialisiert
ioe_rule_id text
ioe_rule_version text
ioe_derivation_method text
Bewertung (Achse 2: Ausgang der Aussage)
result_state enum result_state NN IX
severity_normalized enum severity NN IX default unknown
severity_rank smallint IX fuer Sortierung innerhalb einer Severity-Skala
severity_original text
severity_mapping_rule_id text
severity_mapping_version text
score_original numeric
score_normalized numeric NICHT tooluebergreifend vergleichbar
score_max numeric
score_unit text
Inhalt
title text NN
summary text
description text
rationale text
technical_details text
impact text
risk_statement text
remediation_summary text
remediation_details text
Aggregierte Anzeigehilfen (denormalisiert fuer schnelle Listen)
affected_asset_summary text
evidence_count integer NN default 0 IX
has_detailed_evidence boolean NN default false
primary_category_label text IX gespiegelt aus RESULT_CATEGORY (is_primary)
primary_category_namespace text IX
Quality / Provenance
recognition_confidence numeric
mapping_confidence numeric
classification_confidence numeric
severity_confidence numeric
identity_confidence numeric
evidence_completeness numeric
mapping_method text
mapping_rule_id text
mapping_rule_version text
extractor_id text
extractor_version text
Lifecycle
lifecycle_state enum lifecycle_state NN IX default new
first_seen_at timestamptz NN
last_seen_at timestamptz NN
resolved_at timestamptz
reoccurred_at timestamptz
previous_result_id uuid FK
comparison_id uuid
Zeit / Erweiterung
source_observed_at timestamptz
imported_at timestamptz
evaluated_at timestamptz
created_at timestamptz NN
extensions jsonb bekannte, noch nicht kanonisierte Zusatzwerte
original_fields jsonb vollstaendige unveraenderte Quellfelder
Regel für die denormalisierten Anzeigehilfen (primary_category_label, affected_asset_summary, evidence_count): Sie sind gespiegelte Bequemlichkeitsfelder für schnelle Listenansichten. Die Wahrheit liegt in den Kindtabellen. Die Spiegelung erfolgt deterministisch beim Aufbau des Results, nicht in der UI.
Typspezifische Blöcke
Genau ein Block ist pro Result vorhanden, abhängig von result_type. Die Blöcke tragen die native Wahrheit der Quelle; die Haupttabelle trägt die normalisierte Projektion daraus.
result_exposure (1:1, nur bei result_type = exposure_finding)
────────────────────────────────
result_id uuid PK FK
exposure_state enum exposure_state NN IX
detected | not_detected | not_evaluated | execution_error | unknown
native_severity text
points_original numeric PingCastle
weight_original numeric Purple Knight
test_result_native text Maester
result_compliance (1:1, nur bei result_type = compliance_check)
────────────────────────────────
result_id uuid PK FK
xccdf_result enum xccdf_result NN IX
pass | fail | error | unknown | notapplicable | notchecked | notselected | informational | fixed
benchmark_id text IX
benchmark_version text
rule_id text IX
profile_id text
weight numeric
check_content_ref text
result_deviation (1:1, nur bei result_type = policy_deviation)
────────────────────────────────
result_id uuid PK FK
setting_key text NN IX kanonischer Policy-Schluessel, nie Anzeigetext
setting_namespace text NN registry | privilege_right | system_access | audit | gptmpl | ...
baseline_value text
subject_value text
verdict enum verdict NN IX
match | deviation | missing_on_subject | extra_on_subject | not_comparable
baseline_ref text IX Referenz auf das versionierte Baseline-Objekt
baseline_version text IX
gpo_asset_id uuid FK betroffener GPO-Knoten, falls zutreffend
Der result_deviation-Block ist strukturell vollständig definiert, aber seine Vergleichs- und Verdikt-Logik (Setting-Identität pro Namespace, Behandlung von „nicht konfiguriert" versus „abwesend", Baseline-Registry) ist Gegenstand des noch offenen GPO-Submodells. Bis dahin bleibt der Block ungenutzt; das Modell ist bereits dafür ausgelegt.
Kindtabellen
result_category (1:N)
────────────────────────────────
id uuid PK
result_id uuid FK NN IX
namespace text NN IX pingcastle | purple-knight | maester | cis | anssi | mitre | ...
label text NN IX
path text optionaler Hierarchiepfad
is_primary boolean NN default false
Kategorien sind mehrwertig und namespaced. Eine Kategorie darf nie in eine Severity umgedeutet werden. is_primary markiert die Leitkategorie, die in canonical_result.primary_category_label gespiegelt wird.
result_asset (1:N) Verknuepfung Result zu Knoten
────────────────────────────────
id uuid PK
result_id uuid FK NN IX
canonical_asset_id uuid FK NN IX dedupliziert, siehe Graph-Schicht
role text affected | subject | related
is_primary boolean NN default false
source_locator text
Betroffene Objekte werden nie als kommaseparierter String gespeichert, sondern als Verknüpfung auf deduplizierte Knoten. Das ist zugleich die Brücke zur Graph-Schicht.
result_evidence (1:N)
────────────────────────────────
id uuid PK
result_id uuid FK NN IX
evidence_type enum evidence_type NN
table | table_row | object | property | text | configuration
| command_output | policy_observation | reference | screenshot_reference | unknown
title text
summary text
structured_data jsonb strukturierte Detailzeile, nicht Freitext
text_value text
source_view_id text
source_locator text NN
sequence integer NN Reihenfolge bleibt erhalten
Detailzeilen dürfen nicht in eine große Textspalte gepresst werden. Jede Zeile behält ihren source_locator und ihre sequence.
result_remediation_step (1:N)
────────────────────────────────
id uuid PK
result_id uuid FK NN IX
sequence integer NN
title text
instruction text
command text
validation text
Liefert ein Tool nur Remediation-Freitext, landet dieser in canonical_result.remediation_details; strukturierte Schritte bleiben leer und können später ergänzt werden, ohne den Originaltext zu überschreiben.
result_reference (1:N) Compliance und externe Referenzen
────────────────────────────────
id uuid PK
result_id uuid FK NN IX
reference_type text NN framework_control | mitre_attack | cve | cwe | vendor_doc | anssi | ...
namespace text IX CIS | enterprise-attack | ...
reference_value text NN IX z. B. T1078, 5.2.3
title text
url text
MITRE-ATT&CK- und ANSSI-Zuordnungen sind hier als Annotation abgelegt. Sie sind eine Taxonomie der Findings, keine Angriffskette.
result_review (1:1, aktueller Stand)
────────────────────────────────
result_id uuid PK FK
review_status enum review_status NN default open IX
review_disposition text
review_priority text
review_owner text
review_due_date date
review_comment text
accepted_risk_reason text
compensating_control text
updated_at timestamptz
Reviewdaten sind strikt von Importdaten getrennt und verändern keine importierten Werte.
Graph-Schicht
CANONICAL_ASSET
Deduplizierter Knotenregister. Jede reale Entität erscheint genau einmal. Identität ausschließlich über autoritativen Schlüssel; Anzeigename führt nie zu einem Merge.
canonical_asset
────────────────────────────────
id uuid PK
assessment_id uuid FK NN IX
asset_key text NN IX deterministischer Dedup-Schluessel, namespaced
asset_key_kind enum asset_key_kind NN
sid | object_guid | gpo_guid | domain_sid | trust_key | synthetic
asset_type enum asset_type NN IX
user | group | computer | domain_controller | domain
| ou | gpo | trust | tenant | service_principal | unknown
primary_name text nicht autoritativ
domain text IX
key_confidence numeric NN 1.0 bei autoritativem Schluessel, < 1.0 bei synthetic
resolution_method text
source_tools text[]
first_seen_at timestamptz NN
last_seen_at timestamptz NN
original_fields jsonb
extensions jsonb
UNIQUE (assessment_id, asset_key)
Merge-Regel (verbindlich in 1.0.0): Zusammenführung nur bei identischem autoritativem Schlüssel. synthetic-Knoten (nur-Name-Meldung ohne SID/GUID) verschmelzen nie automatisch mit autoritativen Knoten, auch nicht bei exaktem Namens- und Domänen-Match. Eine Bindung erfolgt ausschließlich über eine explizite, versionierte, protokollierte Rekonziliationsregel. Sichtbare Dubletten sind einer stillen Fehlverschmelzung vorzuziehen. Kein erfundener SID.
asset_property (1:N) finding-abgeleitete Knoten-Eigenschaften
────────────────────────────────
id uuid PK
canonical_asset_id uuid FK NN IX
property_key text NN IX z. B. kerberoastable, asrep_roastable, unconstrained_delegation
property_value text
source_result_id uuid FK Finding, aus dem die Eigenschaft stammt
mapping_rule_version text
Fähigkeiten eines Knotens (Kerberoastable, AS-REP-Roastable, Unconstrained Delegation) sind Eigenschaften, keine Kanten. Die Zuordnung Finding zu Eigenschaft läuft über eine versionierte Regel.
ASSET_EDGE
Kanten sind erste Klasse, getrennt von Findings. Gerichtet, mit Pflicht-Provenienz.
asset_edge
────────────────────────────────
id uuid PK
assessment_id uuid FK NN IX
source_asset_id uuid FK NN IX
target_asset_id uuid FK NN IX
edge_type enum edge_type NN IX
direction text NN gerichtet: source -> target
edge_source text NN Tool + Import-Artefakt
collection_method text NN
collector_version text NN
source_locator text
mapping_method text
mapping_rule_version text
confidence numeric
first_seen_at timestamptz NN
last_seen_at timestamptz NN
properties jsonb
original_fields jsonb
edge_source, collection_method, collector_version sind verpflichtend. Sie ermöglichen die ehrliche Abdeckungsdeklaration.
Kantentyp-Katalog (Stufe A aus dem Bestand erhebbar, Stufe B noch nicht in den Quellen):
Stufe A MemberOf, Trusts, AllowedToDelegate, AllowedToActOnBehalfOf,
GpLink, Contains, GPO-DACL
Stufe B HasSession, AdminTo, CanRDP, CanPSRemote, ExecuteDCOM, Objekt-DACL
EDGE_COVERAGE
Macht ehrlich sichtbar, welche Kantentypen erhoben wurden. Grundlage jeder korrekten Blast-Radius-Aussage.
edge_coverage
────────────────────────────────
id uuid PK
assessment_id uuid FK NN IX
scan_run_id uuid FK IX
edge_type enum edge_type NN
edge_source text
collected boolean NN
collector_version text
Jede Erreichbarkeits- oder Blast-Radius-Berechnung läuft über einen deklarierten Kantentyp-Filter und liefert computed_over_edge_types und uncollected_edge_types (Katalog minus erhoben) mit. Ein Blast Radius über eine unvollständige Kantenmenge unterschätzt die reale Erreichbarkeit und darf nie als vollständig dargestellt werden.
Enum-Katalog
result_type exposure_finding | compliance_check | policy_deviation | informational | unknown
result_state fail | pass | warning | error | not_applicable | not_evaluated | informational | unknown
severity critical | high | medium | low | info | unknown
exposure_state detected | not_detected | not_evaluated | execution_error | unknown
xccdf_result pass | fail | error | unknown | notapplicable | notchecked | notselected | informational | fixed
verdict match | deviation | missing_on_subject | extra_on_subject | not_comparable
lifecycle_state new | persistent | resolved | reoccurred | changed | not_comparable | unknown
review_status open | in_review | accepted | remediated | risk_accepted | false_positive | unknown
asset_type user | group | computer | domain_controller | domain | ou | gpo | trust | tenant | service_principal | unknown
asset_key_kind sid | object_guid | gpo_guid | domain_sid | trust_key | synthetic
edge_type MemberOf | Trusts | AllowedToDelegate | AllowedToActOnBehalfOf | GpLink | Contains
| GPO-DACL | HasSession | AdminTo | CanRDP | CanPSRemote | ExecuteDCOM | ObjectDACL
evidence_type table | table_row | object | property | text | configuration | command_output
| policy_observation | reference | screenshot_reference | unknown
source_tool purple-knight | pingcastle | maester | cis-cat | gpo-analysis | unknown
source_format html | xml | json | xccdf | arf | gpo_backup | unknown
Enums sind geschlossen und versioniert. Keine Quelle führt Ad-hoc-Werte ein; unklassifizierbares fällt auf unknown zurück.
Filteroberfläche
Die folgenden Spalten sind indiziert und bilden die vollständige toolübergreifende Filterebene. Kein Filter erfordert einen Join in einen typspezifischen Block oder JSONB.
canonical_result.result_type
canonical_result.result_state
canonical_result.is_ioe
canonical_result.severity_normalized
canonical_result.source_tool
canonical_result.lifecycle_state
canonical_result.primary_category_namespace
canonical_result.primary_category_label
canonical_result.evidence_count
result_category.namespace / label (fuer Mehrfachkategorien)
result_review.review_status
Typspezifische Verfeinerung über einen bewussten Join:
result_exposure.exposure_state nur exposure_finding
result_compliance.xccdf_result nur compliance_check
result_deviation.verdict nur policy_deviation
Beispiel-Abfragen:
IOE, tooluebergreifend
WHERE is_ioe = true
High-IOEs, tooluebergreifend
WHERE is_ioe = true AND severity_normalized = 'high'
Alle CIS-Haertungsluecken
WHERE result_type = 'compliance_check' AND result_state = 'fail'
Alle Policy-Abweichungen
WHERE result_type = 'policy_deviation' AND verdict = 'deviation'
Alle Fails, tooluebergreifend, unabhaengig vom Typ
WHERE result_state = 'fail'
Nur PingCastle-IOEs
WHERE is_ioe = true AND source_tool = 'pingcastle'
Findings mit strukturierter Evidenz
WHERE evidence_count > 0
Determinismus- und Provenance-Regeln
1. result_type ist geschlossen und versioniert. Keine Ad-hoc-Typen.
2. result_state wird abgeleitet, nie roh aus Quell-String oder Spaltenname uebernommen.
3. is_ioe wird per expliziter, versionierter Regel abgeleitet und materialisiert;
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. Severity nur aus nativem Wert oder versionierter, freigegebener Mappingregel.
Kategorie wird nie in Severity umgedeutet. Sonst severity_normalized = unknown.
7. score ist tooluebergreifend nicht vergleichbar und darf typuebergreifend nicht
sortiert oder schwellwertgefiltert werden.
8. Fehlend ist NULL bzw. unknown, nie erfunden.
9. Mehrwertige Daten (Kategorien, Assets, Evidenz, Referenzen, Schritte) nie als
kommaseparierter String; immer relationale Kindtabelle.
10. Knotenidentitaet nur ueber autoritativen Schluessel; Name fuehrt nie zu Merge.
11. synthetic-Knoten verschmelzen nie automatisch mit autoritativen Knoten.
12. Kanten sind erste Klasse; jede Kante traegt edge_source, collection_method, collector_version.
13. Erreichbarkeit nur ueber deklarierten Kantentyp-Filter; Ergebnis nennt uncollected_edge_types.
14. Finding zu Graph-Rolle (Eigenschaft, Kante, Overlay) nur ueber versionierte Regel.
15. Filterrelevante Werte liegen als indizierte Spalten vor, nie ausschliesslich in JSONB.
Konsumierbare API-Projektion
Konsumenten (UI, LLM, Export) erhalten nicht das rohe Schema, sondern eine versionierte Projektion. Damit bleibt das interne Schema erweiterbar, ohne Konsumenten zu brechen.
GET /api/v1/results
GET /api/v1/results/{id}
GET /api/v1/results/{id}/evidence
GET /api/v1/results/schema
GET /api/v1/assets
GET /api/v1/edges
Die UI rendert ausschließlich gelieferte Facetten und Resultwerte. Keine toolabhängige Sonderlogik im Client.
Bewusst deferiert
- GPO-/Baseline-Vergleichs-Submodell: Setting-Identitaet pro Namespace, Verdikt-Logik,
"nicht konfiguriert" vs. "abwesend", Baseline-Registry. Der result_deviation-Block ist
strukturell bereits vorhanden.
- CIS-CAT-Adapter: XCCDF/ARF vs. JSON nach Verifikation der Lizenzausgabe.
- Elevationsregeln, die einzelne Compliance-/Policy-Befunde auf is_ioe = true heben.
- Rekonziliationsregeln fuer synthetic-Knoten.
- Stufe-B-Kanten (Session, AdminTo, DACL) aus BloodHound-/SharpHound-Klasse-Quellen.
- Mandanten-/Isolationsmodell ueber project_id hinaus.
- Physisches DDL: aus diesem Modell mechanisch ableitbar (Typen, Indizes, Constraints
sind bereits annotiert).
Changelog
1.0.0 Erstfassung des konsolidierten Modells. Vereint das kanonische Result-Modell
(Haupttabelle, Kindtabellen, Provenance, Lifecycle) mit RESULT_CONTRACT 1.0.0
(result_type-Achsen, is_ioe als abgeleitete Facette, typspezifische Bloecke)
und der Asset-/Edge-Graphschicht. Filteroberfläche als indizierte Spaltenmenge
definiert. Implementierungsreif bis auf das deferierte GPO-Submodell.