Jeder OSCAL-Validator behauptet, „OSCAL zu validieren". Kaum einer kann sagen, was dieser Satz bedeutet.
NISTs OSCAL-Modelle sind in Metaschema verfasst – acht Root-Module plus gemeinsame Importe, tausende Zeilen XML, die nicht nur die Form von Dokumenten definieren, sondern ihre Semantik: erlaubte Wertevokabulare, Eindeutigkeitsregeln, Querverweis-Indizes, Kardinalitätsanforderungen. Ein JSON-Schema fängt die Form. Die Semantik ist der Ort, an dem Compliance-Dokumente tatsächlich schiefgehen – und an dem aus „wir validieren OSCAL" leise „wir validieren einen Teil von OSCAL, wir sind nicht sicher welchen" wird.
Wir wollten sicher sein, welchen Teil. Also haben wir unseren Validator so gebaut, wie man ein System auditiert: bei den Quellen anfangen, alles enumerieren und jeden einzelnen Posten erklären.
Die erste Entscheidung war die wichtigste: Die NIST-Quellen sind die Autorität – nicht die Referenzimplementierung. Wir haben usnistgov/OSCAL beim Commit 21403b4a… (v1.2.2) gepinnt, jedes Metaschema byte-exakt mit SHA-256-Hashes gecacht und das altgediente Java OSCAL CLI als das behandelt, was es tatsächlich ist: ein Komparator zum Kreuzverhör, keine Wahrheit zum Abschreiben. (Es bettet zum Beispiel OSCAL-1.2.1-Bindings ein – es kann 1.2.2-Dokumente verarbeiten, aber es spricht kein 1.2.2.)
Dann haben wir jede Constraint-Occurrence in den gepinnten Quellen lexikalisch inventarisiert. Nicht Constraint-Typen – Occurrences, identifiziert über Datei und Zeile, sodass zwei Constraints mit zufällig gleicher ID nie zu einer kollabieren. Das Ergebnis: 348.
Für jede Occurrence muss unser Build genau eines von drei Urteilen halten, erzwungen von einem Generator, der das Verzeichnis bei jedem Lauf neu berechnet:
Unerklärt: null.
Der letzte Bucket – die Ausschlüsse – ist der, in dem es interessant wurde.
Wer 348 Constraints einzeln prüft, findet Dinge. Drei unserer Ausschlüsse sind Defekte in den NIST-Quellen selbst:
oscal_mapping-common_metaschema.xml, Zeile 657, beschränkt ein Flag, das fünf Zeilen weiter oben definiert wird – und dann von nichts referenziert wird. Kein existierbares Dokument trägt dieses Flag. Die Constraint ist unerfüllbar.inventory-item-Prop-Namen greift nur, wenn @type gleich software, hardware oder service ist – aber inventory-item deklariert überhaupt kein type-Flag. (Sein Nachbar asset-id ist in der Quelle selbst auskommentiert.) Das Prädikat kann nie matchen.with-child-controls im Profile-Metaschema. Ein Mensch, der die Quelle liest, sieht sie; das kompilierte Schema setzt sie nie durch. Alle drei Funde sind upstream gemeldet: usnistgov/OSCAL#2254, #2255, #2256.Und das Audit schnitt in beide Richtungen: Unser eigener Evaluator hatte einen Bug, durch den Constraints auf Flags (wie die erlaubten Werte von action/@type) zwar ins Inventar kompilierten, aber nie feuerten. Wir haben ihn nicht durch Glück gefunden – das Closure-Gate verweigerte die Evidenz für genau diese zwei Occurrences. Exakt der Fehlermodus, für dessen Aufdeckung das ganze System gebaut ist.
„Die Referenzimplementierung übersieht Dinge" allein aus Quellen-Inspektion zu behaupten, ist genau die Art unbewiesener Aussage, gegen die dieses Projekt existiert. Also haben wir das Java OSCAL CLI 3.2.0 von Maven Central geladen, hash-verifiziert und beide Validatoren über eine Fixture-Familie gefahren, deren Basisdokumente auf beiden Seiten sauber durchlaufen – jede Abweichung ist damit auf genau eine injizierte Änderung zurückführbar.
OSCAL 1.2.2 hat jenseits der Versionsnummern genau zwei Constraints geändert (beide im SSP-Modell). Das Java CLI – mit seinen 1.2.1-Bindings – liegt bei jeder beobachtbaren Konsequenz falsch:
rel="validation"-Komponenten-Link verletzt 1.2.2. Java: valid, Exit 0.rel="validated-by"-Link ist laut 1.2.2 in Ordnung (die Constraint wurde gelöscht). Java: invalid.responsible-role ohne das optionale party-uuid ist laut 1.2.2 in Ordnung – NIST hat das Prädikat genau dafür ergänzt. Java meldet, wörtlich, Key reference [null] not found.Und dann eines, das gar nichts mit Versionsversatz zu tun hat: ein Katalog, dessen Metadaten-action den Wert "type": "invented-type" deklariert – verboten auf Zeile 877 des Metadata-Metaschemas, in 1.2.2 wie in den 1.2.1-Quellen, die Java einbettet. Java erklärt ihn für valide, in JSON und XML. Die Constraint zielt auf ein Flag; still nicht feuernde Flag-Constraints sind exakt die Fehlerklasse, die unser Vollständigkeits-Gate in unserem eigenen Evaluator gefangen hat. Die Referenzimplementierung hat dieselbe Fehlerklasse – und kein Closure-Gate, das sie fängt. Wir haben sie upstream als metaschema-framework/oscal-cli#279 gemeldet. (Wir haben die Klasse mechanisch kartiert: genau 2 von 200 allowed-values-Occurrences zielen auf Flags, und das einzige fehlerfähige Mitglied ist das live getestete. Kein ungetesteter Rest.)
Und der Ehrlichkeits-Beat, denn der Lauf schnitt in beide Richtungen: Unser Validator lehnte NISTs eigenen SP-800-53-rev5-Katalog ab. Zwanzig Fehler, ein einziger Defekt – unser Index-Evaluator behandelte ein fehlendes optionales Key-Feld als Violation, wo die Metaschema-Spezifikation einen Null-Key vorsieht. Java akzeptierte die Datei; wir lagen falsch; wir haben es am selben Tag behoben, und 800-53 validiert sauber. Später fing der Lauf noch einen zweiten: Wir erzwangen das OSCAL-Action-Type-Vokabular auch dann, wenn action/@system eine eigene URI deklarierte – genau die organisationsweise Segmentierung, die NISTs eigene Bemerkungen vorsehen. Ebenfalls behoben. Eine Differential-Harness, die immer nur die Bugs der Gegenseite findet, ist keine Harness, sondern Marketing.
(Ebenfalls unter „beide Seiten" abgelegt: Keiner der beiden Resolver validierte sein eigenes Ergebnis semantisch. Javas resolve-profile schreibt anstandslos einen Katalog, den Javas eigenes validate anschließend ablehnt – und unserer tat dasselbe, schema-geprüft, aber nicht semantik-geprüft. Unserer verweigert semantisch invalide Auflösungen inzwischen; Java kann das weiterhin nicht, denn dafür braucht man eine semantische Schicht, der man traut.)
Das Zweite, was der bestehenden Werkzeuglandschaft fehlt: OSCAL-Dokumente leben nicht allein. Ein Assessment Result importiert einen Assessment Plan, der ein SSP importiert, das ein Profil importiert, das gegen Kataloge aufgelöst wird. Die Modelldokumentation ist voller Beziehungen, die kein Einzeldatei-Validator prüfen kann – „das Target dieses Findings muss auf ein Statement der Baseline auflösen", „dieses POA&M muss sein System identifizieren".
Also haben wir dokumentübergreifende Validierung gebaut, die diese Kanten wirklich auflöst. Wer ein SSP prüfen lässt, bekommt die importierte Baseline durch die vollständige (Draft-)Profilauflösungspipeline bis zu den Katalogen aufgelöst, die selektierte Control-Menge berechnet – und Antworten wie diese:
{
"ruleId": "SSP-003.b",
"severity": "warning",
"path": "/…/implemented-requirements/1/control-id",
"message": "implemented control 'ac-99' is not supplied by the resolved import-profile 'file:///…/baseline.json'"
}
Mit einer entscheidenden Designregel: Diese Beziehungsprüfungen sind Interpretationen dokumentierter Prosa, und so behandeln wir sie – standardmäßig aus, Warnungen statt Fehler, der Auflösungs-Scope jedes Feldes an seine Quellzeile gepinnt, und alles bloß Erschließbare namentlich ausgeschlossen. Wo der Standard sich bedeckt hält, sollte ein Validator nicht bluffen. (Unser Lieblingsbeispiel: Die POA&M-Spezifikation sagt, ein SSP-Import oder eine System-ID sei erforderlich – „Both may be present." Wir warnen also nur, wenn keines von beiden existiert. Kein erfundenes Exklusiv-Oder.)
Weil die Dokumente in Browsern, Serverless-Funktionen, CLIs und Datenbanken leben – und wir einen Validator in allen wollten, nicht eine JVM-Sidecar daneben. Die Validatoren sind vorkompilierte Ajv-Standalone-Module: schlichtes generiertes JavaScript, null Schema-Kompilierung zur Laufzeit, byte-stabile Ausgabe. Derselbe Code, der unsere Validierungsoberflächen antreibt, läuft unverändert im Browser-Tab und in unserem CLI. Und Determinismus ist keine Ops-Nettigkeit – er ist es, der hash-gepinnte Evidenz überhaupt möglich macht.
import { createDiagnosticsOscalProcessor } from "@secani/oscal/diagnostics";
const result = createDiagnosticsOscalProcessor({ maxIssues: 50 }).validate(doc);
validate --resolve-imports --interpretive all)Der vollständige technische Report – Autoritätsmodell, Vollständigkeits-Closure, die Differential-Belegtabelle und was wir bewusst nicht behaupten – steht in der Validator-Dokumentation. Der Validator ist in privater Vorbereitung vor einem Open-Source-Release; die gehostete Validierungs-API exponiert heute die Schema-Ebene, die semantische Schicht folgt.
Secani verbindet Scopes, Nachweise, Aufgaben und KI-Agenten in einem gemeinsamen Arbeitskontext.
OLIR liefert Mapping-Inhalte und Governance. OSCAL schafft die maschinenlesbare Struktur, um diese Mappings für Gap-Analysen, die Wiederverwendung von Nachweisen und Change-Impact-Workflows einzusetzen.
OSCAL macht aus Compliance-Dokumenten strukturierte Daten: acht Dokumentmodelle, drei Formate und ein Ökosystem, das gerade zum Standard für Regulierung wird.
Mit RFC-0024 und den Consolidated Rules 2026 macht FedRAMP strukturierte Autorisierungsdaten zur Pflicht. Die Fristen sind gestaffelt – die Richtung ist eindeutig.