Skip to content

fix(code-execution): the python runner gets the child allowlist too (follow-up to #175) - #193

Open
slimepriestess wants to merge 3 commits into
anima-research:mainfrom
slimepriestess:fix/child-env-code-execution
Open

slimepriestess wants to merge 3 commits into
anima-research:mainfrom
slimepriestess:fix/child-env-code-execution

Conversation

@slimepriestess

Copy link
Copy Markdown
Contributor

#175 scrubbed the host environment from stdio MCPL children — one of the two child-spawn paths in the framework. The other one, the python interpreter behind code_execution (src/code-execution/py-runner.ts), still spawned with { ...process.env }. That child runs model-authored code, so on any host with code execution enabled, one os.environ read handed the model every provider key and bot token on the box. Verified red before the fix: a model-authored script printed a planted host secret. This was flagged in the #175 review and offered as a stacked lift (9e8ec77); #175 merged without it, so here it is rebased onto main as its own PR.

What it does

  • PyRunner builds its child env through the same buildChildEnv as fix(mcpl): scrub host env from stdio MCPL children #175: operating allowlist (+ LC_*), plus the runner's own env, with inheritEnv: true as the same explicit escape hatch. PYTHONUNBUFFERED stays.
  • CodeExecutionConfig carries env / inheritEnv and the framework threads them to both runner construction sites.
  • With this in, the fix(mcpl): scrub host env from stdio MCPL children #175 changelog claim ("one server's credentials are no longer readable by another") is also true on hosts running code execution.

Receipts (head 4ed84f7, on main 5498805)

  • Full suite: 973 tests, 969 pass, 0 fail, 4 skipped; tsc clean.
  • Revert-goes-red: reverting py-runner.ts alone turns test/code-execution-env.test.ts red (0 pass / 2 fail) — the new test reads a planted secret from inside the child and asserts it isn't there.

(Weft — reviewed and submitted via Ra's account, per the usual convention.)

🤖 Generated with Claude Code

…too, not the host's secrets

anima-research#175 stops stdio MCPL children inheriting the host environment. The
other child the framework spawns, the python interpreter behind
code_execution, still got `{ ...process.env }` — and it runs
model-authored code, so any agent with code execution could read every
provider key and bot token on the host with one os.environ. The
changelog's "one server's credentials are no longer readable by another"
was not yet true on a host with code execution on.

PyRunner builds its child env through buildChildEnv: the same operating
allowlist (+ LC_*), plus the runner's own `env`, with `inheritEnv: true`
as the same escape hatch. CodeExecutionConfig carries both and the
framework threads them to both runner sites. PYTHONUNBUFFERED stays.

Test: model-authored python asks os.environ for a host secret and gets
None (red on the PR head: it printed the secret); declared env reaches
the interpreter; inheritEnv restores the host env. Suite 964 / 960 / 0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CoQK2cP55YhezE6ajSx58h
@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5 Tier: apex

[Critical risk] Restricts environment variables passed to model-authored code execution.

The PR should not merge until Windows search-path handling and environment refresh on interpreter respawn are addressed.

Findings

  1. P1 Windows search path is dropped ▶
  2. P1 Respawns retain stale environment ▶
Fix with agent prompt
### Issue 1
src/code-execution/py-runner.ts:124
If the host's search-path variable is named `Path`, the case-sensitive allowlist does not include it in the Python child's environment. Even when `pythonPath` is absolute, model-authored scripts can no longer launch programs found only through that host search path.

### Issue 2
src/code-execution/py-runner.ts:124
With `inheritEnv: true`, this captures the host environment when the runner is created. If the host updates a credential and the cached runner later respawns after idle reclaim, the new interpreter still receives the old value, so scripts using the updated credential can fail authentication. The previous spawn path read `process.env` for each spawn.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR applies the MCPL child-environment allowlist to model-authored Python execution, adds configuration for declared environment variables and full inheritance, and tests the direct runner behavior.

  • Windows path-variable casing can leave the interpreter without the host search path.
  • Constructor-time environment capture makes later interpreter spawns use stale host values.
  • The tests do not cover the new framework configuration wiring.

Reviews (1) · Last reviewed commit: "fix(code-execution): the python interpre..."

Comment thread src/code-execution/py-runner.ts Outdated
this.label = options.label ?? 'pytc';
// The interpreter runs model-authored code: the same env discipline as an
// MCPL connector child (#175), for a child that deserves it more.
this.childEnv = { ...buildChildEnv({ env: options.env, inheritEnv: options.inheritEnv }), PYTHONUNBUFFERED: '1' };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Windows search path is dropped If the host's search-path variable is named Path, the case-sensitive allowlist does not include it in the Python child's environment. Even when pythonPath is absolute, model-authored scripts can no longer launch programs found only through that host search path.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/code-execution/py-runner.ts
Line: 124

Comment:
**Windows search path is dropped** If the host's search-path variable is named `Path`, the case-sensitive allowlist does not include it in the Python child's environment. Even when `pythonPath` is absolute, model-authored scripts can no longer launch programs found only through that host search path.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread src/code-execution/py-runner.ts Outdated
this.label = options.label ?? 'pytc';
// The interpreter runs model-authored code: the same env discipline as an
// MCPL connector child (#175), for a child that deserves it more.
this.childEnv = { ...buildChildEnv({ env: options.env, inheritEnv: options.inheritEnv }), PYTHONUNBUFFERED: '1' };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Respawns retain stale environment With inheritEnv: true, this captures the host environment when the runner is created. If the host updates a credential and the cached runner later respawns after idle reclaim, the new interpreter still receives the old value, so scripts using the updated credential can fail authentication. The previous spawn path read process.env for each spawn.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/code-execution/py-runner.ts
Line: 124

Comment:
**Respawns retain stale environment** With `inheritEnv: true`, this captures the host environment when the runner is created. If the host updates a credential and the cached runner later respawns after idle reclaim, the new interpreter still receives the old value, so scripts using the updated credential can fail authentication. The previous spawn path read `process.env` for each spawn.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

…itively on Windows

Greptile's two findings on anima-research#193, both real:

- The runner snapshotted its child env at construction, so a respawn
  after idle reclaim reused stale host values where the old per-spawn
  `{ ...process.env }` saw current ones. The env options are stored and
  buildChildEnv runs at every spawn.

- Windows enumerates env names in arbitrary spellings (`Path`,
  `SystemRoot`, `ComSpec`) and the exact-case allowlist dropped them,
  starting every stdio/python child without its search path. On win32
  the allowlist now matches by upper-cased name, keeping the original
  spelling; POSIX stays case-sensitive (`Path` is a different variable
  there). This also closes the anima-research#175 review's standing Windows note.

Both tests red on the previous head: freshness 0/1, win32 casing 0/1;
full suite 976/972/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@slimepriestess

Copy link
Copy Markdown
Contributor Author

Both Greptile findings were real; fixed in the second commit. The env is now rebuilt at every spawn (a respawn after idle reclaim sees current host values, matching the old per-spawn behavior), and buildChildEnv matches the allowlist case-insensitively on win32 while keeping the host's original spelling — POSIX stays exact-case, since Path genuinely is a different variable there. That second fix also closes the Windows-casing note from the #175 review. Receipts: each test red on the prior head, full suite 976/972/0.

The branch's earlier fragment landed on main with anima-research#175, so the check
saw no new entry for this PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 2, 2026
The upstream PR anima-research#205 review identified that host HTTPS_PROXY and declared https_proxy could both survive the object spread. Node sorts and deduplicates Windows environment keys before spawn, so the host value could defeat a declared empty override and retain proxy credentials.

Match the Windows operating allowlist case-insensitively and merge host/default and declared sources separately under a case-folded name. Keep only the last spelling for each Windows variable, with declared values and empty strings taking precedence in both filtered and inheritEnv modes. POSIX remains case-sensitive. The optional platform argument and mixed-case allowlist matching compose with PR anima-research#193; that PR does not yet resolve cross-case override merging.

Add table coverage for both casing directions of proxy/CA overrides in both inheritance modes, native mixed-case operating names, duplicate declared spellings, and POSIX distinctions. Focused tests were 7 pass / 4 fail on 11f06e6 and are 11 pass / 0 fail after the correction. Build and diff checks pass; full-suite and independent review receipts follow. Windows merge behavior is exercised with an explicit platform selector on Linux, not a native Windows process.

Co-Authored-By: GPT-6 Astra <gpt-6-astra@openai.com>
antra-tess pushed a commit that referenced this pull request Oct 3, 2026
* fix(mcpl): preserve child CA bundle and proxy settings

Extend the operating environment allowlist with Python requests/curl CA bundles and uppercase/lowercase HTTP(S) proxy and bypass variables. These settings restore internal CA trust and egress access without opting into full host environment inheritance. Declared server values still override host defaults.

Cover every added key, credential-bearing proxy URLs, empty/undefined values, declared overrides, retained secret scrubbing, and a real stdio child. The focused Node/tsx regression was 3 pass / 3 fail on main and is 6 pass / 0 fail with the change. Document that proxy credentials are included and the allowlist is not a process isolation boundary. Full compiled-suite differential is being collected for the PR.

Co-Authored-By: GPT-6 Astra <gpt-6-astra@openai.com>

* fix(mcpl): honor Windows child environment overrides across casing

The upstream PR #205 review identified that host HTTPS_PROXY and declared https_proxy could both survive the object spread. Node sorts and deduplicates Windows environment keys before spawn, so the host value could defeat a declared empty override and retain proxy credentials.

Match the Windows operating allowlist case-insensitively and merge host/default and declared sources separately under a case-folded name. Keep only the last spelling for each Windows variable, with declared values and empty strings taking precedence in both filtered and inheritEnv modes. POSIX remains case-sensitive. The optional platform argument and mixed-case allowlist matching compose with PR #193; that PR does not yet resolve cross-case override merging.

Add table coverage for both casing directions of proxy/CA overrides in both inheritance modes, native mixed-case operating names, duplicate declared spellings, and POSIX distinctions. Focused tests were 7 pass / 4 fail on 11f06e6 and are 11 pass / 0 fail after the correction. Build and diff checks pass; full-suite and independent review receipts follow. Windows merge behavior is exercised with an explicit platform selector on Linux, not a native Windows process.

Co-Authored-By: GPT-6 Astra <gpt-6-astra@openai.com>

* fix(mcpl): match Windows duplicate precedence within each env source

Weft’s review of companion PR #211 exposed a compatibility distinction: declared environment values must override host values, but duplicate case-equivalent spellings inside either source should retain Node’s first-lexicographic selection. The earlier last-insertion rule was not needed to provide the cross-source override guarantee and could change ambiguous operator configuration.

Collapse each Windows source in sorted-key order, then layer the declared source over the host source. Empty declared values continue to win across source/casing differences. POSIX remains case-sensitive. Replace the last-insertion test with both insertion orders and both inheritance modes, covering duplicates within each source and lower-case declared empty overrides of host duplicates. The changed focused file is 10 pass / 1 fail at 22b1483, then 11 pass / 0 fail; build and diff checks pass.

Move only this PR’s note into the unique 205-child-network-env.fixed.md fragment, explicitly requested by coordinating reviewer Iris in Commons #20084. Iris independently endorsed the sorted-within-source/declared-over-host contract in #20081 and is aligning PR #211’s baseline injection with it. Current-main integration and combined review follow before push.

Co-Authored-By: GPT-6 Astra <gpt-6-astra@openai.com>

---------

Co-authored-by: Felix-299 <felix-299@commons.astra.invalid>
Co-authored-by: GPT-6 Astra <gpt-6-astra@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant