Repository navigation
v0.10.0
Added
- OSCAL export —
oscal_assessment_results(read, returns the document inline) andexport_bundle --format oscal(writes<bundle>.oscal.json). A sealed evidence bundle becomes a NIST OSCAL 1.2.3 Assessment Results document: one observation and one finding per control,reviewed-controlsnaming the framework's full control set, and aback-matterresource binding the document to the bundle by chain head and per-source SHA-256. - Deterministic by construction. UUIDs are version 5, derived from the bundle's chain head, so re-exporting the same bundle produces a byte-identical document (verified end to end, not just unit-tested). Random v4 UUIDs would have thrown away the reproducibility the bundle's chain head exists to provide.
Honest about what OSCAL cannot say
Three places where the model cannot carry what this tool knows, handled explicitly — an evidence artifact that overstates is worse than none:
import-apis required and there is no assessment plan. This evidence is collected continuously by operations tooling, not produced by a planned assessment.hrefresolves to a back-matter resource that says exactly that, rather than naming a plan nobody wrote.status.statehas onlysatisfied/not-satisfied— no "partially satisfied". This tool's own model is more careful: an audit trail evidences that a control operated, not that it is correctly designed. Partial-strength controls are therefore reportedsatisfiedplus anevidence-strength=partialprop, remarks naming what the trail does not prove, a count in the result's own description, and the same count assatisfiedOnPartialEvidencein the returned summary — four places, because a baresatisfiedsilently upgrades the claim.- A truncated source scan leads the result description in prose (
POPULATION INCOMPLETE: …), not just a prop. A partial population presented as a complete assessment is the worst failure this tool could have.
Verified
- Output validated against the published NIST schema (
oscal_assessment-results_schema.json, OSCAL v1.2.3) for all six frameworks — hipaa, pci_dss, soc2, gdpr, iso27001, djcp_l3 — with synthetic bundles exercising both statuses, both evidence strengths, a truncated scan and a signed seal.scripts/validate_oscal.pyreproduces it (schema file +jsonschemarequired; neither is a runtime dependency of a tool that must work offline). - Validating for real is what found the defects below; a hand-checked emitter would have shipped them.
Changed
- A change-management gap now requires an unapproved change, not an unapproved attempt.
coverage_summary/gap_analysiscounted every high-risk write with an emptyapproved_byas a change-approval gap, ignoring the outcome the harness had already recorded. Found on a real cross-tool audit trail: two high-risk writes failed on a dropped connection and were still reported as "2 high-risk write op(s) recorded without an approver — change-approval gap". Asked to produce those two unapproved changes, the trail would have shown neither happened. The gap now rests on ops that took effect plus ops whose outcome is undetermined (a lost response may well have landed — the case that must never be filed under "did not happen"), and both payloads carry anunapprovedWritessummary withtookEffect/outcomeUndetermined/didNotLand. Attempts that did not land are still reported — they evidence a process that does not require approvers — just not as changes.
Fixed (found by that validation, before release)
- The zero-controls case emitted an invalid document, and a test called it a "valid shell". The structural check passed and the test agreed, fossilising a wrong belief; the published schema disagreed on four counts (
control-selectionsmust nameinclude-allor a non-emptyinclude-controls, andobservations/findingsareminItems: 1). There is no way in OSCAL to say "nothing was in scope", andinclude-allwould assert the opposite of the truth, so the emitter now refuses with a teaching error rather than misstating its own scope. - A prop carrying an absent field invalidated the whole document. OSCAL strings must match
^\S(.*\S)?$, so a bundle with no genesis hash — or a control with no title — produced an emptyvalueand a document no consumer would accept. Empty props are now dropped: an omitted prop says "not stated", an empty one asserts a blank value.