Skip to content

A2A SubscribeToTask allows cross-principal access to another task's live execution #1663

Closed

Description

@Blinke182

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 with task 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

  1. Start the ADK Go A2A web launcher using the local execution manager.
  2. Configure two independently authenticated principals: alice and bob.
  3. Authenticate as alice and create an active A2A task.
  4. Record the returned TaskID.
  5. Authenticate as bob.
  6. Call GetTask(TaskID).
  7. Observe that the request is rejected with task not found.
  8. Without changing Bob's authentication, call SubscribeToTask(TaskID).
  9. Observe that the subscription is accepted and the SSE stream is established.
  10. Wait for the post-subscription marker:
    REAL-LAUNCHER-SUBSCRIPTION-MARKER
  11. Only after that marker has been received by Bob, allow Alice's task execution to continue.
  12. Alice's task generates a fresh secret value.
  13. 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

  1. Blinke182 commented on Sep 28, 2026

    @Blinke182
    Author

    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.4 and specifically owner-scopes SubscribeToTask and CancelTask. 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-go issue tracking this specific SubscribeToTask ownership 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 by TaskID without 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-go or 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.

  2. yarolegovich commented on Sep 30, 2026

    @yarolegovich
    Member

    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

  3. Blinke182 commented on Sep 30, 2026

    @Blinke182
    Author

    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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions