ca2a-runtime 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. ca2a_runtime-0.4.0/.clusterfuzzlite/Dockerfile +5 -0
  2. ca2a_runtime-0.4.0/.clusterfuzzlite/README.md +60 -0
  3. ca2a_runtime-0.4.0/.clusterfuzzlite/build.sh +29 -0
  4. ca2a_runtime-0.4.0/.clusterfuzzlite/fuzz_attestation_parsers.py +62 -0
  5. ca2a_runtime-0.4.0/.clusterfuzzlite/fuzz_canonical_json.py +89 -0
  6. ca2a_runtime-0.4.0/.clusterfuzzlite/fuzz_wire_documents.py +92 -0
  7. ca2a_runtime-0.4.0/.github/dependabot.yml +39 -0
  8. ca2a_runtime-0.4.0/.github/workflows/cflite_batch.yml +36 -0
  9. ca2a_runtime-0.4.0/.github/workflows/cflite_pr.yml +43 -0
  10. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/codeql.yml +3 -3
  11. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/docker.yml +2 -0
  12. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/require-maintainer-approval.yml +5 -2
  13. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/scorecard.yml +1 -1
  14. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/CHANGELOG.md +46 -1
  15. ca2a_runtime-0.4.0/Dockerfile +40 -0
  16. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/PKG-INFO +3 -3
  17. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/attestation.md +8 -1
  18. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/mutual-attestation.md +40 -1
  19. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/transport.md +7 -2
  20. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/index.md +2 -2
  21. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/pyproject.toml +3 -3
  22. ca2a_runtime-0.4.0/requirements/build.in +6 -0
  23. ca2a_runtime-0.4.0/requirements/build.txt +26 -0
  24. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/dev.in +2 -2
  25. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/dev.txt +56 -56
  26. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/docs.txt +37 -18
  27. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/release.txt +3 -3
  28. ca2a_runtime-0.4.0/requirements/runtime.in +12 -0
  29. ca2a_runtime-0.4.0/requirements/runtime.txt +620 -0
  30. ca2a_runtime-0.4.0/src/ca2a_runtime/attestation.py +383 -0
  31. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/canonical.py +9 -2
  32. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/challenge.py +10 -0
  33. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/delegation/credential.py +7 -0
  34. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/hardware_acceptance.py +7 -2
  35. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/node.py +9 -2
  36. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/peer.py +65 -2
  37. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/base.py +15 -2
  38. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/binding.py +46 -0
  39. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/sev_snp.py +14 -5
  40. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/software.py +5 -1
  41. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/tdx.py +36 -11
  42. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/tpm.py +10 -7
  43. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/a2a_adapter.py +17 -2
  44. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/client.py +34 -7
  45. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/constants.py +6 -0
  46. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/server.py +6 -1
  47. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/wire.py +13 -1
  48. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/dag.py +4 -0
  49. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/sev_snp.py +2 -1
  50. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/tpm.py +5 -1
  51. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/conftest.py +58 -0
  52. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_a2a_sdk_bridge.py +6 -11
  53. ca2a_runtime-0.4.0/tests/unit/test_caller_offer_relay.py +449 -0
  54. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_canonical.py +13 -0
  55. {ca2a_runtime-0.3.0/tests → ca2a_runtime-0.4.0/tests/unit}/test_challenge.py +18 -0
  56. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_container_release.py +25 -3
  57. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_example_rejection_with_proof.py +17 -8
  58. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_holder_binding.py +16 -11
  59. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_live_call.py +32 -1
  60. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_mutual_attestation.py +19 -15
  61. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_release_artifacts.py +1 -1
  62. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_tdx.py +62 -0
  63. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_trace_binding.py +11 -0
  64. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_verify.py +13 -0
  65. ca2a_runtime-0.3.0/.github/dependabot.yml +0 -23
  66. ca2a_runtime-0.3.0/Dockerfile +0 -30
  67. ca2a_runtime-0.3.0/src/ca2a_runtime/attestation.py +0 -163
  68. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.dockerignore +0 -0
  69. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/CODEOWNERS +0 -0
  70. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  71. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  72. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  73. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  74. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/actionlint.yml +0 -0
  75. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/ci.yml +0 -0
  76. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/contributor-check.yml +0 -0
  77. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/docs.yml +0 -0
  78. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/limitations-parity.yml +0 -0
  79. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/release-drafter.yml +0 -0
  80. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/release.yml +0 -0
  81. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/sbom.yml +0 -0
  82. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/vouch-check.yml +0 -0
  83. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.github/workflows/vouch-command.yml +0 -0
  84. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/.gitignore +0 -0
  85. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/ADOPTERS.md +0 -0
  86. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/CHARTER.md +0 -0
  87. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/CNAME +0 -0
  88. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  89. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/CONTRIBUTING.md +0 -0
  90. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/GOVERNANCE.md +0 -0
  91. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/LICENSE +0 -0
  92. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/LIMITATIONS.md +0 -0
  93. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/MAINTAINERS.md +0 -0
  94. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/NOTICE +0 -0
  95. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/PRIVACY.md +0 -0
  96. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/README.md +0 -0
  97. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/ROADMAP.md +0 -0
  98. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/SECURITY.md +0 -0
  99. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/SPONSORS.md +0 -0
  100. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/benchmarks/.gitkeep +0 -0
  101. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/SPEC.md +0 -0
  102. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/assets/icon.svg +0 -0
  103. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/assets/og.png +0 -0
  104. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/concepts.md +0 -0
  105. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/configuration.md +0 -0
  106. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/hardware-validation.md +0 -0
  107. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/mutual-hardware-acceptance.md +0 -0
  108. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/quickstart.md +0 -0
  109. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/call-graph.md +0 -0
  110. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/cedar-policy.md +0 -0
  111. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/component-model.md +0 -0
  112. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/conformance.md +0 -0
  113. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/delegation-chain.md +0 -0
  114. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/error-codes.md +0 -0
  115. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/failure-modes.md +0 -0
  116. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/profile.md +0 -0
  117. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/provenance-dag.md +0 -0
  118. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/response-authentication.md +0 -0
  119. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/response-binding-requirements.md +0 -0
  120. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/sealed-channel.md +0 -0
  121. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/threat-model.md +0 -0
  122. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/trace-a2a-profile.md +0 -0
  123. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/spec/verification-library.md +0 -0
  124. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/tutorials/authoring-a-delegation-credential.md +0 -0
  125. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/tutorials/emit-and-verify-provenance.md +0 -0
  126. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/tutorials/integrating-with-a2a.md +0 -0
  127. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/tutorials/reproducing-the-claims.md +0 -0
  128. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/docs/tutorials/verify-a-delegation-chain.md +0 -0
  129. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/a2a-sdk/loopback.py +0 -0
  130. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/a2a-sdk/requirements.txt +0 -0
  131. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/README.md +0 -0
  132. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/ca2a-config.yaml +0 -0
  133. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/chain.json +0 -0
  134. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/dag.json +0 -0
  135. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/demo.py +0 -0
  136. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/cross-operator-delegation/policy.cedar +0 -0
  137. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/minimal/README.md +0 -0
  138. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/minimal/ca2a-config.yaml +0 -0
  139. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/minimal/chain.json +0 -0
  140. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/rejection-with-proof/README.md +0 -0
  141. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/rejection-with-proof/chain.json +0 -0
  142. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/rejection-with-proof/dag.json +0 -0
  143. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/rejection-with-proof/demo.py +0 -0
  144. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/trace-dag/README.md +0 -0
  145. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/examples/trace-dag/demo.py +0 -0
  146. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/README.md +0 -0
  147. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim1-attenuation-soundness/README.md +0 -0
  148. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim1-attenuation-soundness/run.py +0 -0
  149. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim2-cross-chain-replay/README.md +0 -0
  150. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim2-cross-chain-replay/run.py +0 -0
  151. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim3-scope-policy-intersection/README.md +0 -0
  152. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim3-scope-policy-intersection/run.py +0 -0
  153. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim4-sealed-payload-confidentiality/README.md +0 -0
  154. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim4-sealed-payload-confidentiality/run.py +0 -0
  155. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim5-provenance-dag-integrity/README.md +0 -0
  156. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim5-provenance-dag-integrity/run.py +0 -0
  157. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim6-cross-operator-attestation/README.md +0 -0
  158. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/claim6-cross-operator-attestation/run.py +0 -0
  159. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/README.md +0 -0
  160. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/ca2a-attested-peer-azure-2026-07-27/bridge.py +0 -0
  161. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/ca2a-attested-peer-azure-2026-07-27/transcript.json +0 -0
  162. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/ca2a-cross-tee-2026-07-27/snp_client.py +0 -0
  163. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/ca2a-cross-tee-2026-07-27/tdx_peer.py +0 -0
  164. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/ca2a-cross-tee-2026-07-27/transcript.json +0 -0
  165. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/mutual-snp-2026-09-17/README.md +0 -0
  166. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/mutual-snp-2026-09-17/a-to-b.json +0 -0
  167. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/mutual-snp-2026-09-17/b-to-a.json +0 -0
  168. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/experiments/hardware-validation/mutual-snp-2026-09-17/controls.py +0 -0
  169. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/governance/ca2a-enforcement.yaml +0 -0
  170. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/hooks/seo.py +0 -0
  171. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/mkdocs.yml +0 -0
  172. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/overrides/main.html +0 -0
  173. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/agt-overrides.txt +0 -0
  174. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/agt.in +0 -0
  175. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/agt.txt +0 -0
  176. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements/release.in +0 -0
  177. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/requirements-docs.txt +0 -0
  178. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/robots.txt +0 -0
  179. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/scripts/gen_action_fixtures.py +0 -0
  180. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/scripts/gen_agt_evidence.py +0 -0
  181. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/scripts/gen_example_chain.py +0 -0
  182. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/__init__.py +0 -0
  183. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/agent_manifest.py +0 -0
  184. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/benchmarks.py +0 -0
  185. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/bootstrap.py +0 -0
  186. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/cedar.py +0 -0
  187. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/channel/__init__.py +0 -0
  188. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/channel/sealed.py +0 -0
  189. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/cli.py +0 -0
  190. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/config.py +0 -0
  191. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/delegation/__init__.py +0 -0
  192. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/delegation/holder.py +0 -0
  193. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/delegation/revocation.py +0 -0
  194. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/errors.py +0 -0
  195. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/policy.py +0 -0
  196. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/provenance.py +0 -0
  197. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/response.py +0 -0
  198. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/__init__.py +0 -0
  199. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/tee/tsm.py +0 -0
  200. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/trace_binding.py +0 -0
  201. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/__init__.py +0 -0
  202. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/_b64url.py +0 -0
  203. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_runtime/transport/a2a_sdk.py +0 -0
  204. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/__init__.py +0 -0
  205. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/tdx.py +0 -0
  206. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/tpm_roots.py +0 -0
  207. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/src/ca2a_verify/verify.py +0 -0
  208. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/__init__.py +0 -0
  209. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/committed_blobs.py +0 -0
  210. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conformance/README.md +0 -0
  211. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conformance/__init__.py +0 -0
  212. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conformance/conftest.py +0 -0
  213. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conformance/test_action_fixture_bundles.py +0 -0
  214. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conformance/test_profile_conformance.py +0 -0
  215. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/conftest.py +0 -0
  216. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/a2a/send_message_ca2a.json +0 -0
  217. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/README.md +0 -0
  218. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-001-verified/chain.json +0 -0
  219. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-001-verified/dag.json +0 -0
  220. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-001-verified/expected.json +0 -0
  221. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-002-parent-hash-mismatch/chain.json +0 -0
  222. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-002-parent-hash-mismatch/dag.json +0 -0
  223. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-002-parent-hash-mismatch/expected.json +0 -0
  224. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-006-policy-denial/chain.json +0 -0
  225. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-006-policy-denial/dag.json +0 -0
  226. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-006-policy-denial/expected.json +0 -0
  227. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-010-scope-escalation/chain.json +0 -0
  228. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-010-scope-escalation/dag.json +0 -0
  229. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-010-scope-escalation/expected.json +0 -0
  230. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-012-credential-expired/chain.json +0 -0
  231. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-012-credential-expired/dag.json +0 -0
  232. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/action/action-012-credential-expired/expected.json +0 -0
  233. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/README.md +0 -0
  234. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/ak.pub +0 -0
  235. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/akcert_0x01C101D0.der +0 -0
  236. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/nonce.hex +0 -0
  237. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/pcrs.bin +0 -0
  238. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/quote.msg +0 -0
  239. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/hardware/azure-vtpm-2026-07-27/quote.sig +0 -0
  240. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/response-mac-v1.json +0 -0
  241. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/sev_snp/amd_milan_cert_chain.pem +0 -0
  242. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tdx/intel_sgx_root_ca.pem +0 -0
  243. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tpm/bare-rsa-prefix-0016/README.md +0 -0
  244. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tpm/bare-rsa-prefix-0016/ak-chain.pem +0 -0
  245. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tpm/bare-rsa-prefix-0016/attest.hex +0 -0
  246. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tpm/bare-rsa-prefix-0016/signature.hex +0 -0
  247. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/fixtures/tpm/bare-rsa-prefix-0016/trusted-root.pem +0 -0
  248. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/__init__.py +0 -0
  249. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_a2a_adapter.py +0 -0
  250. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_a2a_sdk_loopback.py +0 -0
  251. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_agent_manifest_cose.py +0 -0
  252. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_bootstrap.py +0 -0
  253. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_cedar.py +0 -0
  254. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_channel_and_tee.py +0 -0
  255. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim1_attenuation.py +0 -0
  256. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim2_replay.py +0 -0
  257. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim3_scope_policy.py +0 -0
  258. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim4_sealed.py +0 -0
  259. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim5_provenance.py +0 -0
  260. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_claim6_cross_operator.py +0 -0
  261. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_cli_start.py +0 -0
  262. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_committed_examples_verify.py +0 -0
  263. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_config.py +0 -0
  264. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_delegation.py +0 -0
  265. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_dependency_security_floors.py +0 -0
  266. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_docs_quickstart.py +0 -0
  267. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_documented_experiments.py +0 -0
  268. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_errors.py +0 -0
  269. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_hardware_acceptance.py +0 -0
  270. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_lineage_cli.py +0 -0
  271. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_peer.py +0 -0
  272. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_peer_request.py +0 -0
  273. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_provenance.py +0 -0
  274. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_response_authentication.py +0 -0
  275. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_revocation.py +0 -0
  276. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_sealed_channel.py +0 -0
  277. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_sev_snp.py +0 -0
  278. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_snp_tdx_attest.py +0 -0
  279. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_tpm.py +0 -0
  280. {ca2a_runtime-0.3.0 → ca2a_runtime-0.4.0}/tests/unit/test_tpm_attest.py +0 -0
