# parse-net v0.7.0 - Hardening
**Date:** 2026-10-24
**Compare:** [`v0.6.1...v0.7.0`](https://github.com/jamesgober/parse-net/compare/v0.6.1...v0.7.0)
**Every threat tested, every walker linear, every fuzz target 24 hours clean.** v0.7.0 adds an attack test for every row of the threat model, feeds every walker adversarial input up to 1 MiB, and ran all 34 fuzz targets for 24 hours. That work found one quadratic path, one decoding gap, and one line-length bug in `mail::fold`, all fixed here, and the entity-tag list bug fixed in 0.6.1. The two benchmark rows that missed the 20% target at 0.6 now meet it, so every compared row does.
## Highlights
- **A bounded repeated-name check.** Refusing a repeated parameter name read every earlier parameter again, so a field of many parameters cost the square of its length: 8.8 seconds for 1 MiB with `max_params` raised, and about 100 times a normal parse at the default limits. RFC 9110 parameters (under `DuplicatePolicy::Refuse`), authentication parameters, and MIME parameters now compare names from a table of up to 64 offsets on the stack (D-072).
- **Decoded controls refused in encoded words.** `decode_words` refused only a decoded CR, LF, or NUL, so `=1B` put an ESC, the start of a terminal escape sequence, into a decoded `Subject`. It now refuses every control character, as ext-values and RFC 2231 parameters already did (D-071).
- **Every compared row at least 20% faster** than the best Rust competitor, 21% to 81%:
| Operation | parse-net | Fastest competitor | Faster |
|---|---|---|---|
| `parse_media_type` and `charset` | 26.1 ns (32.0 at 0.6) | 37.4 ns (`mime`) | 30% |
| `response_cache_control` | 41.2 ns (47.8 at 0.6) | 59.8 ns (`headers`) | 31% |
| `parse_content_disposition` with `filename*` | 133 ns (158 at 0.6) | 175 ns (`mailparse`) | 24% |
## Fixed
- `mail::fold` could write a 999-byte line: whitespace ending the value was appended after a last word that filled the line to 998 bytes. It is refused with `OutOfRange`, like a word that does not fit, since folding before it would leave a line of only whitespace. Found by the 24-hour fuzz run; the crash input and its neighbours are unit tests.
## Added
- `param::MAX_CHECKED_PARAMS` (64): the most parameters one element may have where repeated names are refused.
- `tests/threats.rs`: an attack test for each threat-table row that had none, and a map of where the rest live.
- `tests/complexity.rs`: 45 adversarial shapes across every walker at 1 KiB, 64 KiB, and 1 MiB. Under the default limits each returns the expected limit error; with every limit raised, time grows with the input, within 1.2 of the size ratio in a release build (a new CI job checks it) and within 2.0 in a debug build.
- `docs/SECURITY.md`: how to report a vulnerability, the trust boundaries, the threat table with each test and its cost, resource bounds, fuzzing, and the differential verdicts.
- `docs/PLATFORM-NOTES.md`: the feature matrix, `no_std` and bare-metal builds, and stack use.
- A weekly CI workflow runs the three differential fuzz targets for 30 minutes each.
## Changed
- The repeated-name check, as above. Those three parameter lists hold at most 64 parameters per element whatever `max_params` says; `First` and `Last` keep the full `max_params`, and its default stays 256 because RFC 9651 requires 256 Structured Field parameters.
- `decode_words` refuses decoded C0 controls (HTAB included), DEL, and C1, as above.
- `param::parse` walks a shrinking slice; `Params::get` compares names in place and reads values without validating them again; `parse_u32_saturating` reads up to nine digits without saturating steps.
- The manifest's `homepage` key, which repeated `repository`, is gone.
## Testing
- Every unit, doctest, property, conformance, vector, and counting-allocator test passes, on Windows and WSL Ubuntu, Rust stable and 1.85.
- Property tests compare the new parameter walker with the 0.6 walker, kept as a test-only reference, under every duplicate policy; `Params::get` and the parameter iterator with the checked word parser; and `parse_u32_saturating` with exact arithmetic. One caught a bug in a draft of the new `get` before it shipped.
- All 34 fuzz targets ran for 24 hours with no finding in the library: 32 at commit `a77a82d`, `authorization` at `16841d5`, and `sfv_dictionary` at `ad714c8`. A first run found the `fold` bug above within minutes; its other 33 targets ran clean for 5 hours before the machine's disk filled and stopped it, and the run was started again on the fixed code. In that run two targets stopped on their own checks, not the library: `authorization` asserted that `Debug` did not contain the token, which the token `Credenti` defeats by occurring in `Credentials` (it now checks the exact `Debug` output), and `sfv_dictionary` walked the whole dictionary for every key, so a 1019-member input timed out (the parser takes 37 microseconds on it; the reference scan now covers the first 32 keys). Each reran its 24 hours on the fixed check. Every target now checks that each error prints exactly its kind's message and position, with no input bytes.
## Compatibility
- Inputs with more than 64 parameters in one element are now refused with `TooManyMembers` where repeated names are refused (they were read before, slowly); no real field comes close.
- `decode_words` now refuses decoded HTAB, DEL, and C1 controls it used to pass through.
- No other API change. MSRV 1.85, edition 2024, `no_std` without `alloc`, zero runtime dependencies.
## What's next
`0.8.0`: alpha. http-net and ws-net releases built on parse-net, then cookie-net and form-net.
**Full Changelog:** https://github.com/jamesgober/parse-net/compare/v0.6.1...v0.7.0