Skip to content

feat(safety): classify run_background_job commands like shell - #4166

Merged
dgageot merged 3 commits into
docker:mainfrom
dgageot:feat/classify-background-job-commands
Sep 4, 2026
Merged

feat(safety): classify run_background_job commands like shell#4166
dgageot merged 3 commits into
docker:mainfrom
dgageot:feat/classify-background-job-commands

Conversation

@dgageot

@dgageot dgageot commented Sep 4, 2026

Copy link
Copy Markdown
Member

Previously, run_background_job was labelled from tool annotations alone, so its cmd argument was always treated as unknown. In balanced mode this meant every background job triggered a confirmation prompt regardless of how benign the command was; in restricted mode every job was denied outright. Worse, a destructive command like rm -rf /tmp/work launched in the background would lose its confirmation badge entirely, since no classifier ever inspected the actual command text.

This change brings run_background_job under the same pkg/safety classifier already used for shell. A new safety.IsCommandTool() predicate covers both tools, and LabelToolCall now classifies by command text for any command tool. Session "always allow" grants are tightened via commandGrantCoversCall: a shell:cmd=… grant no longer covers a background job and vice versa. BuildPermissionPattern emits run_background_job:cmd=<word>* for background jobs, and the deprecated safer_shell hook uses the same predicate.

The change also surfaces two pre-existing issues that would have widened to background jobs otherwise. First, safety.CommandArg now applies the same handler precedence — non-blank cmd wins, else the command alias — so the label always describes the command that actually runs. Second, handlers previously resolved args via encoding/json struct-tag matching, whose case-insensitive last-wins semantics let {"cmd":"ls","CMD":"rm -rf x"} execute a command the runtime had classified as safe; they now use an exact-key lookup over the same safety.CommandArg path. Permission rules and session grants are matched against a normalised view of the call args (call.permissionArgs()) that mirrors the executed command under cmd, so shell:cmd=sudo* deny rules cannot be bypassed with the command alias, and stored grants keep matching alias-style calls.

The net effect by safety mode:

Mode Before After
balanced always ask safe → run silently; destructive/unknown → ask with badge
restricted always deny safe → run; else deny
strict / autonomous unchanged unchanged

One known limitation is unchanged and out of scope: pressing T on a command-tool call with no extractable command still grants the whole tool, because hiding that option requires dialog layout work.

run_background_job's cmd now goes through the same safe/destructive/unknown
classifier as the shell tool. Session "always allow" grants are tool-scoped
for both command tools, and BuildPermissionPattern emits
`run_background_job:cmd=<word>*`.

Assisted-By: docker-agent
…nd granting

safety.CommandArg now applies the shell/background-job handlers' precedence (non-blank `cmd` wins, otherwise the `command` alias), so the safety label, the session-grant hardening and the "always allow" pattern all describe the command that actually runs. BuildPermissionPattern also honours the `command` alias instead of falling back to a whole-tool grant.

Assisted-By: Claude
shell and run_background_job now resolve cmd/command via
safety.CommandArg over an exact-key map instead of encoding/json struct
tags, whose case-insensitive last-wins matching let
{"cmd":"ls","CMD":"rm -rf x"} run a command the runtime never
classified.

Permission rules and session grants for command tools are matched
against args with the executed command mirrored under the canonical
"cmd" key, so a `shell:cmd=sudo*` deny can no longer be dodged with the
"command" alias and "always allow" grants keep matching alias calls.

Assisted-By: Claude
@dgageot
dgageot requested a review from a team as a code owner September 4, 2026 11:59
@aheritier aheritier added area/core Core agent runtime, session management area/docs Documentation changes kind/feat PR adds a new feature (maps to feat:). Use on PRs only. labels Sep 4, 2026
@dgageot
dgageot merged commit 7d3179a into docker:main Sep 4, 2026
21 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Core agent runtime, session management area/docs Documentation changes kind/feat PR adds a new feature (maps to feat:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants