Repository navigation
fix(security): scope A2A task routes to their agent, and stop the ruff pin drifting - #6
Merged
Conversation
… hook The ruff-pre-commit hook pinned v0.8.6 while the dev dependency resolved to 0.15, and the two disagreed: on this tree the pinned version reports 34 lint errors and wants to reformat 9 files that the project's own ruff considers clean. Because the PR gate runs pre-commit over a PR's *changed files*, any PR touching one of those files failed on code it had not written — which happened twice on the streaming PR, once for formatting and once for UP038. Source ruff from ``uv run``, as the mypy hook already does, so there is one ruff: the one in uv.lock. Drift between the hook and the dependency stops being possible rather than being corrected and re-drifting. No source changes: the tree is already clean under the project's ruff (362 files formatted, no lint findings), so this is a tooling fix, not a reformat. The dev floor moves to ruff>=0.15 because the code genuinely no longer lints clean under 0.8, and claiming otherwise was misleading. Verified the hooks still bite: an unused import fails ruff, bad spacing fails ruff-format, and a clean file passes both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk5bxkw7Q3j8VN1TvxeQ5V
``GET /{agent}/a2a/tasks/{id}``, its ``/events`` and ``/cancel`` accepted any
task id regardless of which agent owned it. Zero-trust derives its policy
from the agent segment of the path, so a caller permitted on a permissive
agent could name a restricted agent's task on their own agent's route.
Verified before the fix, on a two-agent platform:
GET /api/v1/faq/a2a/tasks/<payroll id> -> 200, payload leaked
GET /api/v1/faq/a2a/tasks/<payroll id>/events -> 200, payload leaked
POST /api/v1/faq/a2a/tasks/<running id>/cancel -> 200, task cancelled
The cancel is the sharper end: a cross-agent *write* that stops another
agent's work. All three now answer 404 — the same answer as a nonexistent
id, so the response never confirms someone else's task is there.
TaskRecord already carries ``target``, so this is a local check on routes
that were already namespaced per agent. The shared task board at
``/api/v1/tasks/{id}`` is cross-agent by design and is deliberately
untouched; a test asserts it still works.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk5bxkw7Q3j8VN1TvxeQ5V
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.
The two items deferred out of #5, now that it has merged and a tooling change can't bury it.
1. Cross-agent access on the per-agent A2A task routes
GET /{agent}/a2a/tasks/{id}, its/eventsand/cancelaccepted any task id, regardless of which agent owned it. Zero-trust derives its policy purely from the agent segment of the path, so a caller permitted on a permissive agent could name a restricted agent's task on their own agent's route.Verified on a two-agent platform before the fix:
The cancel is the sharper end — a cross-agent write that stops another agent's work. (My first probe showed
409and I nearly wrote it off; that was only because the task had already finished. Re-testing against a running task showed it succeeding.)All three now answer
404— the same answer as a nonexistent id, so the response never confirms someone else's task exists.TaskRecordalready carriestarget, so this is a local check on routes that were already namespaced per agent. The shared task board at/api/v1/tasks/{id}is cross-agent by design and is deliberately untouched — a test asserts it still works.2. The ruff pin drift
.pre-commit-config.yamlpinnedruff v0.8.6while the dev dependency resolved to0.15.x. On this tree the pinned version reports 34 lint errors and wants to reformat 9 files that the project's own ruff considers completely clean. Because the gate runs pre-commit over a PR's changed files, any PR touching one of those files failed on code it hadn't written — which happened twice on #5.I had told you a bump would reformat the repo. That was backwards: the tree is already clean under ruff 0.15 (362/362 formatted, zero findings). The churn comes from staying on 0.8.6.
Rather than bump a number that will drift again, ruff now runs from
uv run— exactly as the mypy hook already does — so there is one ruff, the one inuv.lock. Drift stops being possible instead of being corrected and re-drifting.No source changes. The dev floor moves to
ruff>=0.15because the code genuinely no longer lints clean under 0.8, and claiming otherwise was misleading.Testing
ruff, bad spacing failsruff-format, a clean file passes both. (A hook that silently did nothing would be worse than the drift.)Gate:
ruff check,ruff format --check,mypy(230 files),mkdocs build --strictclean; 2746 passed.The one pre-existing failure is unrelated and untouched:
test_save_read_only_storage_returns_actionable_conflictchmods a directory read-only, which root ignores. It passes on CI's non-root runner.🤖 Generated with Claude Code
https://claude.ai/code/session_01Pk5bxkw7Q3j8VN1TvxeQ5V
Generated by Claude Code