fix(a2a): enforce IBCT capability tokens on client and server request paths - #6289
Merged
Conversation
bug-ops
enabled auto-merge (squash)
July 14, 2026 17:56
bug-ops
disabled auto-merge
July 14, 2026 17:57
bug-ops
enabled auto-merge (squash)
July 14, 2026 17:57
… paths The IBCT (Invocation-Bound Capability Token) primitive was fully implemented, HMAC-signed, and unit-tested in isolation, but never wired into the live A2A request path. Server config documented it as an enforced authorization layer on top of the coarse bearer token, but nothing verified the X-Zeph-IBCT header and nothing issued it (CWE-862 Missing Authorization). Server: new ibct_middleware layered inside auth_middleware rejects /a2a and /a2a/stream with 401/403 when A2aServer::with_ibct_keys is configured; no-op when unconfigured. Daemon wires config.a2a.ibct_keys and ibct_signing_key_vault_ref (vault-resolved) into the builder. Client: A2aClient::with_ibct_key issues and attaches a token scoped to the request's task_id and to the target endpoint's origin (ibct_scope_origin strips the path, since /a2a and /a2a/stream are verified against the one pathless AgentCard::url). The same normalization is applied server-side to card.url so a non-canonical public_url can't silently reintroduce the mismatch. No bundled caller currently attaches the header, so this ships the enforcement primitive rather than a fully activated end-to-end control; corrected the four doc surfaces that overclaimed otherwise. Closes #6260
bug-ops
force-pushed
the
fix/6260-a2a-ibct-enforcement
branch
from
July 14, 2026 17:57
a123279 to
3372e60
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
zeph-a2a's IBCT (Invocation-Bound Capability Token) primitive was fully implemented and unit-tested in isolation, but never wired into the live A2A request path (CWE-862 Missing Authorization). Config documented it as an active per-task authorization layer on top of the coarse bearer token; it wasn't.ibct_middlewarelayered insideauth_middlewarerejects/a2a//a2a/streamwith 401 (missing/undecodable header) or 403 (signature/expiry/scope mismatch) wheneverA2aServer::with_ibct_keysis configured with a non-empty key set; no-op when unconfigured (matches the existing bearer-auth opt-in pattern).src/daemon.rsresolves[a2a] ibct_keys/ibct_signing_key_vault_ref(vault-resolved, hard-fails startup if the ref is set but unresolvable, per the spec invariant).A2aClient::with_ibct_keyissues and attaches a token scoped to the request'stask_idand to the target endpoint's origin (ibct_scope_originstrips the path, since/a2aand/a2a/streamare both verified against the one pathlessAgentCard::url). The same normalization is applied server-side tocard.urlso a non-canonicalpublic_urlcan't silently reintroduce the mismatch.A2aServerConfigdoc comments,crates/zeph-a2a/README.md,specs/014-a2a/spec.md,specs/010-security/spec.md) that overclaimed active enforcement, and added an explicit deployment caveat: no caller bundled in this repository currently attachesX-Zeph-IBCT, so this PR ships the enforcement primitive rather than a fully activated end-to-end control for the delegated-subagent scenario the issue describes.Review history
Went through a full
securitychain (reduced: developer → impl-critic → reviewer, audit lead-in skipped since the issue already specified exact locations/remediation):/a2aand/a2a/streamcould never both pass) and that no production caller attaches the header (S2) — both addressed, re-verified as fixed.card.urlwasn't normalized like the client's endpoint) plus missing mandatory testing-playbook/coverage-status entries — both addressed, re-reviewed and approved.Test plan
cargo nextest run -p zeph-a2a --features server,ibct,card-signing— 235/235 passedcargo nextest run -p zeph-a2a(ibct feature off) — 195/195 passed (backward compat)--profile ci, nextest, rustdoc gate) — all green, confirmed independently by reviewergitleaks protect --staged— no leakspathful_endpoint_scope_is_rejected,origin_scoped_token_accepted_on_both_routes,non_canonical_card_url_still_matches_client_issued_token,ibct_scope_origin_strips_explicit_default_port).local/testing/playbooks/a2a-ibct-enforcement.mdand.local/testing/coverage-status.mdupdated (main repo)Closes #6260