Conversation
…nsports, and docs/threat-model.md cites a test per refusal -- measured first: a 1 MiB body nested 524 288 deep is decoded in full today (79-96 ms, a 38 MiB heap, ~36x the body) and refused afterwards by shape; the test file, a constant-only BeamMCP.JSON scaffold, the page-citation reader moved into the Boundary support module for both censuses: 6 tests, 5 failures (a nest past the bound answered 200 over HTTP and decoded over stdio, the worst body's heap, the page absent) Signed-off-by: Ayla Croft <aylacroft@proton.me>
…oth transports, a byte walk in front of the decoder refusing a body nested past 64 levels by name (-32600; 400 with the connection kept over HTTP, the same object on stdio) and building nothing: the worst 1 MiB body is refused in microseconds where it had cost a 38 MiB heap, a request-sized body pays nothing measurable, a 1 MiB well-formed one +1.7 ms; docs/threat-model.md -- who is trusted for what, the wire vector by vector as refused, bounded or delegated with the test per row and the OWASP ids read from the source (ASI December 2025, LLM 2025 by edition; the plan's LLM03 for prompt injection is LLM01:2025), the node-local adversary out for every module, the federation trust domains, the Plug.Parsers placement measured; the page's citations held to the tree by the reader the will-not-implement census now shares; ExDoc extra under Policy, README pointers, the observed page's pointer, a CHANGELOG entry with the how-to-tell; the package-reach census refused a Jason.DecodeError named in a spec -- struct() instead Signed-off-by: Ayla Croft <aylacroft@proton.me>
…-- the walk ignored, off by one, objects uncounted, no count-down, strings not skipped, escapes not honoured, the wrong depth named, the HTTP refusal as a parse error, the connection closed on it, stdio's clause removed -- zero survivors, every kill a named test; two re-anchored so an orphaned function is not the kill Signed-off-by: Ayla Croft <aylacroft@proton.me>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The package-wide threat model, and the one wire bound it found missing.
docs/threat-model.md. Who is trusted for what — the host for everything, the client on the wire for nothing, the node for everything by physics, this package for holding no tool, key, signature, session, authority or client. Then the wire, vector by vector, each refused, bounded, or delegated to the HTTP server or the host by decision, with the test that enforces it by path and by name and the OWASP id it answers to (the December 2025 Top 10 for Agentic Applications; the 2025 LLM Top 10 by edition — the ids were read from the project's pages, not recalled). It extends the tracer's model shipped in 0.4.0: the node-local adversary stays out of scope, now for every module, with the reason. Prompt injection through tool results is the host's (LLM01:2025) — this package carries bytes and never reads them. A federation section states the trust domains a merge would cross — attribution, identity, signs, integrity in transit, the peer as a client — so the seam is designed against them rather than discovering them. Where the Plug goes in a host's pipeline: ahead ofPlug.Parsers, or its path excluded — behind the parsers every request is-32700 Parse error: empty body(measured). A census holds every citation on the page to a test in the tree, by the reader the will-not-implement census now shares; it caught three misquoted names while the page was being written.JSON nesting is bounded before the decoder runs, on both transports. Measured first: the 1 MiB body cap bounds how deep a body can nest but not what decoding it costs — a 1 MiB body nested 524,288 levels deep was decoded in full, 79–96 ms and a 38 MiB heap for one request, about 36× the body, and refused only afterwards by its shape.
BeamMCP.JSON.decode/1is now the one place the wire's JSON is read: a byte walk in front of the decoder (brackets outside strings, escapes honoured) refuses a body nesting past 64 levels with-32600"Request body nests deeper than 64 levels" —400over HTTP with the connection kept, the same object on stdio — building nothing. The worst body under the cap is refused in microseconds; a request-sized body pays nothing measurable; a 1 MiB well-formed one +1.7 ms. Red first (six tests failing on behaviour with the constant present and nothing enforcing it), then green; ten mutants over the walk and the two transport clauses, zero survivors, every kill a named test. Only a client nesting past 64 levels sees a change; no field, method or capability moves — the conformance rows are unchanged (16/37, 5/6; 0/30 by design).Gate on the head: thirteen
pass,GATE_EXIT=0.11 properties, 611 tests, 0 failures— 604 atv0.5.0plus the seven of the threat-model test. The package-reach census refused aJason.DecodeErrornamed in a@specon the way (struct()instead) — the census working as built.