Skip to content

Retain path-only, bounded audit targets

  • Status: accepted
  • Date: 2026-10-12

Context and Problem Statement

OPS-2 identified request query values reaching durable audit_log.target rows and audit.append debug lines. Bounding UTF-8 alone preserves a credential prefix; redacting selected parameter names cannot protect unknown or encoded names. Audit construction is also bypassable through aggregate initialisation of an event.

Considered Options

  • Rely on callers to sanitise request targets.
  • Retain query strings with a sensitive-parameter allow/deny list.
  • Retain only the path or non-HTTP identifier, bounded at the shared audit boundary.

Decision Outcome

Choose path-only audit targets. sanitized_audit_target removes everything from the first literal question mark and applies the existing 255-byte, control-safe UTF-8 policy. Its delimiter search and encoding work are bounded by the output ceiling. append_local_audit and remember_local_audit use the same value for logging and storage; audit construction, INSERT statement generation and summaries also apply this policy so direct event construction cannot bypass those outputs. Diagnostic HTTP-target redaction remains separate and unchanged.

Positive Consequences

  • Neither known, unknown nor percent-encoded query parameter names can expose a query value in a new audit target.
  • Routing paths, actors, categories and rejection reasons remain useful for investigation, and query-free user/room identifiers retain their meaning.
  • Existing UTF-8, control-character and byte bounds remain enforced.

Negative Consequences

  • Audit targets no longer retain even non-sensitive query parameters.
  • Existing database rows, backups and historical log files are not rewritten. Operators must treat historical audit material as potentially sensitive.
  • This does not make arbitrary secret material in a path, actor or reason safe; callers must still avoid putting credentials in those fields.

Verification

The audit-construction regression failed 20 of 21 assertions before the repair. A separate real SQLite/HTTP regression then failed eight assertions at the shared sink: durable targets and actual debug output still retained query values even after constructor repair. With the sink repaired, it passed 22 assertions, including 429, 503, database reopen, in-memory targets and the non-durable audit path. Additional bounded UTF-8 and idempotency cases are coverage-only tests. Independent review rejected the initial capture fixture because it restored an assumed console level and installed an override for previously inherited policy. An observable restoration/unwinding regression failed four assertions before repair. The fixture now uses exact prior threshold/override exchanges, asserts synchronous writers and redirects the console buffer only after configuration. Both inherited and explicit policy, including exception unwinding, are covered.