Fix credential leakage to subprocesses and sandbox children - #73153
Fix credential leakage to subprocesses and sandbox children#73153praneshnikhar wants to merge 1 commit into
Conversation
14584b4 to
07fd73d
Compare
Bryntly
left a comment
There was a problem hiding this comment.
Code Review
1. Missing Tests (Regression Risk)
The PR introduces critical security filtering in agent/secret_sources/command.py, agent/secret_sources/bitwarden.py, and tools/code_execution_tool.py, but the diff contains no corresponding unit tests. We need tests verifying that _helper_child_env, _bws_child_env, and _scrub_child_env correctly drop mock credentials while preserving allowlisted variables to prevent future regressions.
2. Potential False Positives for "PASS" (Production Risk)
In tools/code_execution_tool.py, adding "PASS" to _SECRET_SUBSTRINGS will drop any environment variable containing "PASS". The removed comment correctly noted this false-positives on legitimate variables like BYPASS_CACHE, COMPASS_DIR, or PASSENGER_HOST. While they may not exist in this codebase, they may be injected by the user's host environment or infrastructure. Is catching DB_PASS worth breaking those user variables? Consider using more specific patterns like _PASS or PASS_ rather than a blanket substring match.
3. Scope Creep / Unrelated Changes (Code Quality)
There are a couple of undocumented changes unrelated to the PR's core purpose of fixing credential leakage:
- In
cli.py,os.system("cls"...)was replaced withsubprocess.run(...). - In
toolset_distributions.py, probability comments were updated.
While these are good improvements, they should ideally be separated into their own PRs to keep this security PR focused.
07fd73d to
6c32f61
Compare
|
added missing test |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for pursuing credential-boundary hardening. The execute_code portion addresses a current gap, but the secret-source portions need rework against current main.
Problems
agent/secret_sources/bitwarden.py:679-683currently preserves an inheritedBWS_SERVER_URLwhen config is empty. The new allowlist drops it, while the added test locks that regression in.- The new
_SCRUB_CONNSTR_REattools/code_execution_tool.py:164requires a non-empty username, so it missesredis://:token@hostdespite the adjacent comment claiming support. - Current
agent/secret_sources/command.py:182-186intentionally uses the centralized no-scrub subprocess factory for a user-configured helper. This was introduced in3d48f893da; replacing it with a fixed allowlist changes that documented compatibility contract.
Suggested changes
- Split/salvage the execute_code hardening, correct the empty-username URI case, and test it through
_scrub_child_env(). - Reconcile the secret-source proposal with the centralized factory and preserve the
BWS_SERVER_URLfallback before retaining those changes.
Automated hermes-sweeper review.
| env["BWS_ACCESS_TOKEN"] = access_token | ||
| # Region / self-hosted support. | ||
| if server_url: | ||
| env["BWS_SERVER_URL"] = server_url |
There was a problem hiding this comment.
When server_url is empty, this drops an inherited BWS_SERVER_URL. Current main deliberately preserves that manual override (agent/secret_sources/bitwarden.py:679-683), and hermes_cli/secrets_cli.py supports it as a non-interactive source. Please preserve the fallback.
| @@ -179,8 +217,7 @@ def _run_helper( | |||
| ) | |||
| return None | |||
|
|
|||
| env = os.environ.copy() | |||
| env["HERMES_SECRET_KEY"] = secret_key | |||
| env = _helper_child_env(secret_key) | |||
There was a problem hiding this comment.
Current main was refactored in 3d48f893da to use build_subprocess_env(scrub_secrets=False, inherit_profile_home=False) here because a user-configured secret helper may require its shell credentials. Replacing that centralized, intentional contract with a fixed allowlist needs a compatibility design and coverage rather than this direct substitution.
| # Connection-string regex matching credential-bearing values like | ||
| # ``postgresql://user:password@host/db`` or ``redis://:token@host``. | ||
| # Reused from agent/redact.py (inlined here to avoid circular imports). | ||
| _SCRUB_CONNSTR_RE = re.compile( |
There was a problem hiding this comment.
This pattern requires a non-empty username before : and therefore does not match the documented redis://:token@host form. Please support that form and add a _scrub_child_env() regression test.
|
@teknium1 addressed feedback: fixed _SCRUB_CONNSTR_RE to support empty-username URIs (redis://:token@host), preserved inherited BWS_SERVER_URL fallback when server_url config is empty, and added tests for both. The command.py helper_child_env change needs to be reconciled with build_subprocess_env(scrub_secrets=False) from main (3d48f89) — that function isn't on this branch yet. |
|
Bound to the child-process credential-inheritance class under #83565 (#83565) — same bug class, different surface. credential leakage to subprocesses and sandbox children; Wave C — shares code_execution_tool.py with #73051; sequence after it. The EPIC carries the live class table, dedup adjudication, and the dependency-driven merge order. |
|
suggesting changes
Security evidence:
Review setup: I reviewed a run-owned local rebase or patch replay against current GitHub Not checked:
Signed: GPT-5.6-luna-max in Codex |
…ndbox children Three child-process credential leaks, all previously shipping the full post-dotenv os.environ into a subprocess: 1. Bitwarden (agent/secret_sources/bitwarden.py): _run_bws_list used build_subprocess_env(scrub_secrets=False) for the legacy single-profile path, exposing every provider credential to the bws binary. Replace with a minimal allowlisted env (_bws_child_env + _BWS_ENV_ALLOWLIST), matching the 1Password provider pattern. Preserve the inherited BWS_SERVER_URL fallback when config server_url is empty (hermes_cli/secrets_cli.py). 2. Command helper (agent/secret_sources/command.py): same issue — the helper received the full env. Switch to build_subprocess_env(scrub_secrets= 'provider'), which strips AI provider/tool credentials while preserving the shell env a configured helper may need (SSH_AUTH_SOCK, DBUS, GPG). Also only export HERMES_SECRET_KEY when the requested key is nonempty. 3. Sandbox scrubbing (tools/code_execution_tool.py): _SECRET_SUBSTRINGS was missing '_PASS' (DB_PASS / REDIS_PASS / HOST_PASS leaked to sandbox children); bare 'PASS' is avoided to skip false positives (PASSENGER_HOST, BYPASS_CACHE). Add _SCRUB_CONNSTR_RE to drop env vars whose VALUES carry embedded connection-string credentials (postgresql://user:pass@host/db, redis://:token@host) even when the name looks safe. Adds build_subprocess_env(scrub_secrets='provider') mode in tools/environments/local.py as the single factory for the helper-child env. Tests: TestBwsChildEnv, TestHelperChildEnv, TestUnderscorePassSubstring, and TestValueLevelConnectionStringScrubbing.
06f2f33 to
d21e0bf
Compare
Replace
os.environ.copy()with minimal allowlist envs forbwsand command helper subprocesses. Also tighten sandbox child env scrubbing to catchDB_PASS-style vars and connection-string credentials embedded in env var values.Three fixes:
1. Bitwarden subprocess (
agent/secret_sources/bitwarden.py)os.environ.copy()leaked every post-dotenv credential into thebwschild. Now uses_bws_child_env()with a focused allowlist — matching the pattern already used by the 1Password provider.2. Command helper subprocess (
agent/secret_sources/command.py)Same issue:
os.environ.copy()leaked all credentials into/bin/sh -c. Now uses_helper_child_env()with a focused allowlist.3. Sandbox env scrubbing (
tools/code_execution_tool.py)_SECRET_SUBSTRINGShad two gaps:"PASS"— letDB_PASS,REDIS_PASS,HOST_PASSleak to sandbox children. The concern about false positives (BYPASS_CACHE,COMPASS_DIR,PASSENGER_HOST) was hypothetical — none of those env vars exist in the codebase."PASS"is now in the list.DATABASE_URL=postgresql://user:pass@host/dbpassed through because only the name was checked. Added_SCRUB_CONNSTR_REthat scans values for embedded connection-string credentials.Files changed:
agent/secret_sources/bitwarden.py— added_BWS_ENV_ALLOWLIST+_bws_child_env()agent/secret_sources/command.py— added_HELPER_ENV_ALLOWLIST+_helper_child_env()tools/code_execution_tool.py— added"PASS"to_SECRET_SUBSTRINGS, added_SCRUB_CONNSTR_RE+ value-level scan in_scrub_child_env()