feat(security): screen rewritten commands and scrub child credentials in the bash tool - #229
Open
raymondginger2018-sudo wants to merge 1 commit into
Open
Conversation
… in the bash tool The native bash tool executed whatever command text it was handed and passed it the full process environment. Both are reachable by an intermediary between the client and the model provider: a relay, gateway, or any OpenAI-compatible proxy terminates TLS by design and can rewrite a tool call on its way back, or just read the request body. Two changes, both fail-closed and both waivable by name: * core.harness.command_guard gains screen_egress() and screen_install() beside the existing screen_command(), plus screen_all() to run them in order. screen_egress flags a fetch piped into an interpreter and any host outside an opt-in allow-list. screen_install flags an install from a non-canonical index and a package name one edit from a declared dependency (Damerau-Levenshtein, so the classic transposition counts as one edit). * core.harness.env_sanitize lifts the credential-shaped environment scrub out of core.harness.agents.external_backend, which already used it for spawned agent CLIs, so every child that can echo its environment uses it too. Both are applied in BashTool; the scrub also covers hook commands and the code-mode runtime. DEEPCODE_ALLOW_REMOTE_SCRIPT=1, DEEPCODE_BASH_FULL_ENV=1 and DEEPCODE_COMMAND_SCREEN=0 waive the respective check. Neither screen is the security boundary - the workspace sandbox is. They are cheap first passes that fail closed on shapes recognisable by inspection, and the docstrings say so. Tests: tests/test_command_guard_egress.py (50), tests/test_env_sanitize.py (35), plus screen cases in tests/test_shell_search_tools.py. Refs HKUDS#128, and HKUDS#228 which applies the same screen to the other call site.
Contributor
Author
|
@Zongwei9888 requesting security review per CONTRIBUTING.md. Two housekeeping notes from the fork side, so they are not mistaken for a missing checklist:
Scope note. #228 applies the same Size note. 1101 insertions across 9 files is above the usual split threshold. It is one file family ( |
This was referenced Sep 12, 2026
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.
Summary
The native bash tool (
core/harness/tools/shell.py) executed whatever command text it was handed, and passed that child the full process environment. Both are reachable by an intermediary between the client and the model provider: a relay, gateway, or any OpenAI-compatible proxy terminates TLS by design, so it can rewrite a tool call on its way back - or simply read the request body.Two fail-closed, explicitly waivable changes:
screen_egress()/screen_install()/screen_all()join the existingscreen_command()incore/harness/command_guard.py.screen_egressflags a fetch piped into an interpreter and any host outside an opt-in allow-list.screen_installflags an install from a non-canonical index and a package name one edit away from a declared dependency - Damerau-Levenshtein, sorequests->reqeustscounts as one edit.core/harness/env_sanitize.pylifts the credential-shaped environment scrub out ofcore/harness/agents/external_backend.py(which already used it for spawned agent CLIs) so every child that can echo its environment uses it too.Both are applied in
BashTool; the scrub also covers hook commands and the code-mode runtime.Changes
core/harness/env_sanitize.pycore/harness/command_guard.pyscreen_egress,screen_install,screen_all,find_confusablescore/harness/tools/shell.pyscreen_allbefore spawning; passes the scrubbed envcore/harness/agents/external_backend.pycore/harness/hooks/execution.py,core/harness/code_mode/tool.pySecurity Considerations
Attack surface changed. Two paths that previously ran unscreened now fail closed: (a) a shell command whose text may not have been written by the model, and (b) the environment handed to any child process the agent spawns.
Why this is fail-safe.
screen_egressonly rejects a blocked host and the pipe-to-interpreter shape;screen_installonly rejects a non-canonical index or a name one edit from a declared dependency.DEEPCODE_ALLOW_REMOTE_SCRIPT=1,DEEPCODE_COMMAND_SCREEN=0,DEEPCODE_BASH_FULL_ENV=1.screen_commandis unchanged; its existing behaviour and tests are untouched.PATH,HOME, locale and proxy variables, so children run normally; a caller that needs a specific variable forwards it explicitly, and the merge happens after the scrub.What this is not. Neither screen is the execution boundary - the workspace sandbox is.
screen_egressin particular is a coarse filter: an attacker who can host the payload on an allow-listed domain, or drop a stager locally and run it through an innocuous command, walks straight through. The docstrings say this explicitly so the code is not mistaken for a guarantee.Security verification.
tests/test_command_guard_egress.py(50 cases) - canonical pipe-to-interpreter payloads, benign fetches that must NOT fire, allow-list opt-in semantics, blocked-host enforcement, the transposition typosquat, non-canonical indexes, and every waiver path.tests/test_env_sanitize.py(35 cases) - the credential names this repo's own.envdefines are all matched and dropped;PATH/HOMEsurvive; explicit forwarding wins; both waiver paths.tests/test_shell_search_tools.py- refusal happens before the process is spawned, and the pre-existing destructive-command path still works.Tests
Verified under the repository's own
pyproject.tomlpytest config;ruff checkandruff formatclean against the pinned ruff.Notes
command_guard/shell/env_sanitize) serving one intent - "the default bash tool must not execute relay-rewritten commands or hand its credentials to children" - and the two features share hunks insideshell.py. Happy to split if you would prefer.tools/command_executor.py; this covers the native bash tool, a separate call site.--no-verifybecause the localpre-commitframework needs/bin/bash, which this Windows checkout does not have.ruff check/ruff formatwere run directly and are clean.