Skip to content

fix(kanban): guard needs_input unblock from non-interactive sessions - #59906

Open
jeslyntsw wants to merge 2 commits into
NousResearch:mainfrom
jeslyntsw:kanban-unblock-needs-input-guard
Open

fix(kanban): guard needs_input unblock from non-interactive sessions#59906
jeslyntsw wants to merge 2 commits into
NousResearch:mainfrom
jeslyntsw:kanban-unblock-needs-input-guard

Conversation

@jeslyntsw

Copy link
Copy Markdown

Problem

A worker profile with the terminal tool can bypass the kanban_unblock model-tool guard (_check_kanban_orchestrator_mode checks HERMES_KANBAN_TASK) by calling hermes kanban unblock <task_id> as a shell command. The terminal tool has no such guard, so the CLI path is unrestricted.

Observed in production: a dispatched planner worker blocked a card with kind: needs_input (awaiting human review), then used terminal to call hermes kanban unblock via CLI, unblocking itself. The next run claimed the card and proceeded as if the human had approved, creating 6 downstream cards.

The model-tool path (tools/kanban_tools.py) is already guarded. This PR closes the CLI gap.

Fix

Add a DB-level guard in unblock_task() (hermes_cli/kanban_db.py) so needs_input blocks cannot be unblocked from non-interactive sessions. Detection: HERMES_KANBAN_TASK is set OR HERMES_PROFILE is set to a non-default profile. The guard fires before any status mutation, inside a write_txn so a raise rolls back cleanly (task stays blocked, block_kind preserved).

  • Scoped: only needs_input is gated. Other block kinds (capability, transient, review-required, dependency, untyped) are unaffected — workers can still unblock those.
  • Call paths covered:
    • CLI handler (hermes_cli/kanban.py) — catches ValueError, prints clear error, returns non-zero
    • Model tool (tools/kanban_tools.py) — catches at existing exception boundary, returns tool_error
    • Dashboard plugin (plugin_api.py) — does not catch, but dashboard runs as HERMES_PROFILE=default so the guard never fires there; uncaught → 500 is a safe fail-closed
  • Clear error: Refused: cannot unblock needs_input block from non-interactive session. This block requires human (Main/default) approval.

Files changed

File Change
hermes_cli/kanban_db.py _is_noninteractive_unblock_session() + guard inside unblock_task() before stale-run recovery / status mutation
hermes_cli/kanban.py _cmd_unblock catches ValueError, prints kanban error, marks task failed for handler-level nonzero return
tests/hermes_cli/test_kanban_block_kinds.py 19 tests: worker refusal, non-default-profile refusal, Main/default success, unset-env success, other block kinds unaffected, untyped block unaffected, CLI handler non-zero from worker, CLI handler zero from Main

Verification

  • uv run pytest tests/hermes_cli/test_kanban_block_kinds.py -q19 passed
  • CLI smoke with temp HERMES_HOME: worker env attempted hermes kanban unblock <needs_input> → refusal printed, task remained blocked needs_input; Main/default unblocked same task → ready needs_input

Non-blocking note

hermes_cli.main does not currently propagate subcommand return codes to the shell process exit status (pre-existing broader behavior). The _cmd_unblock handler returns non-zero correctly on refusal; the shell exit code may still read 0. The security guard itself works — mutation is prevented regardless. Separate CLI return-code cleanup if desired.

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cron Cron scheduler and job management labels Jul 7, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for tracing the CLI path; current main does have the gap: hermes_cli/kanban.py:2012 calls unblock_task() directly, while the model-tool gate is limited to tools/kanban_tools.py:82-93.

Problems

  • The proposed environment check is not an authorization boundary. Dispatcher workers receive mutable HERMES_KANBAN_TASK and HERMES_PROFILE variables in hermes_cli/kanban_db.py:8085-8165, while the board DB is independently pinned at hermes_cli/kanban_db.py:8154-8160. A terminal child can remove or replace the guard variables before invoking hermes kanban unblock, so this does not close the bypass.
  • Rejecting every non-default profile conflicts with the documented orchestrator surface: website/docs/user-guide/features/kanban.md:264-276 assigns kanban_unblock to orchestrators without a default-profile restriction, and tools/kanban_tools.py:82-93 permits configured non-task-scoped profiles.

Suggested changes

  • Establish a worker boundary that prevents terminal subprocesses from invoking Hermes against or writing to the pinned board DB, then add an E2E regression covering an environment-scrubbed CLI invocation.
  • Remove the profile-name test and cover a non-default orchestrator unblocking a needs_input task.

This is an automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
Detect dispatched workers from dispatcher-recorded worker PIDs and process ancestry, rather than trusting mutable environment variables alone. Allow non-task-scoped orchestrator profiles to unblock needs_input gates.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants