* fix(server): the request-body gate reads tokens with jsontext, so a NUL behind a repeated key or beside 1e999 is refused, and a repeated request member is refused on every body (BUG-2812)
The body gate and the store's JSON-column guard decoded into
map[string]any and walked the tree. The tree is a lossy model: it keeps
one value per repeated key, so a NUL in a shadowed copy passed (and the
typed decode could merge it into storage), and a number no float64
holds made the decode fail, so the gate answered "no NUL" for a body the
typed decode then accepted. Walking that tree also cost about 2x on any
body that merely mentioned an escape.
- internal/jsonscan wraps github.com/go-json-experiment/json/jsontext
(pinned v0.0.0-20260820222146-c27c302e5fc3, BSD-3, The Go Authors).
It is the module's only importer; its doc comment names the exit to
stdlib encoding/json/jsontext. A differential test pins its acceptance
to encoding/json v1's, which found that jsontext refuses a lone
surrogate v1 accepts; that case falls back to v1's unquote.
- scanRequestBody reads every body once, recording a NUL and a repeated
request member separately and reading to the end, so malformed input
still reports nothing. Repeats count in the request's own structure,
and not in caller data under JSON-encoded keys (BUG-2896 collapses
those). Names are case-folded where the destination is a struct:
decodeJSONBytes reflects its destination's TOP-LEVEL type, so a struct
folds its members, a slice of structs each element's, and a map
(admin settings, admin user limits) none. Below the top level only an
exact repeat counts. decodeJSONBytes and both bundle paths refuse
either.
- textguard's document walk and artifactIsBindableText read tokens too,
so every layer refuses the shadowed NUL together: the one
textguard.KnownGaps case moves into Corpus.
- The store's suspect path stays, redundant but correct, by ruling; its
deletion is its own unit. Its instruments now pin what is true: no
refused value is a suspect, and the remaining suspects are the
doubled-backslash class the destination accepts.
- The map-model walk survives only as the differential's oracle.
* fix(server): update vendorHash and the Postgres-only suspect instruments for the token scan (BUG-2812)
The Postgres-backed legs of the suspect path fired when the shared
predicate learned to see a NUL behind a repeated key: that value is now a
violation everywhere, and the store's own guard refuses it before the
destination cast. The preflight legs now assert that, the unmigrated-
table fixture uses a doubled-backslash literal (still a suspect), and
PostgreSQL's native refusal stays measured by
TestNativePostgresAgreesWithTheCorpus.
vendorHash is the value this PR's Nix job recomputed.