Skip to content

fix(tui): apply per-call execution policy to Python and JavaScript tools - #6820

Merged
Hmbown merged 3 commits into
codewhale-hq:mainfrom
Guan0923:codex/evaluate-interpreter-entrypoints
Oct 3, 2026
Merged

Hmbown merged 3 commits into
codewhale-hq:mainfrom
Guan0923:codex/evaluate-interpreter-entrypoints

Conversation

@Guan0923

@Guan0923 Guan0923 commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Keep code_execution and js_execution, but route their interpreter processes through the existing permission-aware launcher.

Previously, interpreter dispatch passed only the tool input and workspace path to the Python and Node runners. After approval, both runners launched local interpreters directly without applying the effective session sandbox policy.

A working directory is not filesystem confinement. Environment sanitization, timeouts, and process-tree cleanup are useful protections, but they do not enforce filesystem or network restrictions.

This change connects both tools to the existing execution-policy mechanism. It does not introduce another sandbox implementation or require ordinary interpreter calls to be rewritten as Shell calls.

Explicitly authorized wider execution remains supported: ordinary approval retains the current policy, while a separately approved elevation applies only to the corresponding call.

Changes

  • Pass the effective ToolContext, including per-call policy overrides, to both interpreter runners.
  • Refuse interpreter dispatch without an effective context instead of falling back to an unconstrained local launch.
  • Build Python and Node commands through the existing sandboxed_runner_command.
  • Add optional sandbox_permissions and justification fields to both tool schemas.
  • Extend the existing sandbox-elevation validation and approval flow to these interpreter tools:
    • require a valid permission/justification pair;
    • require a strictly wider policy;
    • require explicit one-shot user approval in Ask mode;
    • refuse elevation when the current posture cannot provide that approval.
  • Deliver source through stdin rather than a host temporary script, avoiding script visibility problems with sandbox-private /tmp.
  • Preserve the result payload, existing execution budgets, environment sanitization, and process-tree cleanup.
  • Preserve Node proxy handling without clearing the environment prepared by the shared launcher.
  • Replace approval wording that implied guaranteed sandbox isolation.
  • Update tool documentation, the feature registry, and existing runner callers/tests.

Authorization behavior

The two decisions remain distinct:

Decision Execution behavior
Ordinary approval Run with the effective session policy; approval does not implicitly grant full access.
Explicit approval of a wider policy Run this exact call with the approved policy; do not change the session default.
Elevation request without an available human approval path Refuse the request.
Local interpreter call in an external-sandbox session Refuse local execution rather than silently bypassing the configured backend.

The tools retain their names and their required code input. Callers that need a wider policy can request it through the same interpreter tool instead of switching to Shell solely to obtain approval.

Scope and known limitations

  • This is a policy-plumbing fix, not a new operating-system sandbox.
  • Native enforcement still depends on the platform and configured backend.
  • The shared launcher currently refuses read-only execution when native enforcement is unavailable.
  • Without an available local wrapper, workspace-write retains the existing unenforced behavior. This change does not add Windows filesystem or network isolation.
  • External-backend sessions cannot use these local interpreter runners; they must use the backend-supported execution path.
  • Source is now delivered through stdin. Tracebacks refer to stdin rather than a generated temporary script filename. Node execution explicitly uses CommonJS.
  • Python keeps its 120-second production budget; JavaScript keeps its 600-second production budget.
  • The separate RLM/REPL kernel is unchanged. Existing Plan/REPL admission checks remain in place.
  • Neither dedicated tool is removed, and no additional execution framework is introduced.

How I tested it

Local verification was performed on Windows.

Focused TUI tests

Group Result
Python execution and existing Plan/REPL checks 7 passed; 0 failed
Interpreter execution-policy regressions 2 passed; 0 failed
JavaScript execution, errors, credentials, proxy handling, and schema 8 passed; 0 failed
Sandbox-elevation validation and approval journeys 4 passed; 0 failed

These are 21 distinct passing TUI tests, not a full-suite run.

Cargo invocations included:

cargo test --offline --locked -p codewhale-tui --lib -j 1 code_execution -- --test-threads=1
cargo test --offline --locked -p codewhale-tui --lib -j 1 interpreter_execution_policy -- --test-threads=1

