Skip to content

feat(audit): emit Cedar deny events to the durable chain#64

Merged
rrrodzilla merged 1 commit into
mainfrom
feat/cedar-deny-audit-emission
May 26, 2026
Merged

feat(audit): emit Cedar deny events to the durable chain#64
rrrodzilla merged 1 commit into
mainfrom
feat/cedar-deny-audit-emission

Conversation

@rrrodzilla
Copy link
Copy Markdown
Contributor

Summary

  • The Cedar engine's audit_decision / audit_field_decision helpers only write to tracing — denials never reach the BLAKE3-chained audit ledger. For NIST 800-53 AU-2 every denied access attempt needs a chain entry with the schema/entity/field triple the engine doesn't see. Emission goes at the route-handler boundaries (where state.audit_logger() is in scope) rather than threading an AuditLogger through the engine API.
  • routes/files.rs: forge.access.denied on every check_schema_access deny across mint_upload_url, confirm_upload, download_file, and on scan_complete's platform_admin gate.
  • routes/entities.rs: forge.access.denied on every record-level can_modify deny in update_entity / patch_entity and can_delete deny in delete_entity.
  • filter_entity_fields now returns the dropped-field list so a future change can roll up field-level denials into a single chain entry per request. Existing callers ignore the return; no behaviour change.

Fourth and final PR from the production-audit gap review.

Test plan

  • cargo nextest run --workspace --features schema-forge-acton/surrealdb — 1672 passed
  • cargo clippy --workspace --features schema-forge-acton/surrealdb --all-targets — clean

The Cedar engine's audit_decision / audit_field_decision functions only
write to tracing — denials never reach the hash-chained audit ledger.
For NIST 800-53 AU-2 every denied access attempt needs a chain entry
that an incident responder can pull verbatim.

This change adds chain emission at the route-handler boundaries (rather
than threading an AuditLogger through the pure engine API):

routes/files.rs:
  forge.access.denied  every check_schema_access deny on mint_upload_url
                       / confirm_upload / download_file, with the full
                       schema/entity/field triple the engine doesn't see.
  forge.access.denied  scan_complete's platform_admin gate when a
                       non-admin caller tries to flip a file's status.

routes/entities.rs:
  forge.access.denied  every record-level can_modify deny in
                       update_entity and patch_entity.
  forge.access.denied  every record-level can_delete deny in
                       delete_entity.

filter_entity_fields now returns the dropped-field list so a future
change can roll up field-level denials into a single chain entry per
request. Existing callers ignore the return; no behaviour change.

Fourth and final PR from the production-audit gap review.
@rrrodzilla rrrodzilla force-pushed the feat/cedar-deny-audit-emission branch from b0a92da to 13ac7e2 Compare May 26, 2026 21:17
@rrrodzilla rrrodzilla merged commit f7a4737 into main May 26, 2026
1 check passed
rrrodzilla added a commit that referenced this pull request May 26, 2026
Release rolling up the production-audit gap PRs (#61, #62, #63, #64).

BREAKING (pre-1.0 minor):
  schema-forge-backend 0.11 → 0.12
    AuthStore trait gains required `record_login(username, at)` method.
    Downstream impls must add it.
  schema-forge-acton 0.30 → 0.31
    DynAuthStore trait gains required `record_login` shim.
    `access::filter_entity_fields` now returns `Vec<String>` (dropped
    field names) instead of `()`. Callers binding the return value
    must update.
@rrrodzilla rrrodzilla deleted the feat/cedar-deny-audit-emission branch May 28, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant