core: control-char-safe Display for consumer-string error variants

Escapes the interpolated strings in the ReservedName/InvalidName/
InvalidSpec (and, for the same consumer-string interpolation class,
Codec) Display arms with char::escape_debug so rendered error
messages cannot carry raw control bytes from consumer input —
review 004 item 14's log-forging finding. The error taxonomy
(variants, kinds, match sites, ADR-008 §5) is unchanged; only the
rendered text's hygiene moved. Pinned by
error_display_escapes_consumer_control_chars.
This commit is contained in:
glm-5.3-flash committed 2026-10-11 14:38:27 +00:00
1 parent 9301bb926b
commit 7700079158
3 files changed
+101 -11

No files matched your search

+46 -7
View File
@@ -1,7 +1,7 @@
---
id: core-fix-display-log-forging
name: Control-char-safe Display for the consumer-string error variants (review 004 item 14)
status: pending
status: completed
depends_on: []
scope: single
risk: low
@@ -32,14 +32,14 @@ include it in the same fix (and say so in Notes).
## Acceptance Criteria
- [ ] The three variants' `Display` renders consumer strings
- [x] The three variants' `Display` renders consumer strings
control-char-safe — pinned by test (a name/spec carrying `\n`
and control bytes renders without any raw control byte)
- [ ] The error taxonomy unchanged (variants, kinds, match sites);
- [x] The error taxonomy unchanged (variants, kinds, match sites);
existing error/validation tests green unchanged
- [ ] Any additional same-class `Display` arms included (or the
- [x] Any additional same-class `Display` arms included (or the
absence recorded in Notes)
- [ ] `cargo test -p alkstore`, clippy `-D warnings`, fmt clean; doc
- [x] `cargo test -p alkstore`, clippy `-D warnings`, fmt clean; doc
gate green
## References
@@ -52,8 +52,47 @@ include it in the same fix (and say so in Notes).
## Notes
> To be filled by implementation agent
- Additional same-class arms found and fixed: one —
`Error::Codec(String)`. Its `Display` interpolated the stored
`String`, and the engine decode paths mint it with
`format!("...: {e}")` embedding driver/serde error text that is not
under core's control, so the same raw-control-byte-forge class was
structurally reachable (in practice serde's `Unexpected::Str`
already escapes via `Debug`, but the invariant was un-pinned and
un-fuzzable). Escaping at the `Display` arm makes
"no raw control byte in any rendered error message" a single-point
invariant. Side effect (accepted): serde messages that already use
`Debug` quoting render double-escaped (`\"`, `\\n`) — slightly
uglier, never unsafe; no existing test asserted literal
quote/backslash rendering.
- Implementation form: thiserror `#[error("... `{}`",
display_safe(.name))]` field-shorthand args (thiserror 2) — the
variant/field/match-site taxonomy is byte-identical (ADR-008 §5
untouched); only the three (+1) format args changed. The escaping
helper (`char::escape_debug` per char, collected) is private to
`error.rs`.
- Deliberately left alone: `Database(#[source] ...)` renders only the
static `"database error"` line (the source chain is a field of the
opaque variant, reached by the consumer's own `Error::source`
call, not by our `Display`); `LeadershipLost`/`Closed`/`PayloadTooLarge`
interpolate no consumer strings.
## Summary
> To be filled on completion
- `alkstore/src/error.rs`: `display_safe` helper (per-char
`char::escape_debug`) applied in the `Display` arms of
`ReservedName`, `InvalidName`, `InvalidSpec`, and `Codec` —
rendered messages can no longer carry raw control bytes from
consumer input. Taxonomy untouched (variants, kinds, fields,
match sites identical).
- `alkstore/src/tests.rs`: new `error_display_escapes_consumer_control_chars`
— a name/spec/message carrying `\u{1}`/`\n`/`\u{7}`/`\t` renders
with zero control chars (exact-text pin: ``invalid name `x\u{1}
\ny` ``), and plain names still render verbatim.
- Verified: `cargo test -p alkstore` (28 green, including all
pre-existing error/validation tests unchanged), workspace
`cargo build`, `cargo clippy --all-targets -- -D warnings`,
`cargo fmt --check`,
`RUSTDOCFLAGS="-D warnings" cargo doc --no-deps -p alkstore`,
and `cargo test --manifest-path fuzz/shared/Cargo.toml` (corpus
replay gate) — all clean.