fix(audit): require off-host completeness evidence#332
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
c713ace to
bee77aa
Compare
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
bee77aa to
2ade766
Compare
A release that still wrote `<path>.anchor.json` completeness anchors can leave one behind after an upgrade; recovery moved the data files aside but let the stale sidecar keep describing the quarantined chain as current. Rename it with the same `.corrupt-<timestamp>` suffix as the data files. Also drop the unused `_max_size_bytes` parameter from `tail_hash_from_files` and pin the retained-set semantics with explicit trailing-truncation and interior-deletion verification tests. Security review: audit integrity. Recovery only renames an existing sidecar; verification semantics are unchanged and now test-documented. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A waiver targeting a gate that is startup-fail under the active profile was silently ignored at evaluation, letting an operator believe a hard gate was waived. Notary already rejects these at load (HardGateNotWaivable); Relay now does the same, sharing one waivability definition with gate evaluation and logging the gate id plus concrete remediation. The retention gate description also spells out what a local-only sink cannot detect. Security review: deployment gates. Fail-closed only: no severity or waivability changed; previously-ignored waivers now fail load, which is a breaking config change recorded in the changelog. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
HardGateNotWaivable named the gate but not what to do about it. Spell out the levers: remove the waiver and fix the condition it reports; for audit retention, ship audit events off-host and declare deployment.evidence.audit_offhost_shipping, or use a non-local sink. The retention gate description now states the truncation rationale. Security review: message text only; no gate logic changed. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…k classification posture.audit shipped last_successful_ship_at and backlog_depth as required fields hardcoded null in both products: there is no observed shipping-health contract yet (source, freshness, and backlog thresholds are an open maintainer decision), so the contract must not carry dead required fields. Remove them from the schema, emitters, fixtures, and the regenerated Notary OpenAPI baseline. The remaining shipping_target_configured/shipping_target pair is declared, config-derived state; its classification now lives in one registry-platform-ops helper (AuditSinkKind, audit_shipping_target) so Relay and Notary cannot drift, with the unknown-sink case an explicit variant instead of a silent wildcard. Security review: audit evidence surface. Emitted values are unchanged apart from the removed always-null keys; strict posture validators are affected as recorded in the changelog. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The machine-readable doctor reports could not distinguish a deployment with declared off-host shipping from one without. Both products now emit an audit_shipping object (sink_type, shipping_target_configured, shipping_target) derived from the shared classification helper, added to the strict diagnostic-report schema as an optional section that degrades like `hashes` when config is unavailable. Notary's "local-chain-only" warning now fires only when off-host shipping is not declared, matching the deployment gate it echoes. registryctl passes the optional field through as raw JSON; teaching its typed aggregation to propagate it is a follow-up. Security review: declared, config-derived state only; the schema rejects invented delivery-health fields until that contract is defined. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The audit README claimed deletions "after the first retained envelope" are detected, which is false for trailing truncation; the notary-server README still walked through the deleted anchored head/tail workflow; SECURITY_PRINCIPLES still pointed at the removed anchored verification helpers. State the real guarantee everywhere: interior edits, insertions, reordering, and deletions only; leading/trailing truncation and self-consistent rewrites need off-host completeness evidence. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Name the removed anchor APIs with migration guidance, the legacy sidecar quarantine, the posture.audit contract change for strict validators, the hard-gate waiver load failure with migration steps, and the doctor audit_shipping section. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…health Add registry.audit.ack_cursor.v1: a local state file written by whatever ships audit events off-host (acked_at, last_acked_hash, writer), plus a shared evaluate_ack_health helper (ok/stale/missing/invalid/unverified, 900s default freshness window, 300s future-skew tolerance). Posture v1 gains required nullable shipping_health/shipping_observed_at fields and the diagnostic report's audit_shipping section gains them optionally, so declared shipping evidence can now carry an observed freshness signal. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Align relay with notary's is_waivable: readiness gates exist to stop traffic when evidence guarantees fail, so a waiver must not silently un-fail them. Waivers naming readiness-fail gates are now rejected at config load with per-gate remediation guidance. BREAKING for operators waiving readiness gates (e.g. relay.audit.best_effort under evidence_grade). Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
6 tasks
Read the registry.audit.ack_cursor.v1 file named by deployment.evidence.audit_ack_cursor_path when building gate facts. relay.audit.shipping_unverified warns when off-host shipping is declared without a cursor; relay.audit.shipping_stale (finding_error under production, readiness_fail under evidence_grade, non-waivable) fires when the cursor is stale, missing, or invalid. Posture and doctor now report shipping_health and shipping_observed_at. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Read the registry.audit.ack_cursor.v1 file named by deployment.evidence.audit_ack_cursor_path when building gate input. notary.audit.shipping_unverified warns when off-host shipping is declared without a cursor; notary.audit.shipping_stale (finding_error under production, readiness_fail under evidence_grade, non-waivable) fires when the cursor is stale, missing, or invalid. Posture and doctor report shipping_health and shipping_observed_at; OpenAPI regenerated. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Model the optional audit_shipping section as a typed field on ConfigDiagnosticReport so registryctl's typed aggregation carries it through instead of silently dropping it on re-serialization. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…iver rules Record the registry.audit.ack_cursor.v1 contract, the shipping_unverified and shipping_stale gates, the new evidence config fields, the posture and doctor shipping-health fields, and the breaking relay readiness-waiver rejection in the changelogs and operator references. Frame the cursor as a liveness signal for the shipping path, not delivery-completeness proof. Correct notary's waiver reference: readiness_fail gates are also rejected at config load. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ompleteness-315 Union-resolved the notary gate catalog, gate tests, and operator config reference so main's signer-custody gate coexists with the branch's audit shipping gates. Regenerated products/notary/openapi/registry-notary.openapi.json with the documented generator instead of trusting the textual auto-merge. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #315.
Summary
posture.auditshipping-state fields and update the shared posture schema, fixtures, docs, changelogs, and Notary OpenAPI artifact.registry-platform-auditverification as retained-set tamper evidence, remove local trusted-anchor helper APIs, and stop writing.anchor.jsonrecovery sidecars.Security invariants
stdoutandsyslogare treated as externally collected sinks; localfile/jsonlrequiresdeployment.evidence.audit_offhost_shipping.startup_failandreadiness_failgates are not waiver-suppressed in Relay or Notary; a waiver naming one is rejected at config load. Production warning waivers still work where the gate remains a warning.Verification
cargo fmt --all -- --checkcargo test --locked -p registry-platform-auditcargo test --locked -p registry-platform-testing --features test-utilscargo test --locked -p registry-platform-ops --test posture_contractcargo test --locked -p registry-relay --test deployment_profile_gatescargo test --locked -p registry-relay audit_retention_local_onlycargo test --locked -p registry-relay posture_audit_summary_reports_shipping_statecargo test --locked -p registry-relay audit_quarantinecargo test --locked -p registry-notary-core audit_retention_local_onlycargo test --locked -p registry-notary-server --test deployment_gates_testcargo clippy --locked -p registry-platform-audit -p registry-platform-testing -p registry-platform-ops -p registry-relay -p registry-notary-core -p registry-notary-server --all-targets --features registry-platform-testing/test-utils -- -D warningscargo check --locked -p registry-platform-audit -p registry-platform-testing -p registry-platform-ops -p registry-relay -p registry-notary-core -p registry-notary-server --all-targets --features registry-platform-testing/test-utilsjust openapi-contractinproducts/notaryandcrates/registry-relay(OPENAPI_CONTRACT_BASE_REFunset, so breaking-change diff skipped)npm run checkindocs/site(passes; pre-existing Vale/Redocly/archive warnings only)Review follow-ups
Two rounds of fixes pushed after review.
Round 1 (
2ade7666..354594d1): quarantine legacy.anchor.jsonsidecars during chain recovery; reject waivers naming hard deployment gates at Relay config load (parity with Notary'sHardGateNotWaivable); shared audit sink classification inregistry-platform-ops; remove the always-null posture shipping-health fields (BREAKING for strict posture validators); report declared shipping state in both doctor outputs; correct chain-guarantee docs to interior-only detection; changelog migration notes.Round 2 (
354594d1..a00c385b): observed shipping health.registry.audit.ack_cursor.v1(registry-platform-ops): a local state file written by whatever ships audit events off-host (acked_at,last_acked_hash, optionalwriter).evaluate_ack_healthmaps it took/stale/missing/invalid/unverified; 900s default freshness window, 300s future-skew tolerance, fail closed on unreadable or malformed cursors.deployment.evidence.audit_ack_cursor_pathanddeployment.evidence.audit_ack_max_age_secs, with load-time validation (max-age without a cursor rejected; a cursor on a local file sink withoutaudit_offhost_shippingrejected;stdout/syslogmay carry a cursor without the attestation).*.audit.shipping_unverified(off-host shipping declared for a local sink without a cursor:finding_warnunder production and evidence_grade) and*.audit.shipping_stale(cursor stale, missing, or invalid:finding_errorunder production,readiness_failunder evidence_grade, non-waivable there).readiness_failgates are no longer waivable, matching Notary. Waivers naming them fail config load with per-gate remediation. Affected combinations:relay.admin.public_exposureandrelay.audit.sink_missingunder production;relay.oidc.client_allowlist_empty,relay.audit.best_effort, andrelay.audit.shipping_staleunder evidence_grade.posture_auditadds required nullableshipping_healthandshipping_observed_at; strict validators must update together with this release.Security review notes (round 2)
last_acked_hashbinds acks to the chain so a later change can add backlog-depth checks without a contract break.invalid(fail closed) with the reason in the doctor detail. No secrets are read or logged.audit_ack_max_age_secsor removeaudit_ack_cursor_path), not a waiver.Verification (round 2, at
a00c385b)cargo fmt --check;cargo check --locked --workspace --all-targets;cargo clippy --workspace --all-targets -- -D warningscargo test --locked --workspace: 3017 passed, 0 failed, 8 ignoredjust openapi-check(products/notary);just openapi-contract(crates/registry-relay)npm test(46/46) andnpm run check(all stages pass; one transient GitHub disconnect duringbuild:archivespassed on isolated re-run)Merge with main (
049945d0): origin/main moved during the follow-up work (signer-custody gate, audit denial coverage, registryctl template profiles, docs). Resolved as a union of both notary gate-catalog additions; the notary OpenAPI document was regenerated via its generator rather than trusting the textual auto-merge. Full gate re-run at049945d0: 3038 workspace tests passed, 0 failed, clippy clean, both OpenAPI checks and all 15 docs-site check stages green.Related: #335 fixes the OpenAPI base-comparison path bug found during review (separate PR; does not block this one).