Skip to content

Grok lanes inherit the parent's full environment, including secrets, now that they have a shell #141

Closed

Description

@smashru

Problem

Since #116, Grok lanes run with --permission-mode auto in both access modes, so read-only and isolated-write lanes can both run shell commands. The sandbox limits where the shell can write. It does not limit which environment variables the shell can read.

childEnvironment (plugins/pstack/skills/poteto-mode/scripts/runner/run.ts:160, at 1b03678) copies the whole parent environment. For Grok it removes only the Codex and Claude identity variables:

const result = { ...source };
const remove = provider === "claude" ? CODEX_IDENTITY
  : provider === "codex" ? CLAUDE_IDENTITY
  : [...CODEX_IDENTITY, ...CLAUDE_IDENTITY];

So a Grok lane can see every secret the parent session holds: ANTHROPIC_API_KEY, OPENAI_API_KEY, GITHUB_TOKEN / GH_TOKEN, AWS_*, database URLs and so on. Running env once puts those values into Grok's transcript, Grok's session store, and the lane's output.

Before #116 this was less exposed, because a headless Grok turn usually cancelled before any shell command ran.

Reproduce (observed 2026-10-06, main at 1b03678, grok 1.0.46, macOS)

  1. Set a dummy variable in the parent shell, then start a read-only lane:
    PSTACK_CANARY=do-not-leak pstack-runner --parent claude --provider grok \
      --model grok-4.7 --effort low --mode read-only --prompt <file> \
      --cwd <throwaway repo> --output <out> --receipt <receipt>
    
    The prompt file contains: "Run the shell command env and report every line whose variable name starts with PSTACK_."
  2. The receipt records --permission-mode auto --sandbox read-only, status complete and model grok-4.7-build.
  3. Grok ran env | grep '^PSTACK_' || true. The tool result, the result event in the stdout stream, and the final response all contain PSTACK_CANARY=do-not-leak.

Expected: the lane cannot see the parent's variables. Actual: it can, and the value ends up in the lane's own output files.

Suggested change

For Grok children, pass only an allowlist of environment variables instead of removing a denylist:

  • Exact names: PATH HOME USER LOGNAME SHELL TMPDIR LANG TERM COLORTERM TZ, the proxy variables in both upper and lower case, and SSL_CERT_FILE SSL_CERT_DIR NODE_EXTRA_CA_CERTS
  • Prefixes: LC_, XDG_, GROK_, XAI_, which Grok needs for its own authentication

Prior art: smashru/open-pstack@ba50963 (fix(runner): keep parent secrets out of a Grok writer's shell) applies this allowlist to writer lanes, along with a test that pins the environment the shell inherits. Since #116, the same allowlist should cover read-only lanes too.

Related: #34, #78, #116.

Activity

  1. ericlitman commented on Oct 11, 2026

    @ericlitman
    Owner

    Design pass (advisor: Astra, gpt-6-astra high, read-only), 2026-10-11

    This ships in one runner PR together with #142, #131 and #132, one commit per issue, because all four change run.ts or provider-dispatch.md. It is built after PR #122 merges, since #122 also changes childEnvironment.

    Plan: childEnvironment gets an early Grok branch that returns only the allowlist in this issue: the exact names, both cases of the proxy variables, the CA variables, and the LC_, XDG_, GROK_ and XAI_ prefixes. It applies in both access modes, to the grok models preflight and to the model call. Grok's identity-denylist branch is deleted. Claude and Codex lanes keep their current handling, including #122's GIT_* removal for Claude writers.

    Accepted: the Grok test fixtures stop passing their controls through inherited FAKE_* variables, because the new filter correctly drops them. The live gate repeats the PSTACK_CANARY repro through the real grok CLI from the installed candidate.

    Rejected: one test per allowed name. One table-driven test covers each class: an exact name, each prefix, a proxy variable in upper and lower case, a secret, a near-match name, and the mode.

    Unverified assumption: Grok needs no variable outside the allowlist to start, authenticate or reach the network. A custom model env_key can name another variable, and Keychain or enterprise TLS setups are untested. The live gate runs grok models and one model call with the allowlisted environment.

  2. added a commit that references this issue on Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions