Skip to content

fix(security): replace shell=True with shlex.split in config-driven execution paths#10698

Closed
shaun0927 wants to merge 2 commits into
NousResearch:mainfrom
shaun0927:fix/shell-true-config-paths
Closed

fix(security): replace shell=True with shlex.split in config-driven execution paths#10698
shaun0927 wants to merge 2 commits into
NousResearch:mainfrom
shaun0927:fix/shell-true-config-paths

Conversation

@shaun0927

Copy link
Copy Markdown
Contributor

Summary

Replace subprocess.run(..., shell=True) with shlex.split() in two config-driven execution paths that bypass the terminal tool's safety infrastructure.

Changes

1. Quick commands (cli.py:5530-5533)

# Before
result = subprocess.run(exec_cmd, shell=True, capture_output=True, text=True, timeout=30)

# After
import shlex
result = subprocess.run(shlex.split(exec_cmd), capture_output=True, text=True, timeout=30)

exec_cmd comes from config.yaml under quick_commands.<name>.command. In a shared-config scenario (project-level HERMES.md or config checked into git), a malicious entry could inject shell metacharacters.

2. Memory plugin dependency check (hermes_cli/memory_setup.py:136-137)

# Before
subprocess.run(check_cmd, shell=True, capture_output=True, timeout=5)

# After
import shlex
subprocess.run(shlex.split(check_cmd), capture_output=True, timeout=5)

check_cmd comes from plugin plugin.yaml metadata. A compromised or malicious memory plugin could specify arbitrary shell commands.

Why this matters

SECURITY.md correctly scopes unrestricted shell access via the terminal tool as by-design. The distinction is that these paths:

  1. Do not go through the terminal tool's approval flow
  2. Are not subject to output redaction (agent/redact.py)
  3. Are not logged in the conversation audit trail
  4. Accept input from files that may originate from untrusted sources

Files changed (2 files, +4/-2)

File Change
cli.py shlex.split() for quick command exec
hermes_cli/memory_setup.py shlex.split() for plugin dependency check

Test plan

  • Quick commands with simple strings (ls, echo hello) → work as before
  • Quick commands with shell metacharacters (echo $(whoami)) → executed literally, not interpreted
  • Memory plugin with normal check command (which redis-server) → works as before
  • No regression in hermes memory setup flow

Closes #10692

…xecution paths

Quick commands (cli.py) and memory plugin dependency checks
(memory_setup.py) passed config-sourced strings to subprocess.run()
with shell=True. Unlike the terminal tool — which has approval UI,
redaction, and audit logging — these paths bypass all agent safety
controls.

Replace with shlex.split() to prevent shell metacharacter injection
from project-level configs or third-party plugin metadata.

Closes NousResearch#10692
shlex.split() raises ValueError on unmatched quotes. Catch this
gracefully instead of falling through to the generic exception handler.

Also adds a comment noting that commands needing shell features
(pipes, redirects) can use explicit "sh -c '...'" in config.yaml.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P1 High — major feature broken, no workaround comp/cli CLI entry point, hermes_cli/, setup wizard tool/memory Memory tool and memory providers labels Apr 25, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Closing alongside #10692 (full reasoning there). Short version:

  • The memory_setup.py half is already on main as of d6c9711b — that part of this diff is a no-op against current code.
  • The cli.py quick_commands half would actively break the feature. quick_commands are user-authored shell snippets from ~/.hermes/config.yaml (the "shared project-level config" attack vector doesn't exist — Hermes doesn't load repo-local config.yaml files), and shell semantics are the point: pipes, redirects, env-var expansion, && chains. The existing code comment at cli.py:8004 already documents this as intentional. Switching to shlex.split() would silently break common quick commands like /disk: df -h | grep sda.

Thanks for the work — the reflex to swap shell=True for shlex.split() is the right one in most contexts. This one is the by-design exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P1 High — major feature broken, no workaround tool/memory Memory tool and memory providers type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

shell=True in config-driven execution paths bypasses terminal tool safety controls

3 participants