CipherFS
In development · unreleased · format may change

ADR 006 Encrypted device access audit

Status

Design accepted 2026-10-18. Implemented with current-entry functional acceptance on 2026-10-24. The authoritative byte layout is FORMAT section 11; current controls are in Access audit.

Context

The signed index proves authored changes but does not record reads, cache hits, denied attempts or transport delivery. Servers also need the application identity verified by their actual authentication adapter. A file access log must preserve these facts without placing paths, bodies or authentication secrets in readable storage or provider logs.

Decision

Use a single Core audit hub with explicit trusted entry contexts. Capture FileId and immutable VersionId at the actual operation snapshot. Keep frontend delivery observations separate from Core business facts, connected by parent operation IDs. Group facts from one business transaction with a transaction ID. Counts describe bytes/items accepted at that boundary; they do not establish human consumption.

Configure off, best_effort and required independently for each entry. HTTP and S3 default to required; daemon mounts and daemon background work default to best effort; other entries default to off. Required intent precedes business access and commit facts share the business transaction. Required completion precedes successful return. An audit failure after a known commit returns a structured failure retaining the exact known receipt. Best effort exposes bounded loss/gap summaries without silently adopting the required policy. Sealed owners retain bounded denial summaries until an authenticated writer is available.

Use independent CFSAUD01 encrypted, signed per-device chains in store prefix a/. Derive each audit key from the epoch key followed by the author DeviceId with cipherfs audit v1; the key cannot sign records. Members with that epoch key can read historical logs, including logs authored by a later-revoked member. Later epochs exclude revoked devices. Recovery envelopes make the corresponding epoch history recoverable.

Keep durable intent, reserved finish capacity, commit facts and crash state in sealed local tables. Prepare final ciphertext before spool publication and reuse its exact bytes after a crash. Fsync the private spool file, rename and directory before discarding outbox records. Require every configured store's positive acknowledgment before deleting the local spool object. Bound queues, event sizes, persistent budgets, query input and shutdown waits. Revocation wipes audit key copies; writer leases prevent a timed-out generation from racing a new owner.

Verify signatures before decryption and verify every bounded query input before filtering. File/device pages refer to a sealed, expiring local snapshot. A missing retained prefix and an unconfirmed remote tail remain explicit. Audit retention has its own conservative first-observation minimum and delete permission; ordinary content/index GC never collects audit objects.

Consequences

Runtime tracing and version history remain separate facilities. This audit covers operations through the instrumented Core and trusted adapters, including cache hits; it cannot observe plaintext copied outside those boundaries. OS UID/GID/PID establish an OS observation, not executable trust. FUSE replies have no receipt and retain unknown delivery. A store can withhold a complete tail, so a verified observed chain does not prove global completeness.

Required mode adds durable barriers and may reject access when its bounded storage is full or its writer fails. Operational status and readiness make this visible. Query cache deletion does not delete the published audit. Device-local audit tables belong to the current unreleased schema profile; this change does not implement a production legacy migration.

Source file in the repository