Skip to content

fix(security): route TUI compute host and LSP server env through the sanitized builder - #77528

Open
andrexibiza wants to merge 1 commit into
NousResearch:mainfrom
andrexibiza:fix/security-child-env-scrub-bypasses
Open

fix(security): route TUI compute host and LSP server env through the sanitized builder#77528
andrexibiza wants to merge 1 commit into
NousResearch:mainfrom
andrexibiza:fix/security-child-env-scrub-bypasses

Conversation

@andrexibiza

@andrexibiza andrexibiza commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Related #77463

What changed and why

Closes #77463 — child-env scrub bypasses (conquest PR 2/26, from the red-team swarm).

Two spawn paths bypassed the child-env scrub entirely:

  1. TUI compute hosttui_gateway/host_supervisor.py:319 ran env.update(os.environ) after hermes_subprocess_env(), re-adding every Tier-1 secret (gateway bot tokens, remote-compute auth) into the compute-host child — the scrub was dead code on that path. The post-scrub update is removed; the heartbeat (HERMES_COMPUTE_HOST_HEARTBEAT_SECS) and PYTHONPATH additions the child legitimately needs are preserved, as is self.env layering.

  2. LSP serversagent/lsp/client.py:292 built the child env with dict(os.environ). Third-party language servers (pyright, gopls, ...) received the full secret ring, model-triggerable by writing a file in the workspace. The spawn now uses hermes_subprocess_env(inherit_credentials=False), stripping Tier-1 and provider keys, with the server's own _env additions layered on top.

Why this matters to users

Before: a compromised or buggy language server — or a compute-host process — had every gateway token and API key Hermes holds, in plaintext environment variables. After: these children get only what they need to run, honoring the same strip-by-default contract every other spawn surface already follows.

How to test

Seeds a Tier-1 secret + a provider key, builds the child env exactly as the fixed spawn paths do, and spawns a real Python child that reports its own environment — asserting the secrets are absent and the legitimate additions (heartbeat, PYTHONPATH, LSP_CUSTOM_OPT) survive.

Tests

  • test_compute_host_spawn_env_excludes_tier1_secrets — E2E real child: GATEWAY_RELAY_SECRET + HERMES_DASHBOARD_SESSION_TOKEN absent, heartbeat + PYTHONPATH survive.
  • test_lsp_spawn_env_excludes_tier1_and_provider_secrets — same pattern + ANTHROPIC_API_KEY absent, LSP_CUSTOM_OPT survives.

Platforms tested

Windows (native, this host). Both E2E tests pass under the canonical runner and direct pytest.

Verification

tests/agent/lsp/test_lifecycle.py + tests/tui_gateway/test_compute_host.py: 8 passed, 1 failed — the sole failure (test_compute_host_line_json_seed_turn_interrupt) is a pre-existing Windows pid-flake, reproduced identically on pristine main in a clean worktree. Ruff clean, git diff --check clean, Windows-footgun lint clean on the changed lines (pre-existing SIGKILL/os.kill hits in host_supervisor.py untouched — separate concern). Independent QA critique: SAFE TO SHIP (9 PASS, 0 FAIL; the 2 non-blocking RISKs — test-drift and _env escape-hatch — tracked for future hardening).

Part of #77463

Part of #83565 — TUI compute host + LSP server env routed through the sanitized builder; Wave D.

…sanitized builder

Closes NousResearch#77463 (child-env scrub bypasses).

Two spawn paths bypassed the child-env scrub entirely:

1. tui_gateway/host_supervisor.py:319 ran env.update(os.environ) AFTER
   hermes_subprocess_env(), re-adding every Tier-1 secret (gateway bot
   tokens, remote-compute auth) into the compute-host child — the scrub
   was dead code on that path. The post-scrub update is removed; the
   heartbeat and PYTHONPATH additions the child legitimately needs are
   preserved.

2. agent/lsp/client.py:292 built the LSP child env with dict(os.environ)
   — third-party language servers (pyright, gopls, ...) received the full
   secret ring, model-triggerable by writing a file in the workspace. The
   spawn now uses hermes_subprocess_env(inherit_credentials=False),
   stripping Tier-1 and provider secrets, with the server's own env
   additions layered on top.

Why this matters to users: before, a compromised or buggy language server
(or a compute-host process) had every gateway token and API key Hermes
holds, in plaintext environment variables. After, those children get only
what they need to run — the same strip-by-default contract every other
spawn surface already honored.

