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.
4.3 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||
|---|---|---|---|---|---|---|---|---|---|---|---|
| core-fix-display-log-forging | Control-char-safe Display for the consumer-string error variants (review 004 item 14) | completed | single | low | isolated | implementation |
|
Description
The core error Display log-forging risk, per
docs/reviews/004-pre-release-general-review.md §"Findings recorded,
not fixed" item 14: InvalidName/ReservedName/InvalidSpec
interpolate raw consumer strings into Display; control characters
can forge log lines in naive consumers. Typical-idiom risk, low — but
the fix is small and real.
Fix: escape/sanitize the interpolated payload in the three variants'
Display arms (e.g. char::escape_debug or a control-char filter) so
the rendered message cannot carry raw newlines/control bytes from
consumer input. The error taxonomy itself is untouched — variants,
kinds, and match sites stay exactly as pinned (ADR-008 §5); only the
rendered text's hygiene changes.
While there: grep the Error enum's other Display arms for the same
consumer-string interpolation class; if any other variant carries it,
include it in the same fix (and say so in Notes).
Acceptance Criteria
- The three variants'
Displayrenders consumer strings control-char-safe — pinned by test (a name/spec carrying\nand control bytes renders without any raw control byte) - The error taxonomy unchanged (variants, kinds, match sites); existing error/validation tests green unchanged
- Any additional same-class
Displayarms included (or the absence recorded in Notes) cargo test -p alkstore, clippy-D warnings, fmt clean; doc gate green
References
- docs/reviews/004-pre-release-general-review.md §"Findings recorded, not fixed" item 14 (the finding)
- docs/architecture/decisions/008-*.md §5 (the error taxonomy — the part that must not change)
- alkstore/src/errors (the
Displayarms)
Notes
- Additional same-class arms found and fixed: one —
Error::Codec(String). ItsDisplayinterpolated the storedString, and the engine decode paths mint it withformat!("...: {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'sUnexpected::Stralready escapes viaDebug, but the invariant was un-pinned and un-fuzzable). Escaping at theDisplayarm makes "no raw control byte in any rendered error message" a single-point invariant. Side effect (accepted): serde messages that already useDebugquoting 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_debugper char, collected) is private toerror.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 ownError::sourcecall, not by ourDisplay);LeadershipLost/Closed/PayloadTooLargeinterpolate no consumer strings.
Summary
alkstore/src/error.rs:display_safehelper (per-charchar::escape_debug) applied in theDisplayarms ofReservedName,InvalidName,InvalidSpec, andCodec— rendered messages can no longer carry raw control bytes from consumer input. Taxonomy untouched (variants, kinds, fields, match sites identical).alkstore/src/tests.rs: newerror_display_escapes_consumer_control_chars— a name/spec/message carrying\u{1}/\n/\u{7}/\trenders 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), workspacecargo build,cargo clippy --all-targets -- -D warnings,cargo fmt --check,RUSTDOCFLAGS="-D warnings" cargo doc --no-deps -p alkstore, andcargo test --manifest-path fuzz/shared/Cargo.toml(corpus replay gate) — all clean.