Loading...
Skip to main content

CVE-2026-75915 — Vulnerability in deepseek-tui — CVE Details

HIGH

CodeWhale: js_execution leaks parent environment to model context via missing env scrub

Published September 4, 2026Updated September 4, 2026Source: osv

Summary

### Maintainer resolution The CodeWhale maintainers validated this report. The affected package ranges are recorded in the advisory metadata. Version 0.8.64 contains the fix in commit 26de44a8bd5051f8f944ea60b2c37ae1d2b7d25e. Users should upgrade to 0.8.64 or later. The original reporter analysis is preserved below. ### Summary js_execution exposes parent process environment to model-provided JavaScript The js_execution tool spawns Node with tokio::process::Command::new without calling the child_env scrubber that exec_shell, the Python REPL, and the MCP launcher all use. Model-provided JavaScript reads process.env and the values flow back to the parent transcript as the tool's stdout, exposing API keys, cloud credentials, and forge tokens to the next model turn. ### Details In `crates/tui/src/tools/js_execution.rs` (v0.8.37, lines 91-105): ```rust let temp_dir = tempfile::tempdir() .map_err(|e| ToolError::execution_failed(format!("tempdir failed: {e}")))?; let script_path = temp_dir.path().join("js_execution.js"); tokio::fs::write(&script_path, code) .await .map_err(|e| ToolError::execution_failed(format!("tempfile write failed: {e}")))?; let mut cmd = tokio::process::Command::new(&node); cmd.arg(&script_path); cmd.current_dir(workspace); let output = tokio::time::timeout(Duration::from_secs(120), cmd.output()) .await .map_err(|_| ToolError::Timeout { seconds: 120 }) .and_then(|res| res.map_err(|e| ToolError::execution_failed(e.to_string())))?; ``` The Command is built without `cmd.env_clear()` and without the project's `crate::child_env::apply_to_tokio_command` helper. Every variable in the parent process environment is inherited by the spawned `node`. For comparison, exec_shell (`crates/tui/src/tools/shell.rs:790-792`) and the Python REPL (`crates/tui/src/repl/runtime.rs:238`) both apply the scrubber: ```rust child_env::apply_to_command(&mut cmd, child_env::string_map_env(&exec_env.env)); ``` apply_to_tokio_command calls `cmd.env_clear()` and then re-installs only the keys that pass `is_allowed_parent_env_key` (PATH, HOME, USER, LANG/LC_*, TMPDIR, proxy variables, Windows toolchain context, terminal settings). Secret-bearing variables (DEEPSEEK_API_KEY, OPENAI_API_KEY, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, etc.) are not on the allowlist and are dropped before the child starts. The js_execution path bypasses both the env_clear and the allowlist. Commit history makes the gap explicit. Commit `e6d4eae fix(security): scrub child process environments` (2026-05-08) introduced child_env.rs and rewrote exec_shell, the Python REPL, the MCP launcher, and main.rs to use it. Commit `2566f3c feat(tools): add js_execution tool` (2026-05-12) added this file four days later and never picked up the helper. The tool is described to the model and surfaced in the approval pane as "Run model-provided JavaScript code in local Node.js execution sandbox" (`crates/tui/src/core/engine/turn_loop.rs:1174-1176`). No sandbox is applied beyond a 120-second timeout; Node has full filesystem and network access in addition to the inherited environment. The wording understates the trust boundary that the user is being asked to cross. In YOLO mode (`auto_approve=true`) the JS body runs without any prompt at all, so a single adversarial prompt-injection from a README, fetched web page, or MCP server output drains the parent environment to the next model turn. ### PoC A standalone Cargo test reproduces the unscrubbed-env behavior. Save as `crates/tui/tests/js_execution_env_leak.rs` and run with `cargo test -p deepseek-tui --test js_execution_env_leak -- --nocapture`: ```rust use deepseek_tui::tools::js_execution::execute_js_execution_tool; use serde_json::json; use tempfile::tempdir; #[tokio::test] async fn js_execution_inherits_parent_secrets() { if deepseek_tui::dependencies::resolve_node().is_none() { eprintln!("node not on PATH; skipping"); return; } unsafe { std::env::set_var("AWS_SECRET_ACCESS_KEY", "leak-marker-AKIA-EXAMPLE"); std::env::set_var("DEEPSEEK_API_KEY", "leak-marker-sk-EXAMPLE"); } let tmp = tempdir().unwrap(); let result = execute_js_execution_tool( &json!({"code": "console.log(process.env.AWS_SECRET_ACCESS_KEY + '|' + process.env.DEEPSEEK_API_KEY)"}), tmp.path(), ).await.expect("execute"); let payload: serde_json::Value = serde_json::from_str(&result.content).unwrap(); let stdout = payload["stdout"].as_str().unwrap_or(""); assert!(stdout.contains("leak-marker-AKIA-EXAMPLE"), "AWS leaked: {stdout}"); assert!(stdout.contains("leak-marker-sk-EXAMPLE"), "DEEPSEEK leaked: {stdout}"); } ``` Equivalent reproducer against the binary: ```bash export AWS_SECRET_ACCESS_KEY="leak-marker-AKIA-EXAMPLE" export DEEPSEEK_API_KEY="leak-marker-sk-EXAMPLE" deepseek # Ask the model to run: # js_execution({"code":"console.log(JSON.stringify(process.env))"}) # Approve once. The returned stdout contains every parent env value verbatim, # including the markers above, and is now part of the model's context for the next request. ``` The fix is one line added next to the existing `cmd.current_dir(workspace)` call: ```rust let mut cmd = tokio::process::Command::new(&node); cmd.arg(&script_path); cmd.current_dir(workspace); crate::child_env::apply_to_tokio_command(&mut cmd, std::iter::empty::<(&str, &str)>()); ``` This calls the existing helper with no overrides, mirroring how repl/runtime.rs spawns the Python REPL. The behavior the description string already promises (sandbox) is then partially honored: secret-bearing parent variables stay in the parent. ### Impact The tool returns parent-environment secrets to the model on a single approval, or with no approval in YOLO mode. Any variable the user has exported becomes part of the next model request and travels to the configured LLM provider's logs. Common variables that the codebase's own provider clients read from process env, and therefore the values most likely to be present, include DEEPSEEK_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, MISTRAL_API_KEY, AZURE_OPENAI_API_KEY, XAI_API_KEY, GROQ_API_KEY, and TOGETHER_API_KEY. Cloud and source-control credentials commonly exported in developer shells include AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, GOOGLE_APPLICATION_CREDENTIALS, GITHUB_TOKEN, GH_TOKEN, GITLAB_TOKEN, NPM_TOKEN, CARGO_REGISTRY_TOKEN, PYPI_API_TOKEN, and DATABASE_URL-style secrets. The local-sandbox wording shown at approval time understates the trust boundary, so users approving what they read as a sandboxed snippet do not anticipate that every shell-exported credential is reachable from the snippet. The remediation matches the pattern already adopted across exec_shell, the Python REPL, and the MCP launcher, so the gap is a missed call site rather than a design tradeoff.

