Skip to content

[DRAFT][Kiro] remediate PR #387 review — A2A hardened (PR-B shape) - #13

Closed
plauzy wants to merge 1 commit into
feat/agentic-protocols-generative-uifrom
kiro/pr387-a2a-hardened
Closed

plauzy wants to merge 1 commit into
feat/agentic-protocols-generative-uifrom
kiro/pr387-a2a-hardened

Conversation

@plauzy

@plauzy plauzy commented Jul 6, 2026

Copy link
Copy Markdown
Owner

This pull request was created by @kiro-agent on behalf of @plauzy 👻

Comment with /kiro fix to address specific feedback or /kiro all to address everything.
Learn about Kiro Web


Fork-internal review artifact. Base is feat/agentic-protocols-generative-ui (the PR awslabs#387 head), so this diff reads as "what the A2A remediation changed." Kiro's counterpart to claude/pr387-a2a-hardened for the head-to-head comparison. Please mark Draft in the UI (the tooling couldn't set it).

What this addresses

The two blocking findings from review #pullrequestreview-4632216702, both on the bundled A2A transport.

1. Auth bypass → per-method scope enforcement

  • a2a/rpc.py — authenticate + authorize per method before dispatch. Missing/invalid bearer → 401 UNAUTHENTICATED; valid token lacking scope → 403 PERMISSION_DENIED. task.send/task.cancel require cao:write, task.get requires cao:read (cao:admin satisfies any; write implies read). JSON-RPC error bodies with the matching HTTP status — not tunnelled through 200. Truthful docstring replacing the false "enforced via JWKS".
  • a2a/stream.py — SSE + REST reads gated by Depends(require_any_scope(read/write/admin)).
  • api/main.py — _should_mount_a2a() fail-closed guard: the transport is not mounted on a non-loopback bind while auth is disabled (the read-only Agent Card doc still publishes). Loopback + no-auth stays allowed for local dev.

2. Unbounded-store DoS → bounded task store

  • a2a/store.py — InMemoryTaskStore is now capped (CAO_A2A_MAX_TASKS, default 1000) and TTL-evicting (CAO_A2A_TASK_TTL, default 3600s). Lazy sweep under the lock; evict oldest terminal first; a full store of live tasks refuses task.send with RESOURCE_EXHAUSTED (429) rather than growing without limit.
  • a2a/types.py — add PERMISSION_DENIED (5) + RESOURCE_EXHAUSTED (6).

Tests (test-first)

  • test/a2a/test_auth.py — 28 tests: per-method matrix (send/get/cancel × {no token, malformed, cao:read, cao:write} → 401/403/200), stream+REST route auth, parametrized mount-guard decision, bounded-store evict/reject/TTL, and send-when-full → 429.
  • test/e2e/test_a2a_roundtrip.py: authenticated roundtrip variant (e2e; tmux-gated in the sandbox, runs in CI). The identical auth code path is fully exercised by test_auth.py via TestClient.

Gates (local)

  • black --check clean · isort clean · mypy src/ Success (142 files)
  • pytest -m 'not e2e': 3596 passed / 21 skipped (incl. the 28 new + 92 existing A2A/agent-card). One unrelated fifo_reader thread-timing test flakes under xdist and passes in isolation.
  • Default-off preserved: with the auth layer off, no token is required and existing behavior is byte-identical.

Scope note

authlib/python-multipart are not imported by src/ (the Agent Card signer uses cryptography via pyjwt[crypto]), so no [a2a] runtime extra was invented — that would be a false claim. Dependency hygiene (authlib→dev, drop multipart) rides the AG-UI core branch. (For contrast, claude/pr387-a2a-hardened folds that dep hygiene into this branch — a legitimate difference to weigh in the comparison.)

…#387 review, PR-B)

Addresses the two blocking findings from the PR awslabs#387 review on the bundled
A2A transport, on a branch stacked on the PR head (f40933d).

Auth bypass (blocking):
- a2a/rpc.py: authenticate + authorize per method before dispatch. Missing/
  invalid bearer -> 401 UNAUTHENTICATED; valid token without the required
  scope -> 403 PERMISSION_DENIED. task.send/task.cancel require cao:write,
  task.get requires cao:read (cao:admin satisfies any; write implies read).
  Errors are JSON-RPC bodies with the matching HTTP status (not tunnelled
  through 200). Truthful docstring replacing the false 'enforced via JWKS'.
- a2a/stream.py: SSE + REST reads gated by Depends(require_any_scope(read...)).
- api/main.py: _should_mount_a2a() fail-closed guard — the transport is not
  mounted on a non-loopback bind while auth is disabled (Agent Card discovery
  still publishes). Loopback + no-auth stays allowed for local dev.

Unbounded-store DoS (blocking):
- a2a/store.py: InMemoryTaskStore is now bounded (CAO_A2A_MAX_TASKS, default
  1000) and TTL-evicting (CAO_A2A_TASK_TTL, default 3600s). Lazy sweep under
  the lock; evict oldest terminal first; a full store of live tasks refuses
  task.send with RESOURCE_EXHAUSTED (429) instead of growing without limit.
- a2a/types.py: add PERMISSION_DENIED (5) + RESOURCE_EXHAUSTED (6).

Tests (test-first): test/a2a/test_auth.py (28) — per-method matrix
(send/get/cancel x {no token, malformed, cao:read, cao:write} -> 401/403/200),
stream/REST route auth, parametrized mount-guard decision, bounded-store
evict/reject/TTL, and send-when-full -> 429. test/e2e/test_a2a_roundtrip.py:
authenticated roundtrip variant (e2e; tmux-gated in this sandbox, runs in CI).

Docs: CHANGELOG A2A bullet updated; docs/auth.md 'A2A transport authorization'
section + 401->token->200 curl walkthrough.

Default-off unchanged: with the auth layer off no token is required and the
existing 92 A2A/agent-card tests pass. Gates: black/isort clean, mypy Success
(142 files), 3596 passed / 21 skipped.

Note: authlib/python-multipart are NOT imported by src (signing uses
cryptography via pyjwt[crypto]); no [a2a] runtime extra was invented. Dep
hygiene rides the AG-UI core branch.

Fork-only review branch; base feat/agentic-protocols-generative-ui.

Co-authored-by: Kiro Agent <244629292+kiro-agent@users.noreply.github.com>
@plauzy

plauzy commented Sep 28, 2026

Copy link
Copy Markdown
Owner Author

Closing as superseded in-fork by #25. This was the earliest A2A draft and has no agent_card/ package at all, so it cannot carry the signed Agent Card the hardened line is named for. Its six symbols absent from #25 are all private helpers reimplemented in #25's reconciled design, and all 21 cases from its test/a2a/test_auth.py were carried into #25's test/a2a/test_store_bounds.py and test/api/test_a2a_mount_guard.py under clearer names. Its docs/auth.md — the largest of the fork's five variants — has been archived to disk. Branch preserved.

@plauzy plauzy closed this Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants