GHSA-2c7f-fxww-6w6c yutu: Arbitrary File Write via MCP `caption-download` Tool
Arbitrary File Write via MCP `caption-download` ToolSummaryThe `caption-download` MCP tool in yutu passes the caller-supplied `file` parameter directly to `os.Create()` at `pkg/caption/caption.go:272` without any path validation, canonicalization, or confinement to the `pkg.Root` boundary (`YUTU_ROOT`). A local attacker — or any process able to reach the HTTP MCP server — can write arbitrary content to any path writable by the yutu process, entirely outside the intended working directory. This is a **High** severity vulnerability (CVSS 7.7) with high integrity and availability impact.Detailsyutu uses `pkg.Root` (backed by Go 1.24's `os.OpenRoot`) to restrict all file I/O to the `YUTU_ROOT` directory. Every other caption file-write path honours this boundary:| Method | Sink | Confined? |
|--------|------|-----------|
| `Caption.Insert()` | `pkg.Root.Open(c.File)` (`caption.go:109`) | Yes |
| `Caption.Update()` | `pkg.Root.Open(c.File)` (`caption.go:193`) | Yes |
| `Caption.Download()` | `os.Create(c.File)` (`caption.go:272`) | **No** |`Caption.Download()` is the sole outlier. The attacker-controlled `file` field flows without restriction from the MCP tool input schema to a raw `os.Create()` call:**Source** — `cmd/caption/download.go:32–41`: `downloadInSchema` declares `file` as a required `string` field in the MCP JSON input schema.**Binding** — `cmd/caption/download.go:61–64`: `cobramcp.GenToolHandler` maps MCP input to `input.Download(writer)`.**Sink** — `pkg/caption/caption.go:272`: `os.Create(c.File)` creates or truncates the file at the attacker-supplied path.**Write** — `pkg/caption/caption.go:280`: `file.Write(body)` writes the downloaded caption bytes to that path.The `caption-download` tool is registered by default in `init()` at `cmd/caption/download.go:52`, and the HTTP MCP server starts with `--auth` defaulting to `false` (`cmd/mcp.go:42`), meaning no authentication is required for local HTTP callers.**Recommended fix:**PoC**Prerequisites:**yutu `0.0.0-dev` / commit `351c99d`Valid `YUTU_CREDENTIAL` and `YUTU_CACHE_TOKEN` availableyutu MCP server running in HTTP mode**Docker-based reproduction (no live credentials needed):**The self-contained PoC builds a binary that exercises `caption.Download()` directly inside a container, with `YUTU_ROOT=/tmp/yutu_safe_root` as the confinement boundary.Expected output confirms:`pkg.Root.Open("/tmp/poc-arbitrary-write.txt")` is correctly rejected with `path escapes from parent` (control).`caption.Download()` with `file="/tmp/poc-arbitrary-write.txt"` succeeds and creates a 79-byte file **outside** `YUTU_ROOT` (exploit).**Live MCP server reproduction:**ImpactThis is an **Arbitrary File Write** vulnerability. Any principal that can invoke the `caption-download` MCP tool — including an unauthenticated local process when the HTTP MCP server is running with default settings (`--auth false`) — can write attacker-controlled bytes to any file path accessible to the yutu process. This bypasses the `YUTU_ROOT` confinement boundary that all other file-write operations in yutu respect.**Potential consequences include:**Overwriting application binaries, configuration files, or shell startup scripts to achieve persistent code execution.Corrupting log files or database files to cause denial of service.Writing web-accessible files in deployments where yutu runs alongside a web server.Exploitable via prompt injection into an AI agent that uses the yutu MCP server, since the `file` parameter is fully attacker-controlled with no guardrails.Impacted parties: operators running yutu as an MCP server (HTTP mode, default configuration), AI agent pipelines that expose `caption-download` to untrusted input, and any user whose machine hosts a yutu process that a local attacker can reach.Reproduction artifacts`Dockerfile``poc.py`
CVSS v3.1 score: 7.7