Advisory Timeline

  • First published September 4, 2026 (38 days ago).
  • Last revised September 4, 2026 — 0 days after initial publication.

Remediation

Upgrade to the fixed version using your package manager.

npm
Update codewhale to 0.8.64 or later
npm install codewhale@0.8.64
npm
Update deepseek-tui to 0.8.41 or later
npm install deepseek-tui@0.8.41
Cargo
Update codewhale-tui to 0.8.64 or later
cargo update -p codewhale-tui --precise 0.8.64

After upgrading, run your dependency scanner again to confirm the vulnerability is resolved.

Affected Packages (4)

Affects 2 ecosystems: crates.io (2), npm (2).

PackageEcosystemAffectedFixed In
deepseek-tui
crates.io
All versionsRange-based data available
codewhale
npm
All versions0.8.64
deepseek-tui
npm
All versions0.8.41
codewhale-tui
crates.io
All versions0.8.64

Vulnerability Classification

Common Weakness Enumeration (CWE) identifiers for this vulnerability type.

CVSS Score Breakdown

What the CVSS (Common Vulnerability Scoring System) 7.5 score means for each attack dimension.

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Frequently Asked Questions

What is CVE-2026-75915?
CodeWhale: js_execution leaks parent environment to model context via missing env scrub This vulnerability has been assigned a severity rating of HIGH (CVSS score: 7.5/10).
How do I check if my project is affected by CVE-2026-75915?
CVE-2026-75915 affects deepseek-tui, codewhale and codewhale-tui. Use GeekWala's free vulnerability scanner to check your dependencies against CVE-2026-75915 and 200,000+ other known vulnerabilities.

Severity & Exploitability

CVSS Score
7.5

High exploitability or significant impact. Prioritize remediation within days.

Also Known As

GHSA-h539-c7r8-3xq4

Related CVEs

  • CVE-2026-75856
    CRITICAL

    CodeWhale: SSRF bypass - TOCTOU on DNS failure for DNS pinning

  • CVE-2026-75859
    HIGH

    CodeWhale: Project config `instructions` override enables arbitrary file read into AI system prompt via cloned repository

  • CVE-2026-75913
    HIGH

    CodeWhale: Argument Injection in `git_show` Tool Allows Arbitrary File Write Without Approval

  • CVE-2026-75912
    HIGH

    CodeWhale: Argument Injection in `git_blame` Tool Allows Arbitrary File Read Without Approval

  • CVE-2026-75857
    HIGH

    CodeWhale: exec_shell_interact sends LLM-controlled input to a running shell without an approval prompt (privilege escalation)

  • CVE-2026-75911
    HIGH

    CodeWhale: Project config `allow_shell` override enables arbitrary shell command execution via cloned repository

  • CVE-2026-75914
    HIGH

    CodeWhale: image_analyze follows workspace symlinks, leaking external file bytes

  • CVE-2026-75858
    HIGH

    CodeWhale: rlm_eval auto-approves arbitrary Python execution, bypassing the user's approval policy (RCE)

Check if you're affected

Scan your dependencies to see if this vulnerability affects your projects.

Scan Your Dependencies