Skip to content

fix(verifiers): enforce canonical base64url, reject private JWKs and duplicate JSON keys in independent_verify and guard request_sha256 in companion_v0 - #364

Open
drk-ram wants to merge 1 commit into
agentrust-io:mainfrom
drk-ram:drk-ram-fix-issue-363-verifier-hardening
Open

drk-ram wants to merge 1 commit into
agentrust-io:mainfrom
drk-ram:drk-ram-fix-issue-363-verifier-hardening

Conversation

@drk-ram

@drk-ram drk-ram commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

Closes #363

Hardens the independent AUCP/TRACE verifier (examples/sovereign-agent-connector/tools/independent_verify.py) and the cA2A companion verifier (integrations/horizonshield-ca2a-companion/vendor/ca2a-companion-v0/companion_v0.py) so malformed inputs fail closed:

  1. examples/sovereign-agent-connector/tools/independent_verify.py:
    • decode_b64url: enforces canonical unpadded base64url (^[A-Za-z0-9_-]+\Z, len(value) % 4 != 1, and round-trip re-encoding equality), raising VerificationError on = padding, standard base64 +// characters, whitespace, or non-zero trailing bits.
    • verify_trace_signature / verify_ed25519: validates trace.cnf.jwk (kty == "OKP", crv == "Ed25519"), rejects RFC 7517 / RFC 7518 / RFC 8037 private or symmetric JWK parameters (d, p, q, dp, dq, qi, oth, k), verifies cnf.jwk.x matches the pinned trust anchor, and requires len(key_bytes) == 32 and len(sig_bytes) == 64.
    • load_json: rejects duplicate JSON object member names via object_pairs_hook, raising VerificationError.
  2. integrations/horizonshield-ca2a-companion/vendor/ca2a-companion-v0/companion_v0.py:
    • Guards request_sha256 with _is(HEX32, req_sha) before dict lookups in settle (requests.get(...), rank, and executed), failing closed with bad_request_sha256 (disposition: "outside") rather than raising TypeError: unhashable type on list or dict values.

Checked that companion_v0.py preserves LF line endings and updated the provenance note and pinned SHA-256 in tests/test_ca2a_companion_vendored.py and README.md so the vendored-file integrity and vector-rebuild tests pass alongside the new regression tests.

Type of change

  • Editorial (typo, link fix, clarification: no normative effect)
  • Non-breaking spec change (new optional field, new platform profile, informative addition)
  • Breaking spec change (requires 14-day comment period and Project Lead sign-off)
  • Schema change
  • Example addition

Spec section

examples/sovereign-agent-connector/tools/independent_verify.py and integrations/horizonshield-ca2a-companion/vendor/ca2a-companion-v0/companion_v0.py.

Tests for a fix

  • examples/sovereign-agent-connector/tests/test_independent_verifier.py:
    • test_decode_b64url_rejects_padding_non_alphabet_and_non_canonical_trailing_bits (fails when decode_b64url hardening is switched off)
    • test_verify_trace_signature_rejects_private_and_symmetric_jwk_parameters (fails when verify_trace_signature JWK validation is switched off)
    • test_verify_trace_signature_enforces_ed25519_key_and_signature_lengths (fails when key/signature byte-length checks are switched off)
    • test_load_json_and_cli_reject_duplicate_json_keys (fails when load_json object_pairs_hook is switched off)
  • integrations/horizonshield-ca2a-companion/tests/test_ca2a_companion_gaps.py:
    • test_settle_guards_unhashable_request_sha256_without_type_error (fails with TypeError: unhashable type when the request_sha256 guard in companion_v0.settle is switched off)
    • test_load_strict_rejects_duplicate_json_keys

Validation commands and results

  • pytest examples/sovereign-agent-connector/tests -q -> passed (74 passed)
  • pytest integrations/horizonshield-ca2a-companion/tests -q -> passed (86 passed)
  • pytest tests -q -> passed (60 passed)
  • ruff check integrations packages scripts tests claude-code copilot plugins scheduled-agents shadow-ai examples/sovereign-agent-connector --target-version py39 --select E4,E7,E9,F -> passed (All checks passed!)
  • python examples/sovereign-agent-connector/tools/independent_verify.py examples/sovereign-agent-connector/vectors/french-healthcare/cmcp-live-bundle.json --trust-anchors examples/sovereign-agent-connector/vectors/french-healthcare/cmcp-live-trust-anchors.json -> passed (PASS: AUCP MVE-1 bundle independently verified)

Checklist

  • DCO sign-off on all commits (git commit -s)
  • CHANGELOG.md updated (for any normative change)
  • Breaking changes marked with <!-- CHANGED: #NNN: description --> in spec text
  • Backward compatibility statement included (for breaking changes)

@drk-ram
drk-ram requested review from a team, carloshvp and imran-siddique as code owners October 23, 2026 01:42
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

🟡 Contributor Check: MEDIUM

Check Result
Profile MEDIUM
Credential LOW
Overall MEDIUM

Automated check by AgenTrust Contributor Check.

@github-actions github-actions Bot added the needs-review:MEDIUM Contributor check flagged MEDIUM risk label Oct 23, 2026

@dinakarjs dinakarjs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head 346beed (full commit attached). Checked independent TRACE signature verification against the explicit out-of-band anchor, canonical key/signature decoding and duplicate-member loading, plus the companion's malformed request digest handling without accidental rank/dedup lookups. Focused local tests: independent verifier 33 passed; companion 86 passed. Hosted sovereign-agent-connector and companion conformance jobs succeed. The overall Examples failure is in unrelated weight-custody-manifest: missing OpenSSL during collection (job 120225017589). Link check fails on two existing Nobulex links returning HTTP 451 (job 120225049655). Those are unresolved integration-wide checks, not passing evidence. No blocking source finding in these changed paths.