@@ -0,0 +1,5 @@
1
+ FROM gcr.io/oss-fuzz-base/base-builder-python
2
+
3
+ COPY . $SRC/ca2a
4
+ COPY .clusterfuzzlite/build.sh $SRC/build.sh
5
+ WORKDIR $SRC/ca2a
@@ -0,0 +1,60 @@
1
+ # Fuzzing
2
+
3
+ Coverage-guided fuzzing via [ClusterFuzzLite](https://google.github.io/clusterfuzzlite/),
4
+ running Atheris against the input cA2A reads before it has decided to trust the
5
+ party that sent it. Mirrors the setup in agentrust-io/agent-manifest and
6
+ agentrust-io/cmcp.
7
+
8
+ ## What is fuzzed
9
+
10
+ | Target | Surface |
11
+ | --- | --- |
12
+ | `fuzz_wire_documents.py` | JSON from outside: the A2A request `PeerNode.handle` serves (delegation chain, holder proof, caller offer, sealed payload), the authenticated-response envelope, a revocation snapshot, and the chain and TRACE DAG documents the offline verifier loads. |
13
+ | `fuzz_attestation_parsers.py` | TDX quote, SEV-SNP report, TPM quote and TPMT_SIGNATURE parsing, plus `verify_challenge` over caller-supplied challenge text. |
14
+ | `fuzz_canonical_json.py` | The RFC 8785 bytes every credential, holder proof, revocation statement and response MAC is computed over. |
15
+
16
+ ## The properties
17
+
18
+ The wire and parser targets assert that each surface fails closed: it returns,
19
+ or raises the error family it documents (`CA2AError`, and `AttestationFailed`
20
+ for evidence). A `TypeError`, `struct.error`, `AttributeError` or
21
+ `RecursionError` escaping means a caller written against the documented
22
+ exception will not catch it, and on the reference server the connection drops
23
+ with no response.
24
+
25
+ `fuzz_canonical_json.py` asserts a round trip: parsing the canonical output must
26
+ reproduce the input exactly, and integers outside +/-(2**53 - 1) must be
27
+ refused. cA2A's data model has no floats, so the comparison is exact.
28
+
29
+ ## Standing when added
30
+
31
+ A local plain-Python run of each `TestOneInput` (Atheris does not install on the
32
+ Windows machine it was written on) found three escapes that are fixed in the
33
+ same change, with regression tests: `TdxQuote.parse` raising `struct.error` on a
34
+ quote whose signature section is shorter than its declared contents,
35
+ `verify_challenge` raising `TypeError` on a non-ASCII MAC, and
36
+ `DelegationCredential.from_dict` raising `TypeError` on a hop that is not an
37
+ object.
38
+
39
+ ## Bundling gotcha
40
+
41
+ `compile_python_fuzzer` bundles each target with PyInstaller, which follows
42
+ static imports only and bundles code, not package data. `build.sh` passes
43
+ `--collect-submodules=email` for the lazy `email.mime` import in the
44
+ cryptography and pydantic stacks, and `--collect-data` for `agentrust_trace`,
45
+ whose TRACE schema `ca2a_verify.dag` loads at import time.
46
+
47
+ ## Running locally
48
+
49
+ ```
50
+ git clone https://github.com/google/clusterfuzzlite --depth 1 /tmp/clusterfuzzlite
51
+ python /tmp/clusterfuzzlite/infra/helper.py build_image --external $PWD
52
+ python /tmp/clusterfuzzlite/infra/helper.py build_fuzzers --external --sanitizer address $PWD
53
+ python /tmp/clusterfuzzlite/infra/helper.py run_fuzzer --external $PWD fuzz_wire_documents
54
+ ```
55
+
56
+ ## In CI
57
+
58
+ `cflite_pr.yml` fuzzes code the pull request touched, for five minutes.
59
+ `cflite_batch.yml` runs every target for thirty minutes, nightly. Both are
60
+ read-only and time out at 45 minutes; the oss-fuzz base image build dominates.
@@ -0,0 +1,29 @@
1
+ #!/bin/bash -eu
2
+ # Build the fuzz targets for ClusterFuzzLite.
3
+ #
4
+ # Installed rather than put on the path so the targets exercise the same import
5
+ # surface a consumer gets. Third-party dependencies come from the same
6
+ # hash-pinned locks the container image uses, and the package goes in with
7
+ # --no-deps and without build isolation, so nothing here resolves unpinned.
8
+
9
+ cd "$SRC/ca2a"
10
+ pip3 install --no-cache-dir --require-hashes -r requirements/build.txt
11
+ pip3 install --no-cache-dir --require-hashes -r requirements/runtime.txt
12
+ pip3 install --no-cache-dir --no-deps --no-build-isolation .
13
+
14
+ # compile_python_fuzzer bundles each target with PyInstaller, which follows
15
+ # static imports only and bundles code, not package data.
16
+ PYI_ARGS=(
17
+ # The cryptography and pydantic stacks reach email.mime lazily; without this
18
+ # the bundled target dies with ModuleNotFoundError, which libFuzzer reports
19
+ # as a crash in the target.
20
+ --collect-submodules=email
21
+ # ca2a_verify.dag validates records against the TRACE JSON schema, which
22
+ # agentrust_trace loads from inside its package at import time.
23
+ --collect-data=agentrust_trace
24
+ --collect-data=agent_manifest
25
+ )
26
+
27
+ for target in "$SRC"/ca2a/.clusterfuzzlite/fuzz_*.py; do
28
+ compile_python_fuzzer "$target" "${PYI_ARGS[@]}"
29
+ done
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/python3
2
+ """Fuzz the attestation evidence parsers.
3
+
4
+ A callee reads a caller's evidence (``raw_evidence`` in a ``caller_offer``)
5
+ before deciding to trust anything about it, and an auditor feeds recorded
6
+ quotes to ``ca2a_verify`` offline. These parsers read that binary at fixed
7
+ offsets under declared lengths the blob itself supplies: a TDX quote with four
8
+ nested size fields, an SNP report, a TPM quote and a TPMT_SIGNATURE.
9
+
10
+ The property is that each fails closed: it returns, or raises
11
+ AttestationFailed, the error the verifier documents. A struct.error, IndexError
12
+ or OverflowError reaching the caller means a declared length was believed. That
13
+ is the bug this target was written against: TdxQuote.parse used to raise
14
+ struct.error on a quote whose signature section was shorter than it claimed.
15
+
16
+ The challenge verifier is included because its input, the offer nonce and the
17
+ holder proof's challenge, is caller text read before anything authenticates it.
18
+ """
19
+
20
+ import sys
21
+
22
+ import atheris
23
+
24
+ with atheris.instrument_imports():
25
+ from ca2a_runtime.challenge import verify_challenge
26
+ from ca2a_runtime.errors import AttestationFailed
27
+ from ca2a_runtime.tee.sev_snp import SevSnpReport
28
+ from ca2a_runtime.tee.tdx import TdxQuote
29
+ from ca2a_runtime.tee.tpm import TpmQuote
30
+ from ca2a_verify.tpm import parse_tpmt_signature
31
+
32
+ _SECRET = b"\x00" * 32
33
+
34
+ _PARSERS = [
35
+ TdxQuote.parse,
36
+ SevSnpReport.parse,
37
+ TpmQuote.parse,
38
+ parse_tpmt_signature,
39
+ ]
40
+
41
+
42
+ def TestOneInput(data: bytes) -> None:
43
+ if not data:
44
+ return
45
+ fdp = atheris.FuzzedDataProvider(data)
46
+ choice = fdp.ConsumeIntInRange(0, len(_PARSERS))
47
+ try:
48
+ if choice == len(_PARSERS):
49
+ verify_challenge(_SECRET, fdp.ConsumeUnicode(fdp.remaining_bytes()))
50
+ else:
51
+ _PARSERS[choice](fdp.ConsumeBytes(fdp.remaining_bytes()))
52
+ except AttestationFailed:
53
+ pass
54
+
55
+
56
+ def main() -> None:
57
+ atheris.Setup(sys.argv, TestOneInput)
58
+ atheris.Fuzz()
59
+
60
+
61
+ if __name__ == "__main__":
62
+ main()
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/python3
2
+ """Fuzz the RFC 8785 canonicalizer for the invariant its signatures depend on.
3
+
4
+ canonicalize() produces the bytes every delegation credential, holder proof,
5
+ revocation statement and response MAC is computed over, so the bytes must be a
6
+ faithful, lossless encoding of the input. The property asserted is a round trip:
7
+ parsing the canonical output back must reproduce the input exactly.
8
+
9
+ A crash-only check would pass over the bugs that matter here. The ones found in
10
+ sibling canonicalizers were silent: two distinct keys serialized as one key
11
+ twice, a parser kept one of the pair, and a field vanished from a document whose
12
+ signature claimed to cover it.
13
+
14
+ Structure is built from the fuzz data rather than by mutating JSON text, so the
15
+ budget goes on key and value shapes (combining marks, surrogate pairs, control
16
+ characters, integers straddling 2**53) rather than on producing valid JSON.
17
+ """
18
+
19
+ import json
20
+ import sys
21
+
22
+ import atheris
23
+
24
+ with atheris.instrument_imports():
25
+ from ca2a_runtime.canonical import canonicalize
26
+
27
+ _MAX_DEPTH = 4
28
+ _MAX_ITEMS = 6
29
+ _SAFE = 2**53 - 1
30
+
31
+
32
+ def _build(fdp, depth=0):
33
+ if depth >= _MAX_DEPTH or fdp.remaining_bytes() == 0:
34
+ return fdp.ConsumeUnicodeNoSurrogates(16)
35
+ kind = fdp.ConsumeIntInRange(0, 6)
36
+ if kind == 0:
37
+ return None
38
+ if kind == 1:
39
+ return fdp.ConsumeBool()
40
+ if kind == 2:
41
+ # Straddle the safe-integer boundary: outside it canonicalize() must
42
+ # refuse rather than emit digits a conforming verifier would not.
43
+ return fdp.ConsumeIntInRange(-(2**54), 2**54)
44
+ if kind == 3:
45
+ return fdp.ConsumeUnicodeNoSurrogates(64)
46
+ if kind == 4:
47
+ return [_build(fdp, depth + 1) for _ in range(fdp.ConsumeIntInRange(0, _MAX_ITEMS))]
48
+ return {
49
+ fdp.ConsumeUnicodeNoSurrogates(24): _build(fdp, depth + 1)
50
+ for _ in range(fdp.ConsumeIntInRange(0, _MAX_ITEMS))
51
+ }
52
+
53
+
54
+ def _has_unsafe_int(value):
55
+ if isinstance(value, bool):
56
+ return False
57
+ if isinstance(value, int):
58
+ return not -_SAFE <= value <= _SAFE
59
+ if isinstance(value, dict):
60
+ return any(_has_unsafe_int(v) for v in value.values())
61
+ if isinstance(value, list):
62
+ return any(_has_unsafe_int(v) for v in value)
63
+ return False
64
+
65
+
66
+ def TestOneInput(data: bytes) -> None:
67
+ fdp = atheris.FuzzedDataProvider(data)
68
+ value = _build(fdp)
69
+ try:
70
+ out = canonicalize(value)
71
+ except ValueError:
72
+ # Declared: integers outside the RFC 8785 safe domain, and only those.
73
+ assert _has_unsafe_int(value), "canonicalize() refused an in-domain value"
74
+ return
75
+ assert not _has_unsafe_int(value), "an out-of-domain integer was serialized"
76
+
77
+ # cA2A's data model has no floats, so every number re-parses as the int it
78
+ # was and the round trip can be asserted exactly, bools included.
79
+ assert json.loads(out) == value, f"canonical bytes did not round-trip: {out!r}"
80
+ assert canonicalize(json.loads(out)) == out, "canonical form is not a fixed point"
81
+
82
+
83
+ def main() -> None:
84
+ atheris.Setup(sys.argv, TestOneInput)
85
+ atheris.Fuzz()
86
+
87
+
88
+ if __name__ == "__main__":
89
+ main()
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/python3
2
+ """Fuzz the JSON documents cA2A accepts from outside.
3
+
4
+ Each surface takes arbitrary JSON from a party that has not been authenticated
5
+ yet, and each documents one error family for anything malformed:
6
+
7
+ - the A2A request a callee serves (``PeerNode.handle``), which parses the
8
+ delegation chain, holder proof, caller offer and sealed payload, then runs
9
+ the inbound pipeline up to the first refusal: CA2AError;
10
+ - the authenticated-response envelope (``response.decode``):
11
+ ResponseAuthenticationFailed, a CA2AError;
12
+ - a revocation snapshot (``RevocationSnapshot.from_dict``): InvalidRevocation;
13
+ - a delegation chain document and a TRACE DAG handed to the offline verifier:
14
+ CA2AError.
15
+
16
+ Anything else escaping (TypeError, AttributeError, KeyError, UnicodeError,
17
+ RecursionError) is a crash: on the live path it drops the connection with no
18
+ response, and offline it escapes the verifier's contract. The input is decoded
19
+ the way the reference server decodes a body, so what reaches the parsers is
20
+ exactly what the wire can deliver.
21
+ """
22
+
23
+ import contextlib
24
+ import json
25
+ import sys
26
+
27
+ import atheris
28
+
29
+ with atheris.instrument_imports():
30
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
31
+
32
+ from ca2a_runtime import response
33
+ from ca2a_runtime.delegation import RevocationSnapshot
34
+ from ca2a_runtime.errors import CA2AError
35
+ from ca2a_runtime.node import PeerNode
36
+ from ca2a_runtime.policy import LocalPolicy
37
+ from ca2a_verify.dag import verify_trace_dag
38
+ from ca2a_verify.verify import _parse_chain, verify_delegation_chain
39
+
40
+ _ROOT = Ed25519PrivateKey.from_private_bytes(b"\x01" * 32).public_key().public_bytes_raw().hex()
41
+ _NODE = PeerNode(LocalPolicy.of(["read", "write"]), trusted_root_issuers={_ROOT})
42
+
43
+
44
+ def _handle(doc):
45
+ if isinstance(doc, dict):
46
+ _NODE.handle(doc)
47
+
48
+
49
+ def _revocations(doc):
50
+ RevocationSnapshot.from_dict(doc)
51
+
52
+
53
+ def _chain(doc):
54
+ verify_delegation_chain(_parse_chain(doc), trusted_root_issuers={_ROOT})
55
+
56
+
57
+ def _dag(doc):
58
+ if isinstance(doc, dict):
59
+ doc = doc.get("records", doc)
60
+ if isinstance(doc, list) and doc:
61
+ verify_trace_dag(doc, trusted_keys=[])
62
+
63
+
64
+ _JSON_TARGETS = [_handle, _revocations, _chain, _dag]
65
+
66
+
67
+ def TestOneInput(data: bytes) -> None:
68
+ if not data:
69
+ return
70
+ fdp = atheris.FuzzedDataProvider(data)
71
+ choice = fdp.ConsumeIntInRange(0, len(_JSON_TARGETS))
72
+ raw = fdp.ConsumeBytes(fdp.remaining_bytes())
73
+ if choice == len(_JSON_TARGETS):
74
+ with contextlib.suppress(CA2AError):
75
+ response.decode(raw)
76
+ return
77
+ try:
78
+ doc = json.loads(raw)
79
+ except (ValueError, RecursionError):
80
+ # The reference server answers 400 before any parser runs.
81
+ return
82
+ with contextlib.suppress(CA2AError):
83
+ _JSON_TARGETS[choice](doc)
84
+
85
+
86
+ def main() -> None:
87
+ atheris.Setup(sys.argv, TestOneInput)
88
+ atheris.Fuzz()
89
+
90
+
91
+ if __name__ == "__main__":
92
+ main()
@@ -0,0 +1,39 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ day: "monday"
8
+ open-pull-requests-limit: 5
9
+ ignore:
10
+ - dependency-name: "*"
11
+ update-types:
12
+ - "version-update:semver-major"
13
+
14
+ - package-ecosystem: "github-actions"
15
+ directory: "/"
16
+ schedule:
17
+ interval: "weekly"
18
+ day: "monday"
19
+ open-pull-requests-limit: 5
20
+ groups:
21
+ codeql-action:
22
+ patterns:
23
+ - "github/codeql-action*"
24
+
25
+ # Refreshes the digest the Dockerfile pins its base image to. Without this the
26
+ # pin would hold the image at whatever it was on the day it was written.
27
+ - package-ecosystem: "docker"
28
+ directory: "/"
29
+ schedule:
30
+ interval: "weekly"
31
+ day: "monday"
32
+ open-pull-requests-limit: 5
33
+ # Digest refreshes and 3.11 patch releases only. Moving the image to a new
34
+ # Python minor is a deliberate change with its own PR and test run.
35
+ ignore:
36
+ - dependency-name: "python"
37
+ update-types:
38
+ - "version-update:semver-major"
39
+ - "version-update:semver-minor"
@@ -0,0 +1,36 @@
1
+ name: ClusterFuzzLite batch
2
+
3
+ on:
4
+ schedule:
5
+ # 04:40 UTC daily, off the hour and off agent-manifest's 04:20 slot.
6
+ - cron: '40 4 * * *'
7
+ workflow_dispatch:
8
+
9
+ permissions: read-all
10
+
11
+ concurrency:
12
+ group: cflite-batch
13
+ cancel-in-progress: false
14
+
15
+ jobs:
16
+ fuzz:
17
+ name: Batch fuzz
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 45
20
+ steps:
21
+ - name: Build fuzzers
22
+ uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
23
+ with:
24
+ language: python
25
+ github-token: ${{ secrets.GITHUB_TOKEN }}
26
+ sanitizer: address
27
+
28
+ - name: Run fuzzers
29
+ uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
30
+ with:
31
+ github-token: ${{ secrets.GITHUB_TOKEN }}
32
+ # Every target, not just what changed. Thirty minutes of fuzzing
33
+ # leaves room for the base image build inside the 45 minute timeout.
34
+ fuzz-seconds: 1800
35
+ mode: batch
36
+ sanitizer: address
@@ -0,0 +1,43 @@
1
+ name: ClusterFuzzLite PR
2
+
3
+ on:
4
+ pull_request:
5
+ paths:
6
+ - 'src/**'
7
+ - 'requirements/build.txt'
8
+ - 'requirements/runtime.txt'
9
+ - '.clusterfuzzlite/**'
10
+ - '.github/workflows/cflite_pr.yml'
11
+
12
+ # Read-only. Findings surface in the job log and the uploaded crash artifact
13
+ # rather than as code-scanning alerts, so no security-events: write is needed.
14
+ permissions: read-all
15
+
16
+ concurrency:
17
+ group: cflite-pr-${{ github.ref }}
18
+ cancel-in-progress: true
19
+
20
+ jobs:
21
+ fuzz:
22
+ name: Fuzz changed code
23
+ runs-on: ubuntu-latest
24
+ timeout-minutes: 45
25
+ steps:
26
+ # Only the address sanitizer. The targets are pure Python under Atheris,
27
+ # where undefined-behaviour instrumentation has nothing to instrument.
28
+ - name: Build fuzzers
29
+ uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
30
+ with:
31
+ language: python
32
+ github-token: ${{ secrets.GITHUB_TOKEN }}
33
+ sanitizer: address
34
+
35
+ - name: Run fuzzers
36
+ uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
37
+ with:
38
+ github-token: ${{ secrets.GITHUB_TOKEN }}
39
+ fuzz-seconds: 300
40
+ # code-change fuzzes only what the PR touched, which is what keeps
41
+ # this inside a PR's time budget. The nightly batch covers the rest.
42
+ mode: code-change
43
+ sanitizer: address
@@ -28,16 +28,16 @@ jobs:
28
28
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29
29
 
30
30
  - name: Initialize CodeQL
31
- uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
31
+ uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
32
32
  with:
33
33
  languages: python
34
34
  queries: +security-extended
35
35
 
36
36
  - name: Autobuild
37
- uses: github/codeql-action/autobuild@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
37
+ uses: github/codeql-action/autobuild@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
38
38
 
39
39
  - name: Perform CodeQL Analysis
40
- uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
40
+ uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
41
41
  continue-on-error: true
42
42
  with:
43
43
  category: /language:python
@@ -9,6 +9,8 @@ on:
9
9
  - "Dockerfile"
10
10
  - ".dockerignore"
11
11
  - "pyproject.toml"
12
+ - "requirements/build.txt"
13
+ - "requirements/runtime.txt"
12
14
  - "src/**"
13
15
  - ".github/workflows/docker.yml"
14
16
 
@@ -13,12 +13,15 @@ on:
13
13
 
14
14
  permissions:
15
15
  contents: read
16
- pull-requests: read
17
- statuses: write
18
16
 
19
17
  jobs:
20
18
  gate:
21
19
  runs-on: ubuntu-latest
20
+ # Reads the PR and its reviews, then fails the job if no maintainer
21
+ # approved the head commit. It writes nothing, so no write scope.
22
+ permissions:
23
+ contents: read
24
+ pull-requests: read
22
25
  if: github.event.pull_request.base.ref == 'main'
23
26
  steps:
24
27
  - name: Check for maintainer approval
@@ -38,6 +38,6 @@ jobs:
38
38
  publish_results: true
39
39
 
40
40
  - name: Upload SARIF
41
- uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
41
+ uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
42
42
  with:
43
43
  sarif_file: scorecard-results.sarif
@@ -7,6 +7,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-09-30
11
+
12
+ ### Security
13
+
14
+ - A delegate with no enclave could be recorded as `caller_attestation="hardware"`
15
+ by relaying an honest hardware peer's attestation: it asked that peer's public
16
+ `/.well-known/ca2a/channel` endpoint to attest under the target's challenge and
17
+ presented the result as its `caller_offer`. Caller offers are now minted in a
18
+ separate role, signed under their own binding prefixes (`ca2a-*-caller-v2|`),
19
+ and the handshake endpoint only mints callee-role offers. The caller must also
20
+ send `caller_possession`, a MAC over the request transcript keyed from X25519
21
+ between its attested channel key and the callee's, which the callee verifies
22
+ before recording any outcome.
23
+
24
+ ### Changed
25
+
26
+ - **Breaking for callers that send `caller_offer`** (`ca2a-caller-offer-v2`). A
27
+ pre-v2 caller offer has no `role` and no `caller_possession` and is refused with
28
+ `ATTESTATION_FAILED`. Callers that send no offer, and the handshake response,
29
+ are unchanged on the wire. `BaseProvider.attest` and the binding helpers
30
+ (`tpm_qualifying_data`, `snp_report_data`, `tdx_report_data`) take a keyword
31
+ `role`, defaulting to the callee role; a custom `Verifier` must derive the
32
+ binding under `report.role`.
33
+
34
+ ## [0.3.1] - 2026-09-26
35
+
36
+ ### Fixed
37
+
38
+ - Malformed input from outside now fails with the documented error instead of
39
+ escaping as a Python exception. `TdxQuote.parse` confines reads to the declared
40
+ signature section and checks each nested length (it raised `struct.error` on a
41
+ short section); `verify_challenge` refuses a MAC that is not 64 lowercase hex
42
+ (a non-ASCII one raised `TypeError`); `DelegationCredential.from_dict` and
43
+ `verify_trace_dag` refuse entries that are not objects; the reference server
44
+ answers 400 to bodies that hit the JSON parser's nesting or integer-digit
45
+ limits instead of dropping the connection (#202).
46
+ - `canonicalize` refuses non-string object keys. It coerced them with `str()`, so
47
+ `{1: "a", "1": "b"}` serialized one key twice (#202).
48
+ - The container image installs its dependencies from hash-pinned locks
49
+ (`requirements/runtime.txt`, `requirements/build.txt`) and pins the base image
50
+ by digest. ClusterFuzzLite targets cover the wire documents, the attestation
51
+ parsers and the canonicalizer (#202).
52
+
10
53
  ## [0.3.0] - 2026-09-25
11
54
 
12
55
  ### Changed
@@ -391,7 +434,9 @@ built/stubbed boundary.
391
434
  - The sealed channel does not by itself establish the enclave-held-private-key property; that is a hardware attestation guarantee that lands with real-hardware validation.
392
435
  - Alpha schemas: the delegation credential and TRACE link schemas are not yet stable or versioned, and peer attestation evidence is not yet RATS/EAT conformant.
393
436
 
394
- [Unreleased]: https://github.com/agentrust-io/ca2a/compare/v0.3.0...main
437
+ [Unreleased]: https://github.com/agentrust-io/ca2a/compare/v0.4.0...main
438
+ [0.4.0]: https://github.com/agentrust-io/ca2a/compare/v0.3.1...v0.4.0
439
+ [0.3.1]: https://github.com/agentrust-io/ca2a/compare/v0.3.0...v0.3.1
395
440
  [0.3.0]: https://github.com/agentrust-io/ca2a/compare/v0.2.0...v0.3.0
396
441
  [0.2.0]: https://github.com/agentrust-io/ca2a/compare/v0.1.0a1...v0.2.0
397
442
  [0.1.0a1]: https://github.com/agentrust-io/ca2a/releases/tag/v0.1.0a1
@@ -0,0 +1,40 @@
1
+ # Base image pinned by its multi-arch index digest, so a re-pushed tag cannot
2
+ # change what is built. Refresh the digest and the tag together.
3
+ FROM python:3.11.16-slim-bookworm@sha256:a36c24f9cbdf4fd0f52d67f0823eeac19c2028c637cecc392d97f980d4fec56b AS builder
4
+
5
+ ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
6
+ PIP_NO_CACHE_DIR=1
7
+
8
+ WORKDIR /build
9
+ COPY requirements/build.txt ./requirements/build.txt
10
+ # The build backend comes from a hash-pinned lock, and the wheel is built
11
+ # without build isolation so pip does not fetch an unpinned hatchling of its own.
12
+ RUN python -m pip install --require-hashes -r requirements/build.txt
13
+ COPY pyproject.toml README.md LICENSE NOTICE ./
14
+ COPY src ./src
15
+ RUN python -m pip wheel --no-deps --no-build-isolation --wheel-dir /wheels .
16
+
17
+ FROM python:3.11.16-slim-bookworm@sha256:a36c24f9cbdf4fd0f52d67f0823eeac19c2028c637cecc392d97f980d4fec56b AS runtime
18
+
19
+ ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
20
+ PIP_NO_CACHE_DIR=1 \
21
+ PYTHONUNBUFFERED=1
22
+
23
+ RUN groupadd --gid 10001 ca2a \
24
+ && useradd --uid 10001 --gid 10001 --no-create-home --home-dir /var/lib/ca2a ca2a \
25
+ && install -d -o ca2a -g ca2a /var/lib/ca2a /etc/ca2a
26
+
27
+ # Third-party runtime dependencies from the hash-pinned lock, then the locally
28
+ # built wheel with --no-deps, so nothing in the image is resolved unpinned.
29
+ COPY requirements/runtime.txt /tmp/runtime.txt
30
+ COPY --from=builder /wheels /wheels
31
+ RUN python -m pip install --require-hashes -r /tmp/runtime.txt \
32
+ && python -m pip install --no-deps /wheels/ca2a_runtime-*.whl \
33
+ && rm -rf /wheels /tmp/runtime.txt
34
+
35
+ USER 10001:10001
36
+ WORKDIR /var/lib/ca2a
37
+ EXPOSE 8443
38
+
39
+ ENTRYPOINT ["ca2a"]
40
+ CMD ["--help"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ca2a-runtime
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Confidential agent-to-agent runtime: attested, attenuated delegation and sealed peer channels for A2A
5
5
  Project-URL: Homepage, https://github.com/agentrust-io/ca2a
6
6
  Project-URL: Repository, https://github.com/agentrust-io/ca2a
@@ -22,7 +22,7 @@ Classifier: Topic :: Security
22
22
  Requires-Python: >=3.11
23
23
  Requires-Dist: agent-manifest>=0.11.1
24
24
  Requires-Dist: agentrust-trace>=0.5
25
- Requires-Dist: cedarpy==4.8.7
25
+ Requires-Dist: cedarpy==4.12.1
26
26
  Requires-Dist: cryptography>=50.0
27
27
  Requires-Dist: pyyaml>=6.0
28
28
  Requires-Dist: rfc8785>=0.1
@@ -30,7 +30,7 @@ Provides-Extra: a2a-sdk
30
30
  Requires-Dist: a2a-sdk<2,>=1.1; extra == 'a2a-sdk'
31
31
  Requires-Dist: aiohttp>=3.14.3; extra == 'a2a-sdk'
32
32
  Provides-Extra: dev
33
- Requires-Dist: a2a-sdk[http-server]==1.1.4; extra == 'dev'
33
+ Requires-Dist: a2a-sdk[http-server]==1.1.5; extra == 'dev'
34
34
  Requires-Dist: agentrust-trace-tests<0.6,>=0.4; extra == 'dev'
35
35
  Requires-Dist: aiohttp>=3.14.3; extra == 'dev'
36
36
  Requires-Dist: bandit[toml]>=1.7; extra == 'dev'
@@ -23,13 +23,20 @@ binding = sha256(prefix || len32(public_key) || public_key || len32(nonce) || no
23
23
  | AMD SEV-SNP | `ca2a-snp-v1\|` | `REPORT_DATA` (64 bytes) | the binding, zero-padded on the right |
24
24
  | Intel TDX | `ca2a-tdx-v1\|` | `REPORTDATA` (64 bytes) | the binding, zero-padded on the right |
25
25
 
26
+ Those are the **callee-role** prefixes: what the handshake endpoint signs. A caller
27
+ offer (see [mutual attestation](mutual-attestation.md)) is signed under its own
28
+ prefix per platform, `ca2a-tpm-caller-v2|`, `ca2a-snp-caller-v2|` and
29
+ `ca2a-tdx-caller-v2|`, selected by the report's `role`. A verifier MUST derive the
30
+ binding under the report's role, so an offer relabelled from one role to the other
31
+ recomputes different bytes and is rejected.
32
+
26
33
  Committing the nonce alone would sign for freshness only, leaving `public_key` an unsigned assertion, so sealing a payload "to a key from a verified report" would not actually be rooted in hardware. A verifier re-derives this value from the report's own fields and requires equality, which is what promotes `public_key` and `nonce` from claim to signed fact. A report whose key was substituted after collection is rejected.
27
34
 
28
35
  Three encoding details are load-bearing, and each prefix is versioned because this is wire format: a peer and its verifier MUST derive identical bytes.
29
36
 
30
37
  - **Hashed, not raw.** `TPM2B_DATA` is capped below 64 bytes on some platforms (Azure returns `TPM_RC_SIZE`), and 32 bytes always fits. SEV-SNP and TDX reserve 64, so the digest is left-aligned and zero-padded, which is the convention the kernel's own callers and `agent-manifest` use; a report collected by either runtime is then byte-comparable.
31
38
  - **Length-prefixed, not delimiter-joined.** With a delimiter a value containing it shifts the split without changing the digest, so `("a|b", "c")` and `("a", "b|c")` would commit identical bytes and a peer could bind a key other than the one it appears to offer. `nonce` is an arbitrary caller-supplied string, so that is reachable rather than theoretical.
32
- - **Domain-separated per platform.** The three prefixes differ so a report collected on one platform cannot be replayed as another's evidence.
39
+ - **Domain-separated per platform and per role.** The prefixes differ so a report collected on one platform cannot be replayed as another's evidence, and so a callee offer, which anybody can obtain from the public handshake endpoint under a nonce of their choosing, cannot be presented as a caller offer.
33
40
 
34
41
  `ca2a_runtime.tee.binding` holds the single derivation the three providers share.
35
42