Skip to content

v0.10.0

Choose a tag to compare

@zw008 zw008 released this 12 Aug 01:11
· 20 commits to main since this release

Added

  • OSCAL export — oscal_assessment_results (read, returns the document inline) and export_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-controls naming the framework's full control set, and a back-matter resource 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-ap is required and there is no assessment plan. This evidence is collected continuously by operations tooling, not produced by a planned assessment. href resolves to a back-matter resource that says exactly that, rather than naming a plan nobody wrote.
  • status.state has only satisfied / 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 reported satisfied plus an evidence-strength=partial prop, remarks naming what the trail does not prove, a count in the result's own description, and the same count as satisfiedOnPartialEvidence in the returned summary — four places, because a bare satisfied silently 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.py reproduces it (schema file + jsonschema required; 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_analysis counted every high-risk write with an empty approved_by as 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 an unapprovedWrites summary with tookEffect / 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-selections must name include-all or a non-empty include-controls, and observations/findings are minItems: 1). There is no way in OSCAL to say "nothing was in scope", and include-all would 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 empty value and a document no consumer would accept. Empty props are now dropped: an omitted prop says "not stated", an empty one asserts a blank value.