GHSA-8qf9-62x2-82pp chrome-devtools-mcp: validatePath() does not canonicalize symlinks before enforcing roots
SummaryI originally reported this through Google Bug Hunters. The Google Bug Hunters team said this is in OSS VRP scope but not reward-eligible due to the project tier, and asked me to file an issue or PR directly with this repository. I am reporting it privately here first because it is an unfixed security issue.`McpContext.validatePath()` enforces workspace `roots` by checking whether `path.resolve(filePath)` textually falls under one of the configured root paths. `path.resolve()` does not canonicalize symbolic links. As a result, a symlink inside a configured workspace root can point to a file outside that root, pass validation, and then be followed by downstream file read/write operations.This bypass applies even when the MCP client correctly declares the `roots` capability with a non-empty list. It is separate from the documented legacy behavior where missing `roots` capability allows all paths.The practical impact is a workspace-boundary bypass. In the write direction, filePath-writing tools can overwrite out-of-root files through an in-root symlink. In the read direction, `upload_file` can read through the symlink and send the file to the currently selected web page.DetailsAffected code:`src/McpContext.ts:178-199``path.resolve()` only normalizes path text such as `.` and `..`. It does not call `realpath()` and does not resolve symlinks. Therefore, a path like:can textually pass the `/workspace` prefix check even when `cache/profile` is a symlink to:Downstream consumers then perform real filesystem operations without `O_NOFOLLOW`:`src/McpContext.ts:720-738` `saveFile()` uses `fs.mkdir({recursive: true})` and `fs.writeFile()`.`src/tools/input.ts:454-497` `upload_file` calls `puppeteer.uploadFile(filePath)` or `fileChooser.accept([filePath])`.Other filePath-writing tools include screenshots, heap snapshots, network response save paths, snapshots, screencasts, Lighthouse output, and performance trace saves.This is not a TOCTOU/race condition. The symlink exists before validation and the PoC uses a single process. The issue is a canonicalization bypass / improper link resolution.Preconditions:The MCP client declares `roots` and supplies at least one workspace root.A symlink exists inside the workspace and points outside the workspace.For the remote prompt-injection chain, the user processes untrusted page content while chrome-devtools-mcp is connected.A remote attacker does not need local access if a suitable workspace-internal symlink already exists, or if another trusted tool/workflow can create it. Without such a symlink, the issue is a local/workspace-state-dependent boundary bypass.PoCConceptual exploitation with a configured root:Lab-only PoC that replicates the exact validation logic and subsequent write. It writes only inside a fresh temporary directory and touches no system paths:Observed result:I can provide an end-to-end MCP client reproduction if needed. The lab PoC above demonstrates the root cause using the same validation logic as the server.ImpactWho can exploit:A local process/user or trusted workflow that can create a symlink inside the workspace.A remote page/prompt-injection attacker, if a suitable workspace-internal symlink already exists or can be created by another trusted workflow/tool.Security impact:Integrity: tools that write to `filePath` can overwrite files outside the configured workspace root through an in-root symlink.Confidentiality: `upload_file` can read a file outside the workspace through an in-root symlink and attach it to a file input on the current page.Stealth/auditability: the exfiltration path goes through normal page file-upload behavior, and chrome-devtools-mcp does not appear to log the canonical path that was uploaded.Example sensitive files reachable if symlinked into the workspace:Cloud credentials such as `~/.aws/credentials`, `~/.config/gcloud/...`, or `~/.azure/...`.SSH private keys or `.ssh` files readable by the user.Project secrets such as `.env`, `.npmrc`, `.netrc`, `secrets.json`, and API tokens.Out-of-workspace source files or configuration files.Severity:Suggested GitHub severity: Moderate.CVSS v3.1 chain estimate: `CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N`.AC:H reflects that a workspace-internal symlink must exist at validation time.Suggested fix:Canonicalize paths before comparing against roots. For an existing file, use `fs.realpath()` on the path. For a new file, resolve the parent directory with `fs.realpath()` and re-join the basename.
CVSS v3.1 score: 6.1