Skip to content

v1.15.3 — control identifier consistency, fail-closed DB password, POA&M generator

Latest

Choose a tag to compare

@clem-field clem-field released this 28 Jul 20:12
v1.15.3
d8ec981

SPARC v1.15.3 is an integrity and consistency release. The through-line: the same control could be identified two different ways depending on which document you exported, and nothing would ever tell you. Alongside that, an operator could run SPARC against an unauthenticated database by omission, PIV sign-in was broken behind an AWS ALB, and the POA&M — the artifact customers touch monthly — was the one document in the authorization chain that could not be generated in-app.

Documents now agree with each other

One canonical control identifier (#852). SPARC had at least a dozen ad-hoc control-id transformers: four byte-identical copies of one normalizer, three padding implementations, two de-padding ones, and a weaker variant in the mapping export. None reconciled zero-padding, so AC-02 and ac-2 were different strings despite naming one control. That is what broke documents flowing Catalog → ATO Package.

The catalog was always right — its importer stores canonical as the identifier and padding only as a sort key. The defect was downstream failing to resolve against it.

Two consequences, now closed:

  • A mapping wrote ac-2-(1) while the SSP, SAP, SAR and POA&M wrote ac-2.1 for the same control. No validator would ever flag thisid-ref is StringDatatype in the mapping schema — so nothing linked a mapping entry to a control in another document, silently.
  • A control selection of ac-2 matched nothing against a stored AC-02, producing an empty SAP reported as success.

Also fixed: the POA&M export passed target_data through verbatim, so a finding carrying AC-2 (1) made the entire document fail validation at export; and the profile export wrote with-ids and modify/alters/control-id raw. Both are TokenDatatype.

The evidence is the feature. Every export that publishes a control identifier — ssp, assessment-results, assessment-plan, component-definition, mapping, poam, profile — is now asserted to emit legal OSCAL tokens and validate, under deliberately hostile inputs (AC-2 (1), AC-02, AC-2(1), whitespace-padded). Schema validity alone was never sufficient evidence, which is exactly how ac-2-(1) shipped unnoticed.

Normalisation covers form, not vocabulary: CCI-000213 is normalised in shape and never translated into a NIST control, because mapping an external identifier is a lookup, not a string transform.

Fail closed rather than run insecure

A missing database password no longer connects anyway (#849). A SPARC_DB_* block without SPARC_DB_PASSWORD used to connect with no password at all — nil reaches libpq, a server on trust auth accepts it, and the app boots, passes health checks and serves traffic against an unauthenticated database. Nothing looked wrong.

Production now refuses to start, naming the unset variable and every supported fix. Development and test are unaffected. SPARC_DB_ALLOW_EMPTY_PASSWORD=true is the explicit opt-out for RDS IAM authentication — running without a database password should be a decision, not something that happened to you.

Also fixed on that path: "" is truthy in Ruby, so an explicitly-empty SPARC_DB_PASSWORD beat the fallbacks and was handed to libpq as a real empty password.

PIV sign-in behind an ALB

Smart-card login failed on a complete, gateway-verified certificate (#850). safe_unescape used CGI.unescape, which implements form encoding and decodes + as a space. Base64 uses + as data, and ALB passthrough leaves it literal — so every + became a space and the whitespace strip then deleted it, losing one character per +.

The corruption passed every internal check: the shortened body still matched the base64 charset guard and the minimum length, so the certificate reported as normalized and only OpenSSL rejected it. The v1.15.1 test could not catch it because it encoded with CGI.escape, which writes + as %2B — it round-tripped its own encoder.

The lifecycle can be completed in-app

POA&M generator (#843). Every other document in the chain had one; POA&M had only parsers and an exporter, so the terminal artifact of an ATO could enter SPARC only by importing external OSCAL or hand-assembling its object graph. A SAR's open risks now become POA&M items, carrying findings and observations across through the assessor's own linkage.

Nothing is synthesised. A source record that cannot form a valid POA&M entry is skipped and reported with a reason, never completed on the author's behalf — a risk statement, a deadline and a finding target are compliance content an AO reads. The one derived value is deadline, and only from the admin-provisioned RemediationTimeline SLA; if no window resolves, the risk is skipped rather than given an arbitrary date.

Standalone SAP generation (#844). POST /api/v1/sap_documents/generate builds a populated SAP from an SSP, a profile, or just a boundary — a plan is not a once-per-authorization artifact, and a 3PAO or automated pipeline could previously create only an empty shell.

Storage, security and hygiene

Structured object keys (#830). Every blob landed in a flat namespace with a random key, so prefix-scoped IAM was impossible and evidence under a retention obligation could not be separated from a user avatar. Keys are now sparc/… for instance-wide and <organization>/<boundary>/… for tenant data, so the bucket root is the tenant list. Migration is copy-then-update via sparc:storage:audit and sparc:storage:relocate (dry-run unless APPLY=true).

Cross-boundary evidence disclosure (#851). KSI validations accepted an evidence_id from another authorization boundary, and the detailed serializer returned that evidence's title, type, status and file hash. Found while triaging CodeQL — which had flagged the safe thing on that line and missed this two arguments away.

CodeQL triage (#846). All 8 open alerts confirmed false positives and dismissed with their specific claims recorded.

yamllint (#847). 815 findings, all line-length, none structural. The limit now applies to hand-maintained config with the rest exempt from that rule only.

Behaviour changes worth reading before upgrading

  1. Production refuses to start without a database password. Set SPARC_DB_PASSWORD, use DB_CREDENTIALS, or declare SPARC_DB_ALLOW_EMPTY_PASSWORD=true.
  2. Exports emit unpadded control ids. A document with padded stored controls previously exported ac-02 and now exports ac-2. Both are legal OSCAL tokens; unpadded is what every other export already produced and is what makes cross-document linkage work.
  3. New blobs use structured keys. Existing blobs keep theirs — a mixed layout is normal and both remain readable. SSP_TPR_MANAGER_DATABASE_PASSWORD is now documented as deprecated, scheduled for removal in 1.16.0.

Verification

Full suite 3,730 examples / 0 failures. API contract suite 385 / 0. Playwright ui-smoke 320 / 0 with two previously-uncovered accessibility baselines captured. The latter two ran against the UBI9 production image over TLS, where structured storage keys, canonical control ids and CCI preservation were each confirmed in a running production environment.

Closes: #830, #843, #844, #846, #847, #849, #850, #851, #852