Tests (E2E with real children): seed Tier-1 + provider secrets, build the
env exactly as the fixed spawn paths do, spawn a real Python child that
reports its own environment, assert the secrets are absent and the
legitimate additions survive. 8 passed (2 new), 1 pre-existing pid-flake
proven identical on pristine main. Ruff clean, diff --check clean, new
lines footgun-clean (pre-existing SIGKILL/os.kill hits in host_supervisor
untouched — separate concern).
@alt-glitch alt-glitch added type/security Security vulnerability or hardening comp/lsp Language Server Protocol integration (P2 policy) comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Aug 3, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

One open PR addresses Issue #77463. Its diff fixes the TUI compute-host post-scrub environment re-addition and routes LSP server spawns through the sanitized builder, but does not address the HERMES_FORCE unwrap, Docker forwarding, case-insensitive blocklist, or execute_code substring bypasses.

Related pull requests

Suggested consolidation

keep open with a salvage path: retain the focused TUI and LSP fixes in #77528, but revise its closing claim so #77463 remains open for the four uncovered bypass classes, or extend the PR with fixes and regression tests for HERMES_FORCE unwrapping, Docker forward-env, case-insensitive matching, and execute_code gaps.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I77463(["issue #77463 (open)"])
    P77528["PR #77528 (open)"]
    P77528 -->|fixes| I77463
    class I77463 open
    class P77528 open
    class P77528 target
    click I77463 "https://github.com/NousResearch/hermes-agent/issues/77463"
    click P77528 "https://github.com/NousResearch/hermes-agent/pull/77528"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 7 kB of PR diffs, 5 kB of issue/PR text, <1 kB of discussion (1 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

@andrexibiza

Copy link
Copy Markdown
Contributor Author

Acknowledged — adopting the salvage-path guidance. This PR stays scoped to the two demonstrated bypass classes it fixes (TUI compute-host post-scrub env.update and LSP server raw-env spawn), and #77463 remains open for the four uncovered classes (HERMES_FORCE unwrap, Docker forward-env, case-insensitive blocklist, execute_code substring). The PR body closing claim will be revised to keep #77463 open; the focused TUI/LSP fixes plus their real-child regression tests stand on their own.

@egilewski

Copy link
Copy Markdown
Contributor

suggesting changes

The patch now removes inherited credentials from the LSP server and compute-host children, but automatic LSP installation still starts npm and Go package-manager children with the parent environment. Those installers can execute third-party code before the sanitized server starts, exposing Hermes provider and gateway credentials. Build every installer environment from hermes_subprocess_env(inherit_credentials=False), then add only the staging variables it requires (such as GOBIN), apply the same policy to any Python-package installer path, and add child-environment tests for each installer.

Security evidence:

  • trust boundary: The gateway/TUI parent holds provider credentials and gateway/infrastructure tokens; LSP servers and package-manager installers are third-party child processes.
  • source/sink/invariant: The patched server and compute-host spawns use a sanitized environment, but the automatic npm and Go installer subprocesses still inherit or copy the parent environment. The invariant is that every third-party child excludes Hermes credentials while preserving only explicit configuration and staging variables.
  • current-main reproduction: Current-main automatic installation constructs npm and Go child environments from the parent process environment before a sanitized LSP server can start.
  • PR-head or patch-replay validation: The patch-replay checks showed provider, gateway, and session secrets absent from patched LSP and compute-host child environments while explicit LSP settings, heartbeat, and PYTHONPATH remained; focused and related tests passed.
  • positive/negative cases: Positive checks retained the required explicit settings and runtime variables, and negative checks excluded provider, gateway, and session secrets from the patched child environments.
  • residual bypass search: The automatic-install path still reaches unsanitized npm and Go children before the sanitized server launch.
  • reviewer validation: The changed spawn logic and related installer call sites were reviewed, and the focused and related tests passed.

Review setup: I reviewed a run-owned local rebase or patch replay against current GitHub main; this does not mean the submitted branch itself merges cleanly.

Not checked:

  • Ruff validation

Signed: GPT-5.6-luna-max in Codex

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

Labels

comp/lsp Language Server Protocol integration (P2 policy) comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(security): child-env scrub bypasses — post-scrub env.update(os.environ) in TUI host, LSP full-env, _HERMES_FORCE_ unwrap, docker forward-env

4 participants