ca2a-runtime 0.2.0__tar.gz → 0.3.1__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.
- ca2a_runtime-0.3.1/.clusterfuzzlite/Dockerfile +5 -0
- ca2a_runtime-0.3.1/.clusterfuzzlite/README.md +60 -0
- ca2a_runtime-0.3.1/.clusterfuzzlite/build.sh +29 -0
- ca2a_runtime-0.3.1/.clusterfuzzlite/fuzz_attestation_parsers.py +62 -0
- ca2a_runtime-0.3.1/.clusterfuzzlite/fuzz_canonical_json.py +89 -0
- ca2a_runtime-0.3.1/.clusterfuzzlite/fuzz_wire_documents.py +92 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/CODEOWNERS +7 -7
- ca2a_runtime-0.3.1/.github/dependabot.yml +39 -0
- ca2a_runtime-0.3.1/.github/workflows/actionlint.yml +41 -0
- ca2a_runtime-0.3.1/.github/workflows/cflite_batch.yml +36 -0
- ca2a_runtime-0.3.1/.github/workflows/cflite_pr.yml +43 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/workflows/ci.yml +46 -16
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/workflows/codeql.yml +4 -4
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/workflows/contributor-check.yml +1 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/workflows/docker.yml +12 -7
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/workflows/docs.yml +14 -5
- ca2a_runtime-0.3.1/.github/workflows/limitations-parity.yml +75 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/workflows/release-drafter.yml +7 -3
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/workflows/release.yml +22 -9
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/workflows/require-maintainer-approval.yml +6 -3
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/workflows/sbom.yml +7 -5
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/workflows/scorecard.yml +2 -2
- ca2a_runtime-0.3.1/.github/workflows/vouch-check.yml +111 -0
- ca2a_runtime-0.3.1/.github/workflows/vouch-command.yml +89 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/ADOPTERS.md +3 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/CHANGELOG.md +164 -2
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/CHARTER.md +1 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/CONTRIBUTING.md +17 -1
- ca2a_runtime-0.3.1/Dockerfile +40 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/GOVERNANCE.md +22 -4
- ca2a_runtime-0.3.1/LIMITATIONS.md +104 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/MAINTAINERS.md +15 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/NOTICE +1 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/PKG-INFO +28 -13
- ca2a_runtime-0.3.1/PRIVACY.md +9 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/README.md +182 -168
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/ROADMAP.md +1 -1
- ca2a_runtime-0.3.1/SPONSORS.md +20 -0
- ca2a_runtime-0.3.1/docs/assets/icon.svg +26 -0
- ca2a_runtime-0.3.1/docs/concepts.md +63 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/configuration.md +14 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/hardware-validation.md +74 -12
- ca2a_runtime-0.3.1/docs/mutual-hardware-acceptance.md +150 -0
- ca2a_runtime-0.3.1/docs/quickstart.md +125 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/spec/attestation.md +4 -4
- ca2a_runtime-0.3.1/docs/spec/call-graph.md +50 -0
- ca2a_runtime-0.3.1/docs/spec/cedar-policy.md +44 -0
- ca2a_runtime-0.3.1/docs/spec/component-model.md +39 -0
- ca2a_runtime-0.3.1/docs/spec/delegation-chain.md +187 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/spec/error-codes.md +18 -5
- ca2a_runtime-0.3.1/docs/spec/failure-modes.md +37 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/spec/mutual-attestation.md +11 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/spec/profile.md +9 -0
- ca2a_runtime-0.3.1/docs/spec/provenance-dag.md +45 -0
- ca2a_runtime-0.3.1/docs/spec/response-authentication.md +132 -0
- ca2a_runtime-0.3.1/docs/spec/response-binding-requirements.md +113 -0
- ca2a_runtime-0.3.1/docs/spec/sealed-channel.md +43 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/spec/threat-model.md +4 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/spec/transport.md +48 -3
- ca2a_runtime-0.3.1/docs/spec/verification-library.md +55 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/tutorials/authoring-a-delegation-credential.md +32 -12
- ca2a_runtime-0.3.1/docs/tutorials/emit-and-verify-provenance.md +104 -0
- ca2a_runtime-0.3.1/docs/tutorials/integrating-with-a2a.md +137 -0
- ca2a_runtime-0.3.1/docs/tutorials/reproducing-the-claims.md +56 -0
- ca2a_runtime-0.3.1/docs/tutorials/verify-a-delegation-chain.md +62 -0
- ca2a_runtime-0.3.1/examples/a2a-sdk/loopback.py +333 -0
- ca2a_runtime-0.3.1/examples/a2a-sdk/requirements.txt +3 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/cross-operator-delegation/README.md +10 -8
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/cross-operator-delegation/demo.py +129 -48
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/minimal/README.md +1 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/minimal/ca2a-config.yaml +4 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/rejection-with-proof/README.md +7 -5
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/rejection-with-proof/demo.py +34 -13
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/trace-dag/README.md +3 -2
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/trace-dag/demo.py +16 -7
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim1-attenuation-soundness/run.py +7 -6
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim2-cross-chain-replay/run.py +9 -8
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim3-scope-policy-intersection/run.py +9 -9
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim4-sealed-payload-confidentiality/run.py +2 -2
- ca2a_runtime-0.3.1/experiments/hardware-validation/mutual-snp-2026-09-17/README.md +58 -0
- ca2a_runtime-0.3.1/experiments/hardware-validation/mutual-snp-2026-09-17/a-to-b.json +168 -0
- ca2a_runtime-0.3.1/experiments/hardware-validation/mutual-snp-2026-09-17/b-to-a.json +168 -0
- ca2a_runtime-0.3.1/experiments/hardware-validation/mutual-snp-2026-09-17/controls.py +85 -0
- ca2a_runtime-0.3.1/hooks/seo.py +102 -0
- ca2a_runtime-0.3.1/index.md +74 -0
- ca2a_runtime-0.3.1/mkdocs.yml +212 -0
- ca2a_runtime-0.3.1/overrides/main.html +130 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/pyproject.toml +8 -7
- ca2a_runtime-0.3.1/requirements/agt-overrides.txt +16 -0
- ca2a_runtime-0.3.1/requirements/agt.in +11 -0
- ca2a_runtime-0.3.1/requirements/agt.txt +1314 -0
- ca2a_runtime-0.3.1/requirements/build.in +6 -0
- ca2a_runtime-0.3.1/requirements/build.txt +26 -0
- ca2a_runtime-0.3.1/requirements/dev.in +27 -0
- ca2a_runtime-0.3.1/requirements/dev.txt +2380 -0
- ca2a_runtime-0.3.1/requirements/docs.txt +597 -0
- ca2a_runtime-0.3.1/requirements/release.in +5 -0
- ca2a_runtime-0.3.1/requirements/release.txt +488 -0
- ca2a_runtime-0.3.1/requirements/runtime.in +12 -0
- ca2a_runtime-0.3.1/requirements/runtime.txt +620 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/requirements-docs.txt +2 -2
- ca2a_runtime-0.3.1/robots.txt +38 -0
- ca2a_runtime-0.3.1/scripts/gen_action_fixtures.py +233 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/agent_manifest.py +7 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/attestation.py +12 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/benchmarks.py +1 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/bootstrap.py +60 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/canonical.py +26 -3
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/challenge.py +10 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/cli.py +178 -9
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/config.py +55 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/delegation/__init__.py +12 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/delegation/credential.py +108 -8
- ca2a_runtime-0.3.1/src/ca2a_runtime/delegation/revocation.py +292 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/errors.py +62 -0
- ca2a_runtime-0.3.1/src/ca2a_runtime/hardware_acceptance.py +264 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/node.py +29 -2
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/peer.py +40 -11
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/provenance.py +8 -2
- ca2a_runtime-0.3.1/src/ca2a_runtime/response.py +269 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/tee/sev_snp.py +19 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/tee/tdx.py +22 -6
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/trace_binding.py +8 -1
- ca2a_runtime-0.3.1/src/ca2a_runtime/transport/_b64url.py +44 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/transport/a2a_adapter.py +3 -35
- ca2a_runtime-0.3.1/src/ca2a_runtime/transport/a2a_sdk.py +294 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/transport/client.py +90 -9
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/transport/server.py +16 -3
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/transport/wire.py +29 -6
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_verify/__init__.py +2 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_verify/dag.py +47 -14
- ca2a_runtime-0.3.1/src/ca2a_verify/sev_snp.py +186 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_verify/tpm.py +53 -8
- ca2a_runtime-0.3.1/src/ca2a_verify/verify.py +130 -0
- ca2a_runtime-0.3.1/tests/committed_blobs.py +56 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/conformance/README.md +68 -13
- ca2a_runtime-0.3.1/tests/conformance/test_action_fixture_bundles.py +131 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/conformance/test_profile_conformance.py +168 -31
- ca2a_runtime-0.3.1/tests/conftest.py +78 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/README.md +68 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-001-verified/chain.json +29 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-001-verified/dag.json +27 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-001-verified/expected.json +10 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-002-parent-hash-mismatch/chain.json +29 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-002-parent-hash-mismatch/dag.json +27 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-002-parent-hash-mismatch/expected.json +10 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-006-policy-denial/chain.json +42 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-006-policy-denial/dag.json +55 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-006-policy-denial/expected.json +10 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-010-scope-escalation/chain.json +27 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-010-scope-escalation/dag.json +25 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-010-scope-escalation/expected.json +10 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-012-credential-expired/chain.json +33 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-012-credential-expired/dag.json +27 -0
- ca2a_runtime-0.3.1/tests/fixtures/action/action-012-credential-expired/expected.json +10 -0
- ca2a_runtime-0.3.1/tests/fixtures/response-mac-v1.json +82 -0
- ca2a_runtime-0.3.1/tests/fixtures/tpm/bare-rsa-prefix-0016/README.md +19 -0
- ca2a_runtime-0.3.1/tests/fixtures/tpm/bare-rsa-prefix-0016/ak-chain.pem +24 -0
- ca2a_runtime-0.3.1/tests/fixtures/tpm/bare-rsa-prefix-0016/attest.hex +1 -0
- ca2a_runtime-0.3.1/tests/fixtures/tpm/bare-rsa-prefix-0016/signature.hex +1 -0
- ca2a_runtime-0.3.1/tests/fixtures/tpm/bare-rsa-prefix-0016/trusted-root.pem +10 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_a2a_sdk_bridge.py +259 -6
- ca2a_runtime-0.3.1/tests/unit/test_a2a_sdk_loopback.py +243 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_agent_manifest_cose.py +31 -2
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_bootstrap.py +115 -3
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_canonical.py +32 -0
- {ca2a_runtime-0.2.0/tests → ca2a_runtime-0.3.1/tests/unit}/test_challenge.py +18 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_claim1_attenuation.py +5 -5
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_claim2_replay.py +4 -3
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_cli_start.py +46 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_committed_examples_verify.py +18 -20
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_config.py +59 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_container_release.py +25 -3
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_delegation.py +35 -11
- ca2a_runtime-0.3.1/tests/unit/test_dependency_security_floors.py +77 -0
- ca2a_runtime-0.3.1/tests/unit/test_docs_quickstart.py +39 -0
- ca2a_runtime-0.3.1/tests/unit/test_documented_experiments.py +59 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_example_rejection_with_proof.py +20 -10
- ca2a_runtime-0.3.1/tests/unit/test_hardware_acceptance.py +199 -0
- ca2a_runtime-0.3.1/tests/unit/test_lineage_cli.py +149 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_live_call.py +169 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_mutual_attestation.py +41 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_peer_request.py +48 -1
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_release_artifacts.py +1 -1
- ca2a_runtime-0.3.1/tests/unit/test_response_authentication.py +452 -0
- ca2a_runtime-0.3.1/tests/unit/test_revocation.py +475 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_sev_snp.py +215 -3
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_tdx.py +62 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_tpm_attest.py +157 -8
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_trace_binding.py +179 -9
- ca2a_runtime-0.3.1/tests/unit/test_verify.py +139 -0
- ca2a_runtime-0.2.0/.github/dependabot.yml +0 -19
- ca2a_runtime-0.2.0/Dockerfile +0 -30
- ca2a_runtime-0.2.0/LIMITATIONS.md +0 -30
- ca2a_runtime-0.2.0/PRIVACY.md +0 -9
- ca2a_runtime-0.2.0/docs/assets/icon.svg +0 -29
- ca2a_runtime-0.2.0/docs/concepts.md +0 -42
- ca2a_runtime-0.2.0/docs/quickstart.md +0 -78
- ca2a_runtime-0.2.0/docs/spec/call-graph.md +0 -109
- ca2a_runtime-0.2.0/docs/spec/cedar-policy.md +0 -98
- ca2a_runtime-0.2.0/docs/spec/component-model.md +0 -63
- ca2a_runtime-0.2.0/docs/spec/delegation-chain.md +0 -84
- ca2a_runtime-0.2.0/docs/spec/failure-modes.md +0 -151
- ca2a_runtime-0.2.0/docs/spec/provenance-dag.md +0 -112
- ca2a_runtime-0.2.0/docs/spec/sealed-channel.md +0 -31
- ca2a_runtime-0.2.0/docs/spec/verification-library.md +0 -29
- ca2a_runtime-0.2.0/docs/stylesheets/extra.css +0 -24
- ca2a_runtime-0.2.0/docs/tutorials/emit-and-verify-provenance.md +0 -203
- ca2a_runtime-0.2.0/docs/tutorials/integrating-with-a2a.md +0 -171
- ca2a_runtime-0.2.0/docs/tutorials/reproducing-the-claims.md +0 -188
- ca2a_runtime-0.2.0/docs/tutorials/verify-a-delegation-chain.md +0 -61
- ca2a_runtime-0.2.0/mkdocs.yml +0 -179
- ca2a_runtime-0.2.0/overrides/main.html +0 -88
- ca2a_runtime-0.2.0/src/ca2a_runtime/transport/a2a_sdk.py +0 -141
- ca2a_runtime-0.2.0/src/ca2a_verify/sev_snp.py +0 -91
- ca2a_runtime-0.2.0/src/ca2a_verify/verify.py +0 -71
- ca2a_runtime-0.2.0/tests/unit/test_dependency_security_floors.py +0 -40
- ca2a_runtime-0.2.0/tests/unit/test_verify.py +0 -64
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.dockerignore +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/.gitignore +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/CNAME +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/CODE_OF_CONDUCT.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/LICENSE +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/SECURITY.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/benchmarks/.gitkeep +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/SPEC.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/assets/og.png +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/spec/conformance.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/docs/spec/trace-a2a-profile.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/cross-operator-delegation/ca2a-config.yaml +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/cross-operator-delegation/chain.json +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/cross-operator-delegation/dag.json +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/cross-operator-delegation/policy.cedar +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/minimal/chain.json +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/rejection-with-proof/chain.json +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/examples/rejection-with-proof/dag.json +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/README.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim1-attenuation-soundness/README.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim2-cross-chain-replay/README.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim3-scope-policy-intersection/README.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim4-sealed-payload-confidentiality/README.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim5-provenance-dag-integrity/README.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim5-provenance-dag-integrity/run.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim6-cross-operator-attestation/README.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/claim6-cross-operator-attestation/run.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/hardware-validation/README.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/hardware-validation/ca2a-attested-peer-azure-2026-07-27/bridge.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/hardware-validation/ca2a-attested-peer-azure-2026-07-27/transcript.json +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/hardware-validation/ca2a-cross-tee-2026-07-27/snp_client.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/hardware-validation/ca2a-cross-tee-2026-07-27/tdx_peer.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/experiments/hardware-validation/ca2a-cross-tee-2026-07-27/transcript.json +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/governance/ca2a-enforcement.yaml +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/scripts/gen_agt_evidence.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/scripts/gen_example_chain.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/__init__.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/cedar.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/channel/__init__.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/channel/sealed.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/delegation/holder.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/policy.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/tee/__init__.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/tee/base.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/tee/binding.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/tee/software.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/tee/tpm.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/tee/tsm.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/transport/__init__.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_runtime/transport/constants.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_verify/tdx.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/src/ca2a_verify/tpm_roots.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/__init__.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/conformance/__init__.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/conformance/conftest.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/fixtures/a2a/send_message_ca2a.json +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/fixtures/hardware/azure-vtpm-2026-07-27/README.md +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/fixtures/hardware/azure-vtpm-2026-07-27/ak.pub +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/fixtures/hardware/azure-vtpm-2026-07-27/akcert_0x01C101D0.der +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/fixtures/hardware/azure-vtpm-2026-07-27/nonce.hex +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/fixtures/hardware/azure-vtpm-2026-07-27/pcrs.bin +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/fixtures/hardware/azure-vtpm-2026-07-27/quote.msg +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/fixtures/hardware/azure-vtpm-2026-07-27/quote.sig +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/fixtures/sev_snp/amd_milan_cert_chain.pem +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/fixtures/tdx/intel_sgx_root_ca.pem +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/__init__.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/conftest.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_a2a_adapter.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_cedar.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_channel_and_tee.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_claim3_scope_policy.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_claim4_sealed.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_claim5_provenance.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_claim6_cross_operator.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_errors.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_holder_binding.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_peer.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_provenance.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_sealed_channel.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_snp_tdx_attest.py +0 -0
- {ca2a_runtime-0.2.0 → ca2a_runtime-0.3.1}/tests/unit/test_tpm.py +0 -0
|
@@ -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()
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Default owners for all files
|
|
2
|
-
* @agentrust-io/maintainers
|
|
2
|
+
* @agentrust-io/maintainers @carloshvp @zohebk8s
|
|
3
3
|
|
|
4
4
|
# Security-sensitive paths require security-reviewers sign-off
|
|
5
|
-
src/ca2a_runtime/delegation/ @agentrust-io/security-reviewers @agentrust-io/maintainers
|
|
6
|
-
src/ca2a_runtime/channel/ @agentrust-io/security-reviewers @agentrust-io/maintainers
|
|
7
|
-
src/ca2a_runtime/tee/ @agentrust-io/security-reviewers @agentrust-io/maintainers
|
|
8
|
-
src/ca2a_verify/ @agentrust-io/security-reviewers @agentrust-io/maintainers
|
|
5
|
+
src/ca2a_runtime/delegation/ @agentrust-io/security-reviewers @agentrust-io/maintainers @carloshvp @zohebk8s
|
|
6
|
+
src/ca2a_runtime/channel/ @agentrust-io/security-reviewers @agentrust-io/maintainers @carloshvp @zohebk8s
|
|
7
|
+
src/ca2a_runtime/tee/ @agentrust-io/security-reviewers @agentrust-io/maintainers @carloshvp @zohebk8s
|
|
8
|
+
src/ca2a_verify/ @agentrust-io/security-reviewers @agentrust-io/maintainers @carloshvp @zohebk8s
|
|
9
9
|
|
|
10
10
|
# CI/CD workflow changes
|
|
11
|
-
.github/workflows/ @agentrust-io/maintainers
|
|
11
|
+
.github/workflows/ @agentrust-io/maintainers @carloshvp @zohebk8s
|
|
12
12
|
|
|
13
13
|
# Package configuration
|
|
14
|
-
pyproject.toml @agentrust-io/maintainers
|
|
14
|
+
pyproject.toml @agentrust-io/maintainers @carloshvp @zohebk8s
|
|
@@ -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,41 @@
|
|
|
1
|
+
name: Workflow lint
|
|
2
|
+
|
|
3
|
+
# actionlint catches what a YAML parser cannot: duplicate mapping keys that
|
|
4
|
+
# make GitHub refuse to load a workflow, invalid ${{ }} expressions, and shell
|
|
5
|
+
# problems inside run: blocks. A duplicate env: key silently broke this repo
|
|
6
|
+
# family's release workflow once; yaml.safe_load keeps the last value without
|
|
7
|
+
# complaining, so local validation passed while Actions rejected the file.
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches: [main]
|
|
11
|
+
paths: ['.github/workflows/**']
|
|
12
|
+
pull_request:
|
|
13
|
+
paths: ['.github/workflows/**']
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
actionlint:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
24
|
+
|
|
25
|
+
# Fetched and checksum-verified rather than run as a third-party action,
|
|
26
|
+
# so this check adds no new action to the supply chain it exists to guard.
|
|
27
|
+
- name: Install actionlint
|
|
28
|
+
env:
|
|
29
|
+
ACTIONLINT_VERSION: 1.7.12
|
|
30
|
+
ACTIONLINT_SHA256: 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8
|
|
31
|
+
run: |
|
|
32
|
+
set -euo pipefail
|
|
33
|
+
archive="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
|
|
34
|
+
curl -sSfL -o "$archive" \
|
|
35
|
+
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/${archive}"
|
|
36
|
+
echo "${ACTIONLINT_SHA256} ${archive}" | sha256sum -c -
|
|
37
|
+
tar -xzf "$archive" actionlint
|
|
38
|
+
install -m 0755 actionlint /usr/local/bin/actionlint
|
|
39
|
+
|
|
40
|
+
- name: Lint workflows
|
|
41
|
+
run: actionlint -color
|
|
@@ -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
|
|
@@ -12,8 +12,13 @@ on:
|
|
|
12
12
|
required: false
|
|
13
13
|
default: ""
|
|
14
14
|
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
15
17
|
jobs:
|
|
16
18
|
test:
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
id-token: write # Authenticate coverage uploads with GitHub OIDC.
|
|
17
22
|
runs-on: ${{ matrix.os }}
|
|
18
23
|
strategy:
|
|
19
24
|
matrix:
|
|
@@ -21,17 +26,26 @@ jobs:
|
|
|
21
26
|
os: [ubuntu-latest, windows-latest]
|
|
22
27
|
|
|
23
28
|
steps:
|
|
24
|
-
- uses: actions/checkout@v7
|
|
29
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
25
30
|
|
|
26
|
-
- uses: actions/setup-python@v7
|
|
31
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
27
32
|
with:
|
|
28
33
|
python-version: ${{ matrix.python-version }}
|
|
29
34
|
|
|
30
35
|
- name: Install dependencies
|
|
31
|
-
|
|
36
|
+
# Third-party dependencies come from the hash-pinned lock first; the
|
|
37
|
+
# local package then goes in with --no-deps, because pip cannot
|
|
38
|
+
# hash-pin an editable install in the same invocation.
|
|
39
|
+
run: python -m pip install --require-hashes -r requirements/dev.txt
|
|
40
|
+
|
|
41
|
+
- name: Install local package
|
|
42
|
+
run: python -m pip install --no-deps -e .
|
|
32
43
|
|
|
33
44
|
- name: Security scan
|
|
34
|
-
|
|
45
|
+
# bandit and pip-audit are already in the dev extra above, so the
|
|
46
|
+
# scan runs against the same pinned versions rather than fetching
|
|
47
|
+
# whatever is current at scan time.
|
|
48
|
+
run: bandit -r src/ -c pyproject.toml && pip-audit
|
|
35
49
|
|
|
36
50
|
- name: Lint
|
|
37
51
|
run: ruff check src/ tests/
|
|
@@ -48,23 +62,31 @@ jobs:
|
|
|
48
62
|
run: pytest tests/unit/ tests/conformance/ -v --tb=short --cov=src --cov-report=xml
|
|
49
63
|
|
|
50
64
|
- name: Upload coverage report
|
|
51
|
-
uses: codecov/codecov-action@v7
|
|
65
|
+
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
|
52
66
|
with:
|
|
53
|
-
|
|
67
|
+
use_oidc: true
|
|
68
|
+
files: coverage.xml
|
|
69
|
+
fail_ci_if_error: true
|
|
54
70
|
|
|
55
71
|
governance:
|
|
56
72
|
runs-on: ubuntu-latest
|
|
57
73
|
needs: test
|
|
58
74
|
|
|
59
75
|
steps:
|
|
60
|
-
- uses: actions/checkout@v7
|
|
76
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
61
77
|
|
|
62
|
-
- uses: actions/setup-python@v7
|
|
78
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
63
79
|
with:
|
|
64
80
|
python-version: "3.12"
|
|
65
81
|
|
|
66
82
|
- name: Install dependencies
|
|
67
|
-
|
|
83
|
+
# Third-party dependencies come from the hash-pinned lock first; the
|
|
84
|
+
# local package then goes in with --no-deps, because pip cannot
|
|
85
|
+
# hash-pin an editable install in the same invocation.
|
|
86
|
+
run: python -m pip install --require-hashes -r requirements/dev.txt
|
|
87
|
+
|
|
88
|
+
- name: Install local package
|
|
89
|
+
run: python -m pip install --no-deps -e .
|
|
68
90
|
|
|
69
91
|
# AGT 4.1 currently constrains cryptography below 49, while the runtime
|
|
70
92
|
# requires 50+ for published security fixes. Keep the scanner isolated
|
|
@@ -73,8 +95,10 @@ jobs:
|
|
|
73
95
|
- name: Install governance tooling in an isolated environment
|
|
74
96
|
run: |
|
|
75
97
|
python -m venv .agt-venv
|
|
76
|
-
|
|
77
|
-
|
|
98
|
+
# --no-deps because the lock already lists every transitive
|
|
99
|
+
# dependency, and pip would otherwise re-resolve and reject the
|
|
100
|
+
# cryptography override the compile deliberately applied.
|
|
101
|
+
.agt-venv/bin/pip install --require-hashes --no-deps -r requirements/agt.txt
|
|
78
102
|
|
|
79
103
|
- name: Generate evidence file
|
|
80
104
|
run: python scripts/gen_agt_evidence.py
|
|
@@ -89,7 +113,7 @@ jobs:
|
|
|
89
113
|
run: .agt-venv/bin/agt --json verify --evidence agt-evidence.json > agt-attestation.json
|
|
90
114
|
|
|
91
115
|
- name: Upload governance artifacts
|
|
92
|
-
uses: actions/upload-artifact@v7
|
|
116
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.0
|
|
93
117
|
with:
|
|
94
118
|
name: agt-governance-${{ github.sha }}
|
|
95
119
|
path: |
|
|
@@ -103,20 +127,26 @@ jobs:
|
|
|
103
127
|
if: github.ref == 'refs/heads/main'
|
|
104
128
|
|
|
105
129
|
steps:
|
|
106
|
-
- uses: actions/checkout@v7
|
|
130
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
107
131
|
|
|
108
|
-
- uses: actions/setup-python@v7
|
|
132
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
109
133
|
with:
|
|
110
134
|
python-version: "3.12"
|
|
111
135
|
|
|
112
136
|
- name: Install dependencies
|
|
113
|
-
|
|
137
|
+
# Third-party dependencies come from the hash-pinned lock first; the
|
|
138
|
+
# local package then goes in with --no-deps, because pip cannot
|
|
139
|
+
# hash-pin an editable install in the same invocation.
|
|
140
|
+
run: python -m pip install --require-hashes -r requirements/dev.txt
|
|
141
|
+
|
|
142
|
+
- name: Install local package
|
|
143
|
+
run: python -m pip install --no-deps -e .
|
|
114
144
|
|
|
115
145
|
- name: Run benchmark (software-only, CI gate p99 < 5ms)
|
|
116
146
|
run: python -m ca2a_runtime.benchmarks --provider software-only --hops 10000 --out benchmarks/
|
|
117
147
|
|
|
118
148
|
- name: Upload benchmark results
|
|
119
|
-
uses: actions/upload-artifact@v7
|
|
149
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.0
|
|
120
150
|
with:
|
|
121
151
|
name: benchmark-results
|
|
122
152
|
path: benchmarks/
|