The js_execution and sandbox_escalation groups were run directly from the compiled TUI unit-test binary.

The runtime regressions cover both interpreter entrypoints:

  • restrictive calls cannot perform the attempted writes;
  • explicitly wider per-call policies allow execution;
  • subsequent read-only calls remain restricted;
  • an external execution boundary cannot silently become a local interpreter launch.

The existing engine approval journeys were extended to cover Python and JavaScript, including approval before execution and refusal in postures that cannot provide the required human decision.

Negative control

Temporarily bypassed the shared launcher and restored direct local command construction while keeping the same source-delivery path.

Both permission regressions failed:

test result: FAILED. 0 passed; 2 failed

After restoring the fixed sources and rebuilding, both passed again:

test result: ok. 2 passed; 0 failed

Feature registry and formatting

  • Independently compiled and executed the original feature-registry test source with cached dependencies: 6 passed; 0 failed.
  • This was a standalone test-source run, not a full Cargo integration-test build.
  • cargo fmt --all -- --check passed.
  • git diff --check passed.

Not run locally:

  • the full workspace suite;
  • live Linux Bubblewrap or macOS Seatbelt confinement;
  • packaged/released application acceptance.

Issue

No-Issue: This PR replaces its original design-only proposal with the implemented interpreter-policy repair; no separate issue is linked.

The original proposal commits remain in the branch history. The RFC-only document is no longer part of the final diff, which contains the verified repair instead of an unimplemented direction discussion.

Checklist

  • Started from fetched origin/main before implementation.
  • Reused the existing execution-policy and elevation mechanisms.
  • Updated documentation and the feature registry.
  • Ran focused execution and authorization tests.
  • Demonstrated that the permission regressions fail when enforcement is bypassed.
  • Documented platform limitations and stdin-related behavior changes.
  • Preserved published branch ancestry; no force push.
  • No release, deployment, or cross-platform acceptance claim.

Request a maintainer decision on removing duplicate Python/JavaScript entrypoints before extending their permission handling. Document the current policy-dispatch gap, existing shared launcher, and platform limitations. Documentation only; no runtime implementation or confinement tests.
@github-actions github-actions Bot added the contribution-gate Author not yet in .github/APPROVED_CONTRIBUTORS; a maintainer grants access with /lgtm label Oct 2, 2026
Pass effective tool context to Python and Node and reuse sandboxed_runner_command. Extend exact-call sandbox elevation to both tools, deliver source on stdin, preserve Node proxy environment, and document shared-launcher platform limits.

Windows verification: code_execution 7/0, js_execution 8/0, interpreter execution policy 2/0, sandbox escalation 4/0; standalone feature registry source 6/0. Bypassing the shared launcher makes both policy regressions fail (0 passed, 2 failed); restoring the fix passes 2/0. Formatting and diff checks passed. No native Linux/macOS confinement or full workspace-suite claim.
Replace the RFC-only proposal in the final PR diff with the verified interpreter-policy implementation. Preserve published ancestry without rewriting history; the resulting tree matches the already tested repair commit.
@Guan0923 Guan0923 changed the title docs(rfc): evaluate consolidating Python and JavaScript tools into Shell fix(tui): apply per-call execution policy to Python and JavaScript tools Oct 3, 2026
@Guan0923
Guan0923 marked this pull request as ready for review October 3, 2026 12:17
Copilot AI balanced review requested due to automatic review settings October 3, 2026 12:17
@Guan0923
Guan0923 requested a review from Hmbown as a code owner October 3, 2026 12:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Hmbown
Hmbown merged commit 9e19f03 into codewhale-hq:main Oct 3, 2026
27 checks passed
Hmbown pushed a commit that referenced this pull request Oct 4, 2026
Windows Native hosts: on 02fcc3e the LPAC token check and the data-directory
share conflict were fixed, and the isolation probe ran inside the LPAC for the
first time. Node then aborted at startup (WSAStartup 10107): an LPAC cannot
open any registry key without the registryRead capability, and Winsock reads
its catalog from the registry. Native hosts now receive exactly that one
capability (derived by name), and the token check requires exactly it; it
grants no file, network or COM access, and socket creation stays denied,
which the probe checks. Bun failed opening the NUL device (EPERM) for
--config=NUL: the plain Bun host now reads an empty config from the granted
runtime-copy directory, and the Windows compiled host relies on
--no-compile-autoload-bunfig instead of baking --config=NUL. Unprotected file
ACL edits now pass an explicit-only DACL to SetSecurityInfo with
UNPROTECTED_DACL_SECURITY_INFORMATION so Windows re-derives inherited entries
from the parent (a file has no children to propagate to); the kernel write had
turned a file's inherited entries explicit. ACL pointer reads use checked
NonNull access (CodeQL rust/access-invalid-pointer on 02fcc3e).

