fix(kanban): enforce worker task ownership on kanban_link - #72309
Open
Frowtek wants to merge 1 commit into
Open
Conversation
NousResearch#19534 / NousResearch#19713 established that a dispatcher-spawned worker may only mutate run-lifecycle state on its own task, and wired _enforce_worker_task_ownership into kanban_complete / kanban_block / kanban_heartbeat / kanban_attach / kanban_attach_url / kanban_unblock. kanban_link was left ungated, and it mutates the child task: - link_tasks() demotes a `ready` child back to `todo` when the parent isn't done, so the dispatcher stops promoting it; - the child then inherits the parent's notify subscriptions. A task-scoped worker (or a prompt-injected one — the threat named in the guard's own docstring) can therefore point kanban_link at a sibling or cross-tenant task and both stall it indefinitely and redirect its terminal notifications to its own parent's subscribers. Observed on main: victim BEFORE status: ready | subs: [] kanban_link -> {"ok": true} victim AFTER status: todo | subs: ['ATTACKER-CHAT'] Gate the child end — the task whose state changes. Orchestrator profiles (no HERMES_KANBAN_TASK) keep unrestricted graph edits, a worker can still attach its own task to a parent, and follow-up work keeps its documented path via kanban_create(parents=[...]).
teknium1
reviewed
Jul 30, 2026
teknium1
left a comment
Contributor
There was a problem hiding this comment.
Thanks for the focused reproduction and the child-state/notification assertions. The current mutation path is real: tools/kanban_tools.py:1415-1428 forwards any supplied child to link_tasks, and hermes_cli/kanban_db.py:3423-3436 can demote that child and inherit notification subscriptions.
Problems
- This changes an explicit existing policy, not just an unguarded sibling. The original ownership fix,
d3b22b76d8, sayskanban_linkwas deliberately kept unrestricted; current regression guidance repeats that attests/tools/test_kanban_tools.py:558-564. - The updated happy-path test clears
HERMES_KANBAN_TASK, so it stops covering the current worker-scoped permissive contract instead of reconciling that policy change.
Suggested changes
- Please establish the intended graph-mutation policy with maintainers before changing this handler contract. Current documentation treats worker/orchestrator separation for graph edits as a convention (
website/docs/user-guide/features/kanban.md:341), and related PR #68029 proposes a configurable alternative. - If strict ownership is selected, update the policy comment and docs alongside the tests.
Automated hermes-sweeper review.
| ownership_err = _enforce_worker_task_ownership(str(child_id)) | ||
| if ownership_err: | ||
| return ownership_err | ||
| board = args.get("board") |
Contributor
There was a problem hiding this comment.
Current main deliberately keeps kanban_link unrestricted for task-scoped workers (d3b22b76d8; tests/tools/test_kanban_tools.py:558-564). This guard reverses that policy, so the worker graph-mutation direction needs maintainer agreement before changing the handler contract.
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.
What does this PR do?
#19534 / #19713 established that a dispatcher-spawned worker may only mutate run-lifecycle state on its own task, and wired
_enforce_worker_task_ownershipintokanban_complete,kanban_block,kanban_heartbeat,kanban_attach,kanban_attach_urlandkanban_unblock.kanban_linkwas left ungated — and it mutates the child task:So a task-scoped worker — or a prompt-injected one, which is the threat the guard's own docstring names — can pass any
child_idand:readytask is demoted totodo, so the dispatcher stops promoting it, for as long as the attacker's parent stays un-done;Reproduced against
main(759f68bc2) from a worker context (HERMES_KANBAN_TASKset to the attacker's own task):After this change the same call returns the standard scope refusal and the victim is untouched (
status: ready | subs: []).The gate goes on the child end — the task whose state actually changes. That keeps every legitimate flow working:
HERMES_KANBAN_TASK) still rewire the graph freely;kanban_create(parents=[...]), which the guard's own error message already points workers at.Note on related work: #68029 proposes
kanban.worker_graph_mutationsas an opt-in policy that hideskanban_create/kanban_linkfrom workers entirely; by its own summary "existing installs are unchanged by default". This PR is the narrower, on-by-default fix — it restores the ownership invariant the other destructive kanban tools already enforce, and remains correct with that policy either way.Related Issue
No separate issue filed. Completes the invariant from #19534 (fixed for the sibling tools in #19713).
Fixes #
Type of Change
Changes Made
tools/kanban_tools.py(_handle_link): call_enforce_worker_task_ownership(child_id)before connecting, mirroring the sibling handlers, with a comment recording why the child end is the gated one.tests/tools/test_kanban_tools.py: three new tests — a worker cannot link a foreignreadychild (asserting it staysreadyand inherits no subscriptions), a worker may still link its own task under a parent, and an orchestrator context can still link two foreign tasks.tests/tools/test_kanban_tools.py(test_link_happy_path): this existing test used theworker_envfixture only for its DB setup but incidentally ran withHERMES_KANBAN_TASKset while linking two unrelated tasks, which is now an orchestrator operation — it drops the worker scope the fixture also sets. Flagging the edit explicitly: unliketest_worker_can_comment_on_foreign_task, which documents its permissive policy and asks to fail CI if gated, this test states no intent about worker scope.How to Test
HERMES_KANBAN_TASK= its own task), callkanban_link(parent_id=<own task>, child_id=<another tenant's ready task>).ok: true, the victim task drops fromreadytotodo, andlist_notify_subs(victim)shows the attacker's chat. After, the call is refused and the victim is unchanged.pytest tests/tools/test_kanban_tools.py -q-> 123 passed (3 new). The cross-tenant test fails onmainwithout the code change (assert True is not True— the link succeeds).pytest tests/tools/test_kanban_tools.py tests/hermes_cli/test_kanban_core_functionality.py tests/gateway/test_kanban_notifier.py tests/hermes_cli/test_kanban_notify.py -q-> 321 passed, 8 failed; those 8 (protocol-violation streaks, stale detection) are pre-existing onmain— verified by re-running them with this change stashed (same 8).Checklist
Code
fix(scope):,feat(scope):, etc.)kanban_linkby defaultDocumentation & Housekeeping
docs/, docstrings) — N/A (restores the documented worker-scope rule; the refusal message already names the supported alternatives)cli-config.yaml.exampleif I added/changed config keys — N/A (no config keys added or changed)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/Akanban_linkschema is unchanged; the refusal surfaces at call time like the sibling tools' does