Skip to content

v1.15.2 — OSCAL export conformance, POA&M integrity, credential recovery

Choose a tag to compare

@clem-field clem-field released this 28 Jul 13:31
v1.15.2
5cc4e69

SPARC v1.15.2 is a correctness and integrity release. The through-line: SPARC could produce documents that were not valid OSCAL, and only find out much later — or never. Every fix here moves the failure to the point of entry, and none of them fills a gap by inventing content.

It also closes a credential-recovery hole and makes RDS password rotation work without a redeploy.

OSCAL export conformance

Every OSCAL XML export was schema-invalid (#827). The converter walked the parsed JSON in insertion order, but each OSCAL assembly is an xs:sequence with a mandated child order. download_xml is a user-facing route on seven document types and all seven failed their XSD.

Ordering turned out to be the first of five defects, all the same mistake — treating a per-type schema fact as a global rule:

Before Now
element order JSON key order the parent's xs:sequence
attributes one global list per type — name is an attribute of <prop> but an element of <party>
prose chosen by key name markup-line (text) vs markup-multiline (<p>), from the schema
text content n/a xs:simpleContent puts the value in the text position
array names a hand-written plural map schema-resolved, incl. wrappers (revisions) and renames (remediations<response>)

The ordering table is generated from the same XSDs the validator uses, so the order used to write XML cannot disagree with the order used to check it. A drift spec regenerates and compares, so it cannot rot.

The ATO package (#828, #829). The manifest was built from the boundary's associations while the archive was built from exports that could fail — so a failed export was skipped silently while the manifest went on naming the missing file. Exports now run first and both the archive and the manifest derive from the same results, so they cannot disagree. Omissions are stated in the manifest with their error.

The package also shipped JSON only; it now emits JSON, YAML and XML, each validated against the schema matching its serialization, with format and conformance recorded per file.

sar_from_hdf returned schema-invalid OSCAL unchecked (#831). A consumer got a 200 and a document no OSCAL tool would accept. It now validates before returning.

POA&M integrity

PoamRisk validated only uuid (#832). A risk could be saved with no title, description, statement, status or deadline — invalid OSCAL could be created, failing only at export with nothing to say which record was at fault. Now validated on every save. deadline is a SPARC rule rather than an OSCAL one: a POA&M with no time commitment is not a plan, and hdf convert refuses it.

HDF aggregation made the POA&M non-exportable (#840). HdfAggregationService wrote findings with no OSCAL target, so one aggregation run made the entire document fail schema validation in every serialization — the user met it at export as ?oscal_validation_failed=1. The target is now derived from the control the scanner actually assessed.

Nothing in either fix is defaulted or synthesised: a risk statement, a deadline and a finding target are all substantive compliance content. Pre-existing rows are found with bin/rails sparc:poam:audit_risks and sparc:poam:audit_findings, which deliberately offer no auto-fix.

Credentials and recovery

A forgotten local-login password was unrecoverable (#841). No admin reset, no self-service flow, and the password-change screen requires the current password — which is exactly what has been lost. The only way back in was a Rails console. Two routes now exist, and neither leaves an administrator holding a password the user keeps:

  • Temporary password — works anywhere including with no outbound mail. The admin hands it over out of band and the user must replace it at first sign-in.
  • Emailed link — a single-use, expiring token (requires SMTP).

The break-glass admin credential could not stay in sync (#841). Bootstrap forced a password change even when the password came from Secrets Manager, which made a loop: the operator complies, the credential recorded in SM is now stale, and the next restart overwrites their choice and forces another change. A forced change now applies only when SPARC generated the password itself.

RDS connection from DB_CREDENTIALS (#834). DATABASE_URL is rendered at deploy time and therefore pins the password, so a Secrets Manager rotation had no effect until the next redeploy. SPARC now prefers the structured secret, read at boot — a rotated password is picked up on the next task restart with no IaC change. This unblocks the DB half of sparc-iac#531.

Three defects were found and fixed on that path during review: the password was rendered into unquoted YAML (a value containing & was read as a YAML anchor and became nil — connecting with no password); the container entrypoint resolved the database host in the shell using a different precedence than Rails, so it could wait on one database and connect to another; and database credentials could reach CloudWatch through driver errors and inspect output. Credentials are now redacted from logs by default, with SPARC_LOG_CREDENTIALS=true as a deliberate, loudly-warned escape hatch.

Behaviour changes worth reading before upgrading

  1. POST /api/v1/oscal/sar_from_hdf returns 502 for real HDF input instead of 200-with-invalid-OSCAL, until a fixed hdf-libs is pinned (mitre/hdf-libs#184). The caller's input is fine; the fault is upstream, which is what a Bad Gateway means.
  2. POA&M imports are stricter. Third-party POA&Ms with incomplete risks or findings are now rejected rather than silently creating invalid records.
  3. Pre-existing incomplete PoamRisk / PoamFinding rows are unsaveable until completed. That is the point; the audit tasks exist to find them.
  4. The ATO package contains 3× the files — one per serialization.
  5. DB_CREDENTIALS outranks DATABASE_URL, and removes it from the environment when both are set. The resolved source is now reported at boot, with a warning when a second source is configured but ignored.

Also included

  • hdf-cli pinned to 3.4.1 across the repo (#837) pending mitre/hdf-libs#184.
  • Dependency updates: patch group (aws-sdk-s3, csv, net-imap, pagy, simplecov) and solid_queue 1.5.0. openssl 4.x was declined — it would revert a deliberate pin (#779), and bundle-audit reports no vulnerabilities on the current lock.

Verification

Full suite 3,537 examples / 0 failures; API contract suite 385 / 0; Playwright 201 / 0 — the latter two run against the UBI9 production image over TLS, not the Debian dev compose.

Closes: #827, #828, #829, #831, #832, #834, #840, #841