Windows atomic writes: Fleet's WorkspaceFile/WindowsDirectory pins, new in
0.10.1, keep every ancestor directory of a Fleet file open with read-only
sharing. MoveFileExW (tempfile::persist) reopens the destination directory
with FILE_ADD_FILE, so while a Fleet ledger is alive (a whole `codewhale
fleet` run, or a Runtime API request) Codewhale's own atomic writes into the
workspace root or its ancestors failed with os error 32. That includes
file-tool edits; hosted Windows showed it as a bare 500 writing mcp.json. On
Windows, write_atomic now creates its temp with DELETE access and renames
through that handle with a bare file name (the rename Fleet publication
already uses), so the parent is never reopened and no pin is relaxed. Names
Windows would rewrite (trailing dot or space, device names) are refused
before any temp exists, and every failure path closes the delete-denying
handle before removing the temp. MCP config write errors now name the file.
A Windows test writes and replaces a file beside live Fleet pins and checks
refused names leave no temp behind.

Wire JSON: the extension-host `isJson` guard used Array.every and
Object.values, so it accepted sparse arrays, extra array properties,
accessors and symbol keys that serialize differently from what was checked
(Copilot review on #6815). It now accepts only dense arrays and plain objects
whose own keys are enumerable string data properties, the rule storage.ts
already enforced; storage's duplicate check is removed. A new test fails on
the old guard and passes on the new one. Bundles that include the guard were
rebuilt with the pinned esbuild; the harness builtin digest pin in tier.rs
moves with it.

Release notes: credits and entries for #6820, #6829, #6830 and #6831, which
landed in 0.10.1 without CHANGELOG coverage, and for #6827 (Windows node.exe
kills are held by the built-in safety floor even in Full Access), crediting
@jayanthvee in the CHANGELOG, docs/CONTRIBUTORS.md, website credits and
public-surface facts.

Fixes #6827

Source15 verification (3,792 rows; owning HEAD cf03333 and index 5983aa54
unchanged):
- npm test and npm run check:web: 1,185 passed / 0 failed / 7 explicit skips
  (the website credit-parity test caught the first draft missing the new
  reporter credit);
  extension-host suite 390 tests, 383 passed, 0 failed, 7 skipped.
- macOS, compiled host rebuilt from the new bundle: extension_host 177/0,
  plugins::install::dsh 28/0, mcp 407/0, utils 38/0, tools::file 190/0,
  runtime_store_convergence 14/0, builtin digest table 2/0.
- Windows: cargo check --target x86_64-pc-windows-gnu of codewhale-tui lib
  and tests clean under deny(warnings); write_atomic change reviewed
  adversarially (FIX_FIRST findings applied: early-error temp cleanup, single
  retry layer, rewritten-name refusal). The LPAC capability, Bun config and
  file-ACL changes are type-checked only; their runtime proof is this head's
  hosted Windows run.

Hosted 02fcc3e: Safety, Lint, npm wrapper smoke and Test (ubuntu-latest)
passed. Windows 17,952 passed / 91 failed: LPAC runtime startup (Node
WSAStartup, Bun NUL config), one file-ACL hygiene test, the mcp.json write
conflict and one DSH HTTP case behind the probe, all addressed above. CodeQL
flagged five pointer reads, addressed above. Not proven here: Windows runtime
of these repairs.
This is source integration, not release, deploy, provider or install
approval.

Evidence: artifacts/gpt61-0101-takeover-20261002/parent-source15-application,
parent-source15-local, parent-source15-npm-web-actual, windows-gnu-check.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contribution-gate Author not yet in .github/APPROVED_CONTRIBUTORS; a maintainer grants access with /lgtm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants