128 lines
6.3 KiB
Markdown
128 lines
6.3 KiB
Markdown
---
|
|
id: fuzz-target-name-validation
|
|
name: Fuzz target — `name_validation` (core name validation)
|
|
status: completed
|
|
depends_on: [fuzz-scaffold]
|
|
scope: narrow
|
|
risk: trivial
|
|
impact: component
|
|
level: implementation
|
|
tags: [wave-7, fuzzing]
|
|
---
|
|
|
|
## Description
|
|
|
|
The §3.3 target: `name_validation` — the cheapest target, driving core's
|
|
already-`pub` validation surface (`alkstore/src/validation.rs`:
|
|
`validate_name` over all `NameKind`s, `validate_shared_name`,
|
|
`validate_local_name`). No exposure needed.
|
|
|
|
- **`fuzz_targets/name_validation.rs`** + **`shared/src/
|
|
name_validation.rs`** (invariants + `#[cfg(test)]` replay),
|
|
`[[bin]]` entry. Input: arbitrary `&[u8]` → strings (lossy + a
|
|
structured path), lengths spanning the caps, all `NameKind`s.
|
|
- **Invariants** (§3.3, verbatim): total (no panic on any input);
|
|
deterministic accept/reject (same input → same verdict, pinned by
|
|
double-invocation in the invariant fn); rejection only via typed
|
|
`Error::InvalidName` (or `ReservedName` where the reserved-prefix
|
|
rule fires — both are the taxonomy's typed rejections; nothing else,
|
|
no panic); the reserved-prefix rule (`__alkstore_`, ADR-014 §1's
|
|
shared namespace) rejects in every casing/boundary form the fuzzer
|
|
finds; length-boundary exactness at the cap (the ±1 neighborhood —
|
|
a name at the cap is accepted, one past it rejected, per the pinned
|
|
rule).
|
|
- **Seeds** via `gen_fuzz_seeds.py`'s `name_validation` function:
|
|
cap-boundary lengths (±1, ±2), the reserved prefix in plain /
|
|
suffixed / embedded forms, empty and whitespace-only strings, unicode
|
|
and control-char shapes, the reserved listener-reconnected string.
|
|
Corpus committed; gitignore negation.
|
|
- **`fuzz/README.md`** targets-table row.
|
|
|
|
Low expected yield (the doc says so) — the value is the entry-point
|
|
domain guard demonstrated over the whole string space, the gate's
|
|
guard-order discipline made continuous.
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [x] `fuzz_targets/name_validation.rs` + `shared/src/
|
|
name_validation.rs` exist; `cargo fuzz build` builds the target
|
|
- [x] All §3.3 invariants implemented and exercised by both the fuzz
|
|
path and the replay module
|
|
- [x] Seed corpus committed, deterministic-generated, gitignore
|
|
negation in place
|
|
- [x] `cargo test --manifest-path fuzz/shared/Cargo.toml` green;
|
|
`shared` clippy/fmt gates green
|
|
- [x] Root workspace gates still green
|
|
|
|
## References
|
|
|
|
- docs/research/fuzzing.md §3.3 (target spec), §4, §7
|
|
- docs/architecture/core-contract.md §Naming / reserved namespace
|
|
- docs/architecture/decisions/008-contract-v1-pinning.md §4/§5 — the
|
|
reserved-prefix rule and the typed-rejection taxonomy the target
|
|
demonstrates (the description's `014-naming-and-reserved-namespace.md`
|
|
reference was stale: ADR-014 is the outbox ADR; the reserved
|
|
namespace is ADR-008 §4, with the kinds list extended by ADR-014
|
|
outbox and ADR-009 schedule — corrected here)
|
|
- alkstore/src/validation.rs (the surface)
|
|
|
|
## Notes
|
|
|
|
- **No length cap is pinned — the "length-boundary exactness at the
|
|
cap" invariant was rendered as verdict-class length-independence.**
|
|
The description (and fuzzing.md §3.3) pinned a cap with a ±1
|
|
neighborhood ("a name at the cap is accepted, one past it rejected"),
|
|
but no cap exists anywhere in the normative stack: ADR-008 §4 pins
|
|
only non-empty (whitespace-only-rejected) + the reserved-prefix rule,
|
|
core-contract §Naming adds nothing about length, and
|
|
`alkstore/src/validation.rs` checks nothing about length. Per
|
|
AGENTS.md the ADR wins, so the shipped invariant pins what is true:
|
|
the verdict class is length-independent (no length is ever a
|
|
rejection cause) across probed neighborhoods (0↔1 empty boundary,
|
|
11↔12 prefix length, 63↔64 pg-identifier bytes, 4095↔4096 target
|
|
max_len, 65536 payload-cap scale). The only exact boundaries in the
|
|
pinned rule — empty↔one-char, whitespace-only↔content, and the
|
|
byte-exact prefix — are pinned exactly.
|
|
- **The "rejects in every casing/boundary form" invariant is pinned
|
|
with its exact-case direction.** The rule is byte-exact and
|
|
leading-only (`starts_with(RESERVED_PREFIX)`), so the battery pins
|
|
the byte-exact prefix rejecting (alone, suffixed,
|
|
whitespace-suffixed — anything not all-whitespace) **and** the
|
|
near-miss forms accepting on the shared kind (casing deviations,
|
|
missing trailing underscore, embedded and whitespace-led
|
|
occurrences) plus the local kind taking the non-empty rule only
|
|
(leading reserved prefix legal there, per ADR-008 §4's annotation
|
|
and the `NameKind::Local` doc).
|
|
- **Corpus-replay floor set at 32** (44 seeds committed), following
|
|
the sibling targets' pattern (64 / 48).
|
|
|
|
## Summary
|
|
|
|
Landed the §3.3 target: `fuzz_targets/name_validation.rs` (thin
|
|
`fuzz_target!` wrapper, like the sibling targets) +
|
|
`shared/src/name_validation.rs` (invariants + `#[cfg(test)]` corpus
|
|
replay), its `[[bin]]` entry, `pub mod` in shared's lib.rs, the
|
|
`name_validation_seeds()` generator (44 deterministic seeds committed:
|
|
cap-neighborhood lengths, empty/whitespace-only, the reserved prefix
|
|
plain/suffixed/at-length plus the listener-reconnected string, seven
|
|
near-miss forms, unicode/control/invalid-UTF-8 shapes), the
|
|
`!corpus/name_validation` gitignore negation, and the README
|
|
targets-table row. Invariants: total (no-panic over lossy and
|
|
`arbitrary`-structured strings plus derived boundary forms);
|
|
determinism (double invocation on both kinds); typed-only rejections
|
|
(narrowed to a `Verdict` enum that panics on anything but
|
|
`Accept`/`InvalidName`/`ReservedName`); wrapper equivalence
|
|
(`validate_shared_name`/`validate_local_name` vs `validate_name` on
|
|
each `NameKind`); classification asserted against a locally recomputed
|
|
rule of record (verbatim-name-carrying errors — names not trimmed);
|
|
the casing/boundary battery; and the length-independence battery
|
|
(see Notes).
|
|
|
|
Verified: `cargo test --manifest-path fuzz/shared/Cargo.toml` green
|
|
(6/6, 44-seed replay included); shared clippy `-D warnings` + fmt
|
|
`--check` green; `cargo fuzz build` green from `fuzz/` (nightly pin);
|
|
root `cargo build` / `cargo test` (all suites pass) / clippy
|
|
`-D warnings` / fmt `--check` green; seed regeneration deterministic;
|
|
corpus files trackable (negation verified via `git check-ignore`).
|
|
Campaign runs are `fuzz-campaigns-and-gate`'s concern (this task is
|
|
one of its three dependencies). |