Repository navigation
review: sandbox on encoding/json/v2 - #243
Merged
Merged
Conversation
Every module now calls encoding/json/v2 and jsontext. The one v1
import supplies DefaultOptionsV1 for the two digests.
Kept as before:
- ClusterDigest and the pools.json seed hash marshal with the v1 option
set, so a rolling upgrade keeps the mesh digest and the warm pools;
golden tests pin the v1 bytes.
- Request bodies and MCP input match member names case-insensitively.
- Guest bytes that are not valid UTF-8 come out as U+FFFD in sandboxd
responses, the usage and audit journals, and MCP replies; MCP input
with invalid UTF-8 or a lone surrogate is decoded the same way
instead of being dropped without a reply.
- MCP replies sort map keys, so tools/list is byte-stable across calls
and sessions.
- A renew without a body takes the node default.
- A nil stdin or upload chunk encodes as "", now the v2 default, so
wire.B64 is gone.
- The MCP ping reply keeps its empty result object (omitzero).
Changed on purpose:
- Nil slices and maps encode as [] and {}. silkd's Vec fields accept
both; a git add or replace with an empty list is now a no-op instead
of bad_request, as an explicit empty list already was.
- Map keys are no longer sorted in sandboxd responses, journals and
gossip; the digests and MCP replies still sort them.
- A request body with invalid UTF-8 is 400, and so is one with data
after the value, except renew's optional body, which stops at the
first value.
- Case-insensitive matching also ignores - and _, as DecodeStrictJSON
already did.
- JSON responses no longer end with a newline.
- sdk/go returns an error for an argv or path that is not valid UTF-8
instead of sending U+FFFD.
Request bodies decode through UnmarshalRead and responses encode through
MarshalWrite: a claim body takes 670 ns and 6 allocs (v1 1020 ns, 11),
a response 342 ns and 2 allocs (v1 323 ns, 2).
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.
Summary
Every module (protocol/wire, sandboxd, sdk/go, e2e, mcp) now calls
encoding/json/v2andencoding/json/jsontext. The one remainingencoding/jsonimport suppliesDefaultOptionsV1()for the two digests that must keep their v1 bytes.Kept as before
ClusterDigestand the pools.json seed hash marshal with the v1 option set, so a rolling upgrade keeps the mesh digest and the warm pools. Golden tests pin the v1 bytes (TestClusterDigestMatchesTheV1Bytes,TestPoolSeedHashMatchesTheV1Bytes).TestClaimMatchesBodyKeysCaseInsensitively,TestRenewMatchesBodyKeysCaseInsensitively).TestExecKeepsInvalidUTF8OutputAsReplacementCharacters,TestJournalKeepsAnEventWithInvalidUTF8,TestToolRepliesReplaceInvalidUTF8).TestServeSpeaksMCP).tools/listis byte-stable across calls and sessions (TestRepliesEncodeMapsInSortedOrder)."". That is now the v2 default, sowire.B64is gone (TestRequestDataNeverNull).result,omitzero).Changed on purpose
[]and{}. silkd'sVecfields accept both. A git add or replace with an empty list is now a no-op instead ofbad_request, which is what an explicit empty list already did.-and_.DecodeStrictJSONalready did this on main.Hot path
Request bodies decode through
UnmarshalReadand responses encode throughMarshalWrite. This is an interleaved A/B with the order swapped, on a claim-shaped payload:decodeOptionalBody(renew only) keeps ajsontext.Decoder, because it is the only reader that tells an empty body (io.EOF) apart from a truncated one.Review
wire.B64, pooled body decode and response encode, MCP replies keeping invalid guest UTF-8, and digests onDefaultOptionsV1. The plain v2 swap had broken the MCP replies: a read of a binary file madeserveexit.jsontext.Decoder.SkipValuefor the tag scan. It would raise the nesting bound from 16 to 10000, and a hostile frame would then cost about 470 KB of heap per scan (measured).tools/liststays byte-stable for a host's prompt cache (v2 gave 8 encodings of one schema in 50 runs). MCP input with a lone surrogate or invalid UTF-8 is now decoded with U+FFFD; before, the line was dropped without a reply.DigestJSONalso gains a one-line godoc.docs/security.mdplaces callers inside the trust boundary.Gates
Run on 2899388:
make go-lint: rc=0, 10 of 100 issues.lines (5 modules, linux and darwin).asl -forwarder=false ./...: 0 findings in every module, on both GOOS.make go-test(race): 20 ok, 0 FAIL.golang:1.27.1container, uid 1000: protocol/wire, sdk/go, sandboxd, mcp and e2e all ok.Hardware (host A, linux/amd64, fork-dev CH)
Frozen set, sha-checked before and after every lane:
sandbox/rt:24.04= sha256:a29e0acdscripts/sandboxd-e2e.sh: golden, warm pool, claim tiers, the SDK smoke (files, session, find/replace, watch, git, pty), reap, restartscripts/archive-e2e.sh: idle hibernate, store archive, wake with the same id and guest stateSBU in detail:
SBM in detail:
tools/listis byte-identical across calls: 9149 bytes on both arms.SBX, SBS and SBU ran the sandboxd build of 03e9da0. The final head 2899388 changes only
utils.go's godoc and import spelling for sandboxd, and a-buildvcs=false -buildid=build of both commits has identical.text,.rodataand.data; only the.gopclntabline table differs. SBM ran the 2899388 build.Size