carloshvp
carloshvp previously approved these changes Oct 23, 2026

@carloshvp carloshvp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head 346beed26423c421b7bbfa75c0e0e0ad60ce4e79 in an isolated checkout.

Approving the scoped verifier hardening. The independent TRACE verifier enforces canonical unpadded base64url, rejects private/symmetric JWK members and duplicate JSON keys, and binds the embedded public key to the explicit out-of-band anchor. The companion rejects malformed request_sha256 values without using them as dictionary keys or reporting a successful receipt.

Independent macOS/Python 3.12 validation: all 74 sovereign-connector tests and 86 companion tests pass; the independent positive bundle CLI, CI-selected Ruff and compilation checks pass. Additional controls pass 80 canonical byte round trips and 522 negative cases. The private-JWK cases were freshly signed by the pinned test key, so rejection is not merely caused by an invalid signature; duplicate-key controls include nested and escaped-equivalent names.

These checks preserve the documented software-only trust boundary. The failed hosted links job identifies the unchanged Nobulex URL returning 451, and the WCM job reports the inherited missing OpenSSL import. #357 and #362 carry those separate repairs. The normal current-head CI and approval gates remain applicable. The commit carries DCO sign-off.

Reviewed with OpenAI Codex.

@rajnisht7 rajnisht7 left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the head 346beed, the independent_verify hardening looks good canonical base64url, private/symmetric JWK rejection, length checks, and duplicate-key loading are correct and well tested.
Two issues on the companion side:

  • Provenance is inconsistent - README still says the reference verifier is “unchanged” and points at horizon-shield 05b4924. The actual file at that commit has SHA a6e2b2c9… and the original unguarded requests.get(it["request_sha256"]). This PR changes the file (new guards + new SHA fc53665c…). The vendored test only checks the local hard-coded SHA, so it doesn’t catch the mismatch. Either land the guard upstream and bump SOURCE_COMMIT + SHA + README, or drop the claim for the file is an unchanged byte-for-byte copy.
  • README still says Expected: 83 passed. Suite is now 86.

non-blocking: the new request_sha256 regression test only hits the guard via monkeypatch. parse_item already rejects non-HEX32 values, so a normal external malformed input never reaches the settle path. The guard is still good defense-in-depth, but the test doesn’t demonstrate the original external TypeError claim.

…duplicate JSON keys in independent_verify and guard request_sha256 in companion_v0

- Enforce canonical unpadded base64url in decode_b64url (rejecting padding,
  non-alphabet characters, len % 4 == 1, and non-zero trailing bits).
- Validate TRACE cnf.jwk in verify_trace_signature, rejecting RFC 7517 /
  RFC 7518 / RFC 8037 private or symmetric JWK parameters
  (d, p, q, dp, dq, qi, oth, k) and requiring 32-byte Ed25519 keys and
  64-byte signatures.
- Reject duplicate JSON object member names via object_pairs_hook in
  load_json.
- Guard request_sha256 in companion_v0.settle so non-string or unhashable
  values fail closed with bad_request_sha256 instead of raising TypeError,
  and document the local defense-in-depth provenance against upstream
  horizon-shield 05b4924 (a6e2b2c9...) and updated 86-test suite count.

Closes agentrust-io#363

Signed-off-by: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com>
@drk-ram
drk-ram force-pushed the drk-ram-fix-issue-363-verifier-hardening branch from 346beed to 4327773 Compare October 29, 2026 15:59
@drk-ram

drk-ram commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

@rajnisht7 @carloshvp Rebased onto origin/main and updated at 4327773:

  1. Companion provenance (integrations/horizonshield-ca2a-companion/README.md, tests/test_ca2a_companion_vendored.py): Removed the claim that vendor/ca2a-companion-v0/companion_v0.py is an unchanged byte-for-byte copy of horizon-shield 05b4924 (a6e2b2c9d920eda65723e1d863e8e108df642c6b3ece8b40499726bfc425a781), and explicitly documented that gen_vectors.py and vectors/vectors.json are byte-for-byte from 05b4924 while companion_v0.py (fc53665ce6dfd7f763299ff0c2ab3e8f91cc8698ce7e7a327021f93dc77c9627) is derived from 05b4924 (UPSTREAM_COMPANION_SHA256 = "a6e2b2c9...") with the local defense-in-depth request_sha256 type/hex validation guard in settle.
  2. Expected suite count (integrations/horizonshield-ca2a-companion/README.md): Updated Expected: 83 passed to Expected: 86 passed.
  3. Validation: Re-ran pytest integrations/horizonshield-ca2a-companion/tests -q (86 passed) and pytest examples/sovereign-agent-connector/tests/test_independent_verifier.py examples/sovereign-agent-connector/tests/test_appraisal.py -q (48 passed).

@carloshvp carloshvp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head 43277730a9878e1a41e9497346de6aa60dfb27e7 in an isolated checkout.

The independent TRACE verifier checks canonical encodings, exact Ed25519 sizes, public-only JWKs and the explicit trust-anchor binding. The companion validates request digests before dictionary lookup and no longer claims its locally modified verifier is byte-identical to upstream; unchanged generator/vector provenance is retained. All 74 sovereign connector tests, 86 companion tests and 60 root tests pass.

The repository CI Ruff selection and whitespace checks pass; all PR commits carry DCO sign-offs. This approves the bounded software scope. Current-head hosted checks and maintainer requirements remain applicable.

Reviewed with OpenAI Codex.

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

needs-review:MEDIUM Contributor check flagged MEDIUM risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(verifiers): enforce canonical base64url, reject private JWKs and duplicate JSON keys in independent_verify and guard request_sha256 in companion_v0

4 participants