Repository navigation
A2A SubscribeToTask allows cross-principal access to another task's live execution #1663
Description
Describe the Bug
SubscribeToTask does not enforce the same task ownership boundary as GetTask.
An authenticated principal that is not authorized to access a task can nevertheless subscribe to its active execution by providing the victim's TaskID.
In the reproduction, Alice creates an active task under principal alice. Bob is authenticated as a different principal, bob.
GetTask(AliceTask)correctly fails for Bob withtask not found.SubscribeToTask(AliceTask)nevertheless succeeds.- Bob receives a post-subscription marker, confirming that the unauthorized subscription is active.
- Only after that confirmation, Alice's task generates a fresh secret.
- Bob receives that newly generated secret through the live task stream.
This results in cross-principal disclosure of live task data.
Steps to Reproduce
- Start the ADK Go A2A web launcher using the local execution manager.
- Configure two independently authenticated principals:
aliceandbob. - Authenticate as
aliceand create an active A2A task. - Record the returned TaskID.
- Authenticate as
bob. - Call
GetTask(TaskID). - Observe that the request is rejected with
task not found. - Without changing Bob's authentication, call
SubscribeToTask(TaskID). - Observe that the subscription is accepted and the SSE stream is established.
- Wait for the post-subscription marker:
REAL-LAUNCHER-SUBSCRIPTION-MARKER - Only after that marker has been received by Bob, allow Alice's task execution to continue.
- Alice's task generates a fresh secret value.
- Observe that Bob receives the newly generated secret through the previously unauthorized subscription.
The reproduction does not rely on a fixed sleep or timing assumption: receipt of the marker explicitly confirms that the subscription was established before the secret was generated.
Expected Behavior
A principal that is not authorized to access task T should not be able to subscribe to T or receive subsequent task events.
SubscribeToTask(T) should enforce the same task ownership / authorization boundary as GetTask(T).
Observed Behavior
GetTask(T) rejects the non-owner principal with task not found, but SubscribeToTask(T) accepts the same principal.
The unauthorized principal then receives task events generated after the subscription was established, including a fresh secret generated only after the unauthorized subscription became active.
Environment Details
- ADK Library Version: main branch, commit
a61147276167f6b1b636d863f23a030c0a6babdf - A2A dependency:
github.com/a2aproject/a2a-go/v2 v2.5.0 - OS: Parrot Security 7.1 (echo), Linux amd64
- Go Version:
go1.24.4
Model Information
- Which model is being used: N/A
Regression
N/A
Logs
The reproduction was executed 3/3 times successfully.
Bob GetTask denied=true
Bob received post-subscription marker=true
Bob saw victim secret=true
--- PASS: TestRealWebLauncher_CrossUserSubscribeToTaskAuthorizationBypass
The same test also passed with the Go race detector.
Screenshots / Video
N/A
Additional Context
The behavior was reproduced against the real ADK Go A2A web launcher rather than only against a unit-level or synthetic server.
The affected serving path is the A2A server functionality exposed by google/adk-go.
The test explicitly synchronizes on receipt of a post-subscription marker before generating the victim secret, demonstrating that the disclosed data is generated after the unauthorized subscription has already been established.
The reproduction succeeded 3/3 times and also passed with the Go race detector.
Google VRP previously reviewed this behavior and decided not to track it through their security process. The Google Bug Hunter Team explicitly stated that the issue could be publicly disclosed on GitHub.
Minimal Reproduction Code
A deterministic reproduction is included in the attached test:
TestRealWebLauncher_CrossUserSubscribeToTaskAuthorizationBypass
The test:
starts the real ADK web launcher;
authenticates two distinct principals;
creates an active task as Alice;
verifies Bob is denied by GetTask;
subscribes Bob to Alice's task;
synchronizes on a post-subscription marker;
generates a fresh victim secret only after the unauthorized subscription is confirmed;
verifies Bob receives the secret.
How often has this issue occurred?
Always (100%)
[adk_go_vrp_554182774_followup_20260904.zip](https://github.com/user-attachments/files/32752022/adk_go_vrp_554182774_followup_20260904.zip)
Activity
Blinke182 commentedon Sep 28, 2026
Related A2A SDK behavior
There is a closely related authorization issue in the A2A Python SDK:
That fix was released in
a2a-python v1.1.4and specifically owner-scopesSubscribeToTaskandCancelTask. This is the same owner-isolation class of issue, although the implementation and code paths are different between the Python and Go SDKs.I have not found a dedicated public
a2a-goissue tracking this specificSubscribeToTaskownership gap.In the Go SDK path reproduced here, the authorization boundary is enforced for
GetTask, while the local execution/resubscription path can resolve the active execution byTaskIDwithout applying the equivalent owner check.The A2A specification also states that task-related operations, including Subscribe, must verify that the client has appropriate access rights.
I am happy to prepare a PR with a regression test and the corresponding authorization check if the maintainers would like the fix implemented in
a2a-goor in the ADK integration.I also checked the a2a-go v2.6.0 release notes (25 Sep 2026) and found no ownership fix for SubscribeToTask, so this appears to still be unaddressed in the latest release.
yarolegovich commentedon Sep 30, 2026
a2a sdk provides an API for authorization middleware and it's SDK users' responsibility to implement and attach it. at the very least this issue doesn't belong to the adk repository
Blinke182 commentedon Sep 30, 2026
Thanks for the response. To make sure I understand: in the reproduction, GetTask already returns task_not_found for Bob without any authorization middleware configured on my end — so some ownership scoping is applied by default on that path. Is that default scoping coming from a2a-go's local TaskStore? If so, wouldn't the same default scoping reasonably apply to SubscribeToTask/Resubscribe, or is that intentionally left entirely to middleware? If this is purely an a2a-go concern, should I move the report there, or is there a preferred place to track it?