README
¶
ASB A2A Security Test Kit
This directory contains the candidate self-contained ASB binding tester. It runs the security roles as separate operating-system processes and exercises the A2A 1.0 HTTP+JSON Send Message surface. The same binary can run natively, as six role-isolated containers, or as explicit roles on separate hosts.
The tester checks ASB bindings. It is not a general A2A conformance suite, and the bundled tests use the ASB reference roles. Another Agent B implementation can be connected through the multi-host adapter contract below, but no independent-vendor run is claimed by this repository.
The no-flag behavior remains the original v1 profile. An independent,
experimental draft06-v2 profile is available for exercising the repository's
non-normative draft-06 interpretation without changing the v1 wire contract.
Manager ──signed grant──────────────┐
Attester ──evidence──> Verifier ────┼──> Agent A == mTLS 1.3/A2A ==> Agent B
│ │
└──session-bound result───────┘
Replay Store <┘
Agent A first reads Agent B's Agent Card, obtains a fixed-policy Manager grant,
and opens the exact TLS connection used for POST /message:send. The TLS 1.3
exporter, canonical A2A request context, Agent A certificate, and attestation
evidence are bound together. Agent B verifies the grant, Agent signature,
attestation result, local policy, and durable one-shot replay record before
returning a completed A2A task.
The A2A portion of the application surface is an interoperable subset of the official A2A 1.0 HTTP+JSON binding:
GET /.well-known/agent-card.jsonPOST /message:sendContent-Type: application/a2a+jsonA2A-Version: 1.0A2A-Extensionsselecting both required security-extension URIs- Agent Card
mutualTLSsecurity scheme - v1 required extension URIs
urn:agents-secure-binding:security-binding:v1andurn:agents-secure-binding:attestation-result:v1 draft06-v2required extension URIsurn:agents-secure-binding:security-binding:v2andurn:agents-secure-binding:attestation-result:v2
The v2 challenge path, POST /extensions/agents-secure-binding/v2/challenges,
is repository-local rather than an A2A endpoint.
The A2A 1.0.1 specification describes the protocol binding. Its Protocol Buffers schema is the normative data-model source. This demo deliberately implements only the Agent Card and Send Message subset; it does not claim full A2A conformance.
Build and run Direct-Agent v1
From the repository root:
make a2a-test
./build/asb-a2a-test
The default self-test runs eight scenarios:
ASB-A2A-001: accept a correctly bound new message;ASB-A2A-002: reject a tampered attestation result at Agent B;ASB-A2A-003: reject an unknown client-supplied Task ID;ASB-A2A-004: reject an expired session proof;ASB-A2A-005: reject replay of a consumed proof;ASB-A2A-006: reject credentials moved to another TLS session;ASB-A2A-007: reject a resource change made after binding; andASB-A2A-008: reject an A2A version downgrade.
Use JSON on standard output or write a report file:
./build/asb-a2a-test --format json
./build/asb-a2a-test --report ./asb-a2a-report.json
The report follows
schemas/a2a-security-test-report-v1.schema.json.
It records whether simulation or a requested hardware platform was used. The
command exits with zero only when all expected decisions are observed.
The source form remains available:
go run ./examples/a2a-multiprocess
Mac and local debug mode
On a Mac or another local development machine without confidential-computing hardware, run:
make mac-debug-a2a
The target builds the tester and runs the same explicit command:
make a2a-test
./build/asb-a2a-test --debug-simple
With no workflow override, this mode starts the reference roles on loopback and
runs the existing eight-scenario security-test suite. It still uses mutual TLS
and checks the exact session/request binding, the signed attestation result,
local policy, and one-shot replay state. Its Attester signs demo evidence
labeled SIMULATED. That signature detects tampering within the demo trust
setup; it is not an SNP, TDX, TPM, or vTPM quote and is not evidence of
confidential execution.
To use two OpenAI-compatible model servers on loopback, create prompt.txt and
run, for example:
./build/asb-a2a-test --debug-simple \
--workflow llm-conversation \
--prompt-file ./prompt.txt \
--agent-a-llm-url http://127.0.0.1:11434 \
--agent-a-llm-model model-a \
--agent-b-llm-url http://127.0.0.1:11434 \
--agent-b-llm-model model-b
Replace the model labels and ports as needed. In --debug-simple mode, both
model URLs must use cleartext HTTP and a loopback hostname. The runtime disables
proxy use and rejects any resolved address outside loopback. This workflow
makes one ASB-bound request from Agent A to Agent B. The reply is authenticated
by its TLS connection but is not a separately ASB-bound reverse-direction
request.
By default, the demo PKI, role keys, and file-backed state are placed in a
temporary directory and removed after the run. If --state-dir is supplied,
that test material remains there for inspection. It is not production identity,
enrollment, or a durable replay service. Do not supply production data or
credentials. The mode refuses multi-host deployment and role URLs, hardware
options, and the Redis/Valkey acceptance store. A successful run does not
establish hardware qualification or production readiness.
Run two selected LLMs
The llm-conversation workflow uses two OpenAI-compatible
Chat Completions
endpoints. When a provider requires authentication, set its API key in
ASB_AGENT_A_LLM_API_KEY or ASB_AGENT_B_LLM_API_KEY with your shell or
secret manager. A keyless local runtime may leave its variable unset. Then run:
./build/asb-a2a-test \
--workflow llm-conversation \
--prompt-file ./prompt.txt \
--agent-a-llm-url https://provider-a.example \
--agent-a-llm-model model-a \
--agent-b-llm-url https://provider-b.example \
--agent-b-llm-model model-b
The prompt goes to Agent A's model. Its output becomes an ASB-bound A2A request. Agent B completes the normal transport, grant, session-binding, attestation, authorization, and replay checks before calling its model. The reply returns as a TLS-authenticated A2A text artifact.
HTTPS is required. HTTP is accepted only for a local loopback endpoint when
--allow-insecure-llm-loopback is set. The API key environment variable names
can be changed with --agent-a-api-key-env and --agent-b-api-key-env; they
must start with ASB_ and remain different. The orchestrator passes each key
only to its matching Agent child. Key values are not put in CLI arguments,
reports, or logs.
With the default text format, stdout shows the Agent A request and Agent B
reply. JSON reports contain only the workflow decision. This workflow supports
both Direct-Agent v1 and the experimental draft06-v2 profile, makes one round
trip, and does not retry model calls. Select v2 with:
./build/asb-a2a-test \
--binding-profile draft06-v2 \
--workflow llm-conversation \
--prompt-file ./prompt.txt \
--agent-a-llm-url https://provider-a.example \
--agent-a-llm-model model-a \
--agent-b-llm-url https://provider-b.example \
--agent-b-llm-model model-b
Model IDs are configured labels, not proof of model-weight provenance. Agent B's artifact is authenticated by the return TLS connection; it is not a separately ASB-signed reverse-direction message. ASB does not establish the truth or safety of model-generated text.
The separate draft06-v2 profile remains experimental:
go run ./examples/a2a-multiprocess --binding-profile draft06-v2
In that profile, Agent B issues a 32-byte, single-use verifier_nonce and a
16-byte attempt_id. Agent A obtains the challenge and sends the bound A2A
request on the same completed, non-resumed TLS 1.3 connection. The profile
constructs separate task and target contexts, uses sbaip_context_v2, binds
attestation to the accepted endpoint SPKI and 32-byte TLS exporter, evaluates
D6 target matching separately from D7 authorization, and commits durable
replay state only after the other acceptance checks succeed. The replay service
also reserves a stable operation ID and request digest in the same update.
Agent B records RUNNING before invoking the model or demo action, then records
SUCCEEDED and an encrypted copy of the exact A2A response in one store update.
The encryption key remains with Agent B. If the HTTP response is lost, a fresh,
exactly bound request retrieves that response without invoking the model again.
An execution error is recorded as INDETERMINATE; RUNNING and INDETERMINATE do
not permit automatic re-execution.
The orchestrator bootstraps an ephemeral CA and role-specific keys, starts five servers as child processes, then starts Agent A as a sixth process.
The draft06-v2 security-test run reports one accepted request and ten blocked
cases: nonce reuse after a task-context change, a challenge borrowed by another
TLS connection, target and operation substitutions, wrong endpoint role and
interaction type, absent exporter binding, a reserialized-grant digest, and
missing attestation binder or result. The summary is 11/11; see the
experimental v2 profile for the unit-level
checks and their limits. Text and JSON reports are available for both profiles.
Only decisions and process endpoints are logged. JWTs, evidence, private keys, raw replay keys, and plaintext responses are not logged. The replay service stores a SHA-256 digest of each one-shot key and opaque encrypted responses; state is committed with an atomic rename.
For reproducible review, the full-wire fixture fixes the HTTP request, public-key JWS inputs, canonical contexts, binding hashes, and expected Accepted Assertion. The separate Python vector verifier rebuilds those values and uses OpenSSL to check all three ES256 signatures. It starts from recorded TLS outputs and is not a live multi-host test.
Run the reference roles on separate hosts
testdata/multihost-deployment.example.json
is the deployment input. Copy it and replace the example origins with names or
IP addresses reachable from their clients. Agent A must reach Manager,
Attester, Verifier, and Agent B; Agent B must reach Replay. Each server needs a
distinct, path-free HTTPS origin and an explicit non-loopback listen address.
The file also fixes the binding profile and attestation mode for every role.
Create the role bundles on an offline setup host:
./build/asb-a2a-test \
--role bootstrap \
--state-dir ./asb-multihost-state \
--deployment-config ./deployment.json
Multi-host bootstrap refuses a non-empty state directory. It creates a
24-hour test CA, a TLS certificate whose SAN matches each configured origin,
and the existing role signing relationships. It also writes
asb-multihost-state/multihost-trust.json. That manifest contains endpoints,
certificate fingerprints, and public-key fingerprints. It contains no private
key or credential secret.
Distribute only the directory needed by each role. For example, the Manager
host receives manager/, while the Agent A host receives agent-a/. Place the
role directory under the path passed as --state-dir; do not copy the complete
bootstrap directory to an online host. Copy deployment.json to every host and
copy multihost-trust.json to Agent A for the run record.
Start the five servers on their assigned hosts. The deployment file supplies their listen addresses and service URLs:
./build/asb-a2a-test --role replay --state-dir /var/lib/asb --deployment-config /etc/asb/deployment.json
./build/asb-a2a-test --role manager --state-dir /var/lib/asb --deployment-config /etc/asb/deployment.json
./build/asb-a2a-test --role attester --state-dir /var/lib/asb --deployment-config /etc/asb/deployment.json
./build/asb-a2a-test --role verifier --state-dir /var/lib/asb --deployment-config /etc/asb/deployment.json
./build/asb-a2a-test --role agent-b --state-dir /var/lib/asb --deployment-config /etc/asb/deployment.json
Then run Agent A from another host:
./build/asb-a2a-test \
--role agent-a \
--state-dir /var/lib/asb \
--deployment-config /etc/asb/deployment.json \
--trust-manifest /etc/asb/multihost-trust.json \
--format json \
--report ./asb-multihost-report.json \
--deployment-evidence ./asb-multihost-evidence.json
The ordinary report is marked target because Agent A did not start the other
roles. The separate evidence file links the exact deployment file, non-secret
trust manifest, and report by SHA-256. It records the configured origins and the
result status, but not grants, proofs, nonces, private keys, API keys, request
text, or response text. Both output files are owner-readable only.
Verify the links later, without credentials or network access:
./build/asb-a2a-test \
--role verify-evidence \
--deployment-config ./deployment.json \
--trust-manifest ./multihost-trust.json \
--report ./asb-multihost-report.json \
--deployment-evidence ./asb-multihost-evidence.json
The hashes detect a changed input. The evidence file is not signed, so retain or sign the four files with the operator's normal audit system when origin and custody must also be proved.
The evidence shows that a run succeeded through the configured, hostname- verified mTLS origins. A DNS name is not proof that services ran on different physical machines. The file therefore states that physical separation, independent-vendor interoperability, multi-replica behavior, and full A2A conformance were not established.
Agent B adapter entry point
An alternate Agent B can use the generated agent-b/ credential bundle. A
custom PKI needs a matching trust manifest. The implementation must expose the
health check, Agent Card, v2 challenge endpoint, and A2A Send Message endpoint
used by this test. The exact request and expected binding projection are fixed
by testdata/draft06-v2-wire.json.
The Python verifier is a second
implementation of the recorded fixture checks.
This is an adapter boundary, not independent-vendor evidence. Such a claim requires a separately maintained implementation and an actual recorded run. The optional Redis/Valkey acceptance backend coordinates Agent B replicas, but the recorded fixture does not prove a multi-replica deployment. The challenge store also remains process-local.
Shared acceptance store
The replay role uses an owner-only file by default. Select the shared backend
explicitly with --acceptance-store redis, a TLS address, server name, CA file,
and --redis-password-env. The password is read from that environment variable
only by the replay process; it is never a command-line argument.
Redis/Valkey Lua scripts commit replay plus operation acceptance and successful
state plus sealed result as single primary commands. Operation and result keys
do not expire: use noeviction and do not delete them without a reviewed
archival policy. WAIT acknowledgements can be required with
--redis-replica-acks and --redis-replication-timeout, but they do not make
failover zero-loss. A caller that loses a response must look up the exact
operation before taking further action. The configured address must route to
the writable primary; this small adapter does not perform Sentinel discovery or
follow Redis Cluster redirects.
Run with Docker Compose
docker compose -f examples/a2a-multiprocess/compose.yaml \
up --build --abort-on-container-exit --exit-code-from agent-a
Select the draft-06 profile for all relevant containers with:
BINDING_PROFILE=draft06-v2 \
docker compose -f examples/a2a-multiprocess/compose.yaml \
up --build --abort-on-container-exit --exit-code-from agent-a
Manager, Attester, Verifier, Replay Store, Agent A, and Agent B have separate containers and separate named credential volumes. The bootstrap container is the only component that initially mounts every credential volume. Remove the ephemeral demo PKI and replay state after the run with:
docker compose -f examples/a2a-multiprocess/compose.yaml down -v
Try hardware attestation
Hardware mode is fail-closed. It requires a Linux confidential guest, an SNP or TDX guest device, network access from the Verifier to the AMD KDS or Intel PCS, and the exact expected 48-byte launch measurement. For example:
ATTESTATION_PLATFORM=snp \
ATTESTATION_DEVICE=/dev/sev-guest \
EXPECTED_MEASUREMENT_HEX=<96-hex-characters> \
docker compose \
-f examples/a2a-multiprocess/compose.yaml \
-f examples/a2a-multiprocess/compose.hardware.yaml \
up --build --abort-on-container-exit --exit-code-from agent-a
Use ATTESTATION_PLATFORM=tdx and the guest's TDX device path for TDX. The
Verifier checks the evidence signature and certificate chain, revocation data,
the 64-byte report data derived from the live TLS binder, and the configured
measurement. Missing devices, collateral, measurements, or verification steps
cause rejection. The hardware override runs only the Attester container as
root (with all Linux capabilities dropped) so it can open the mapped device.
Canonical request context
The context input to the TLS exporter and Session Binding Statement is:
A2A/1.0\nPOST\n/message:send\n<canonical JSON request>
The canonical JSON is Go's deterministic JSON encoding of the supported Send Message structure after removing the two security-extension payload values. The extension URI list remains covered. Omitting the payload values avoids a circular dependency because those values contain the resulting hashes and signatures. Application fields, task and context IDs, part metadata, resource, operation, output modes, and extension selection remain covered.
The draft06-v2 path does not use this v1 JSON construction. It length-prefixes
fixed fields under ASB-A2A-TASK-v2 and ASB-A2A-TARGET-v2, then supplies both
byte strings independently to sbaip_context_v2. The strict v2 decoder rejects
duplicate members (including escaped duplicates), member-name aliases, unknown
members, invalid UTF-8, U+FFFD, unsupported metadata, and whitespace-normalized
resource or operation substitutions.
Scope boundaries
-
draft06-v2is experimental and non-normative. Passing this demonstration or its negative tests is not a claim of conformance to an Internet-Draft, A2A as a whole, or a production attestation deployment. -
Simulation evidence is signed by a dedicated demo Attester key, is labeled
SIMULATED, and is accepted only with Agent B's explicit--allow-simulationpolicy. -
Bootstrap creates a short-lived local CA; it is not online enrollment, production certificate issuance, CRL, or OCSP.
-
The demo uses a fixed application policy and synthetic document reference. Consent text, retention policy, real data, and external application adapters remain deployment responsibilities.
-
The Compose topology is single-replica and does not exercise the optional Redis/Valkey shared store or model a TLS-terminating proxy. Hardware appraisal needs deployment-specific collateral and launch measurement management.
-
Agent B owns the result-sealing key. Replicas need the same protected key; rotation and encrypted-result retention are not automated by this demo.
Documentation
¶
There is no documentation for this package.
Source Files
¶
- agent_a.go
- agent_a_response_v2.go
- agent_a_v2.go
- agent_b.go
- agent_b_v2.go
- attester.go
- challenge_v2.go
- conversation.go
- conversation_options.go
- crypto.go
- debug_simple.go
- http_helpers.go
- main.go
- manager.go
- model.go
- multi_host.go
- multi_host_pki.go
- operation_acceptance_v2.go
- orchestrator.go
- pki.go
- protocol.go
- protocol_v2.go
- replay.go
- report.go
- result_recovery_v2.go
- transport.go
- verifier.go