Skip to content

docs: pin the JCS nesting bound with a jcs_depth_v1 vector corpus - #2246

Open
astrogilda wants to merge 2 commits into
a2aproject:mainfrom
astrogilda:jcs-depth-v1-conformance-vectors
Open

astrogilda wants to merge 2 commits into
a2aproject:mainfrom
astrogilda:jcs-depth-v1-conformance-vectors

Conversation

@astrogilda

@astrogilda astrogilda commented Sep 18, 2026 •

Copy link
Copy Markdown

jcs_depth_v1 goes in beside the jcs_edge_v1 appendix from #2219. It holds 8 vectors: 4 accept at a bound of 128 open containers, 4 reject one level past it, 4 pair invariants.

jcs_edge_v1 pins the output a canonicalizer must produce, and this pins the input it must refuse, which RFC 8785 leaves open. So 2 implementations agreeing on its 10 vectors can still disagree over whether a deep artifact canonicalizes at all. Canonicalization runs before signature verification, and I read that as putting the recursive walk in reach of anyone holding an artifact.

That bound is the rule in-toto/attestation#570 proposes, in a pull request that has not merged. The 2 boundary cases come from agent-evidence-vectors, which runs with uvx agent-evidence-vectors, ahead of the 22 September TSC slot.

Closes #2255.

RFC 8785 specifies byte production and no limit on input nesting, so two
canonicalizers that agree on every byte of the jcs_edge_v1 corpus can still
disagree on whether a deeply nested artifact canonicalizes at all. One
returns bytes, one returns an error, and one exhausts its call stack. The
profile has to state the bound itself.

This corpus pins the boundary at 128 open containers, counted so that an
empty container is charged its own level. Four accept vectors sit at the
bound in objects, arrays, alternating containers and with an empty-container
leaf; three reject vectors sit one level past each of those; and one holds
ten million levels, which separates an implementation that refuses from one
that dies. Four pair invariants state the relations no single vector can.

Expected canonical bytes come from canonicalizing each materialized preimage
with rfc8785 0.1.4, the reference implementation jcs_edge_v1 also names, and
were reproduced with a second serializer before being pinned. Preimages are
given as nesting rules rather than literal JSON so the corpus file stays
shallow enough to be read by a parser enforcing the bound it describes.
@eriknewton

Copy link
Copy Markdown

Thanks, Sankalp. The empty-container boundary pair is a useful addition to the discussion. For the TSC, I would keep the distinction explicit: #2219 checks canonical output bytes; #2246 proposes an input-admission rule with a 128-container limit. Could the README label that limit as a proposed profile choice, rather than a requirement of RFC 8785 or an already-adopted A2A rule? The provenance file already makes the status of the in-toto proposal clear. It would also help to link the second serializer and its pinned run results so others can reproduce the independent cross-check. I haven’t run these vectors against Concordia yet.

The README now says 128 is a proposed profile choice, required by neither
RFC 8785 nor an adopted A2A rule, and states the grounds the in-toto
proposal gives for it. The unsourced second-serializer claim is withdrawn
from both the README and PROVENANCE.json, and replaced by the third-party
run against the a2a-go canonicalizer reported on a2aproject#2219, including its
finding that the jcs-depth-104 refusal there came from the encoding/json
decoder rather than from the canonicalizer. The gowebpki/jcs measurement
now names its version, and notes that v1.0.2 adds a limit of 10,000.
PROVENANCE.json pins the new README digest and names the source corpus by
its current repository path.
@astrogilda

Copy link
Copy Markdown
Author

Erik, thank you for the label catch: the README now calls 128 a proposed profile choice, required by neither RFC 8785 nor A2A. The second-serializer line is withdrawn, because it named nothing a reader could repeat.

@kuangmi-bit, thank you for the run that replaced it, and for finding that jcs-depth-104's refusal came from the decoder's cap. The corpus does not depend on the number, so whichever bound the TSC settles, the counting rule from agent-evidence-vectors carries over unchanged.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Content Integrity Profile: conformance vectors for canonical output and the input nesting bound

2 participants