Vulnerabilities in pkg:npm/chrome-devtools-mcp

PACKAGE
Advisory Identifier: GHSA-8qf9-62x2-82pp
Advisory Summary

CVE-2026-53766 is a MODERATE (CVSS 6.1) validatepath() does not canonicalize symlinks vulnerability (CWE-59: Improper Link Resolution Before File Access ('Link Following')) affecting chrome-devtools-mcp libraries in the npm ecosystem. The issue involves validatepath() does not canonicalize symlinks before enforcing roots. Remediation is available by upgrading affected packages to version 1.1.0.

2 Known Advisory Record(s)
Package Vulnerability Intelligence

pkg:npm/chrome-devtools-mcp

2 Known Advisory Record(s)

Vulnerability advisories affecting package pkg:npm/chrome-devtools-mcp. Select an advisory below to view technical details or fixes.

Advisory ID CVE / NVD Severity Summary
GHSA-8qf9-62x2-82pp CVE-2026-53766 MODERATE chrome-devtools-mcp: validatePath() does not canonicalize symlinks before enforcing roots
GHSA-3pvj-jv98-qhjq CVE-2026-53765 MODERATE Chrome DevTools for agents: daemon.pid write follows symlinks in /tmp fallback runtime directory

CVE-2026-53766 at a Glance

CVE Identifier CVE-2026-53766
GHSA Identifier GHSA-8qf9-62x2-82pp
Severity MODERATE
CVSS v3.1 Base Score 6.1
CWE Classification CWE-59: Improper Link Resolution Before File Access ('Link Following')
Affected Product chrome-devtools-mcp
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 6.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L).

Attack Vector Local (AV:L)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity High (I:H)
Availability Low (A:L)

How to Fix CVE-2026-53766

To remediate this vulnerability, upgrade all affected chrome-devtools-mcp dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

chrome-devtools-mcp fixed: 1.1.0
npm install chrome-devtools-mcp@1.1.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls chrome-devtools-mcp

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-53766, inspect your project dependencies using npm:

npm ls chrome-devtools-mcp

Analyze the terminal output to locate any version of chrome-devtools-mcp in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.1.0), your application is affected.

Note: The npm ls chrome-devtools-mcp command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-59: Improper Link Resolution Before File Access ('Link Following'), where the product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource. In chrome-devtools-mcp, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary I 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. ### Details Affected code: `src/McpContext.ts:178-199` ```ts validatePath(filePath?: string): void { if (filePath === undefined) { return; } const roots = this.roots(); if (roots === undefined) { return; } const absolutePath = path.resolve(filePath); for (const root of roots) { const rootPath = path.resolve(fileURLToPath(root.uri)); if ( absolutePath === rootPath || absolutePath.startsWith(rootPath + path.sep) ) { return; } } throw new Error( `Access denied: path ${filePath} is not within any of the workspace roots ${JSON.stringify(roots)}.`, ); } ``` `path.resolve()` only normalizes path text such as `.` and `..`. It does not call `realpath()` and does not resolve symlinks. Therefore, a path like: ```text /workspace/project/cache/profile ``` can textually pass the `/workspace` prefix check even when `cache/profile` is a symlink to: ```text /home/user/.aws/credentials ``` 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. ### PoC Conceptual exploitation with a configured root: ```text Configured roots: file:///workspace Workspace path: /workspace/project/cache/profile -> /home/user/.aws/credentials Tool call: upload_file({ filePath: "/workspace/project/cache/profile", uid: "<file input element on current page>" }) Result: validatePath() accepts the path because it textually starts with /workspace. Puppeteer follows the symlink and uploads the target file to the page. ``` 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: ```js const path = require('node:path'); const fs = require('node:fs'); const os = require('node:os'); const {pathToFileURL, fileURLToPath} = require('node:url'); const lab = fs.mkdtempSync(path.join(os.tmpdir(), 'cdtmcp-lab-')); try { fs.chmodSync(lab, 0o755); const workspace = path.join(lab, 'workspace'); fs.mkdirSync(workspace); const outside = path.join(lab, 'outside-secret.txt'); fs.writeFileSync(outside, 'sensitive outside content\n'); const symlinkInside = path.join(workspace, 'innocent.txt'); fs.symlinkSync(outside, symlinkInside); function validatePath(filePath, roots) { const absolutePath = path.resolve(filePath); for (const root of roots) { const rootPath = path.resolve(fileURLToPath(root.uri)); if ( absolutePath === rootPath || absolutePath.startsWith(rootPath + path.sep) ) { return true; } } throw new Error(`Access denied: ${filePath}`); } const roots = [{uri: pathToFileURL(workspace).href, name: 'workspace'}]; validatePath(symlinkInside, roots); fs.writeFileSync(symlinkInside, 'OVERWRITTEN BY MCP\n'); console.log(fs.readFileSync(outside, 'utf8')); // -> "OVERWRITTEN BY MCP" } finally { fs.rmSync(lab, {recursive: true, force: true}); } ``` Observed result: ```text validatePath() accepts the in-root symlink path. The subsequent write follows the symlink and modifies the out-of-root target. ``` 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. ### Impact Who 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. ```ts async validatePath(filePath?: string): Promise<void> { if (filePath === undefined) return; const roots = this.roots(); if (roots === undefined) return; const abs = path.resolve(filePath); let canonical; try { canonical = await fs.realpath(abs); } catch (err) { if (err.code === 'ENOENT') { const parent = await fs.realpath(path.dirname(abs)); canonical = path.join(parent, path.basename(abs)); } else { throw err; } } for (const root of roots) { const canonicalRoot = await fs.realpath(fileURLToPath(root.uri)); if ( canonical === canonicalRoot || canonical.startsWith(canonicalRoot + path.sep) ) { return; } } throw new Error( `Access denied: ${filePath} (canonical: ${canonical}) is not within any workspace root.`, ); }

CVE-2026-53766 Affected chrome-devtools-mcp Versions

1 component(s)
Fixed Versions:
1.1.0
Product / Package Ecosystem Affected Versions Fixed Version
chrome-devtools-mcp
npm
≥ 0.24.0
< 1.1.0
1.1.0

References

Frequently Asked Questions

What is CVE-2026-53766?
CVE-2026-53766 is a security vulnerability in chrome-devtools-mcp involving validatePath() does not canonicalize symlinks before enforcing roots under CWE-59: Improper Link Resolution Before File Access ('Link Following').
Which chrome-devtools-mcp versions are affected?
Affected versions include releases prior to 1.1.0 across affected packages: chrome-devtools-mcp.
What version fixes CVE-2026-53766?
Upgrading to 1.1.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is chrome-devtools-mcp affected?
Yes, chrome-devtools-mcp is affected in releases prior to 1.1.0. Upgrade your dependency to 1.1.0 or later.
What is the CVSS score of CVE-2026-53766?
CVE-2026-53766 has a CVSS v3.1 base score of 6.1 (MODERATE) with vector string CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L.
What CWE is associated with CVE-2026-53766?
CVE-2026-53766 is classified under CWE-59: Improper Link Resolution Before File Access ('Link Following') (The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.).
How can I check npm dependencies for CVE-2026-53766?
Run the dependency tree command: 'npm ls chrome-devtools-mcp'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-8qf9-62x2-82pp",
  "modified" : "2026-08-17T21:56:12Z",
  "published" : "2026-08-17T21:56:12Z",
  "aliases" : [ "CVE-2026-53766" ],
  "summary" : "chrome-devtools-mcp: validatePath() does not canonicalize symlinks before enforcing roots",
  "details" : "### Summary\n\nI 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.\n\n`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.\n\nThis 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.\n\nThe 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.\n\n### Details\n\nAffected code:\n\n`src/McpContext.ts:178-199`\n\n```ts\nvalidatePath(filePath?: string): void {\n  if (filePath === undefined) {\n    return;\n  }\n  const roots = this.roots();\n  if (roots === undefined) {\n    return;\n  }\n  const absolutePath = path.resolve(filePath);\n  for (const root of roots) {\n    const rootPath = path.resolve(fileURLToPath(root.uri));\n    if (\n      absolutePath === rootPath ||\n      absolutePath.startsWith(rootPath + path.sep)\n    ) {\n      return;\n    }\n  }\n  throw new Error(\n    `Access denied: path ${filePath} is not within any of the workspace roots ${JSON.stringify(roots)}.`,\n  );\n}\n```\n\n`path.resolve()` only normalizes path text such as `.` and `..`. It does not call `realpath()` and does not resolve symlinks. Therefore, a path like:\n\n```text\n/workspace/project/cache/profile\n```\n\ncan textually pass the `/workspace` prefix check even when `cache/profile` is a symlink to:\n\n```text\n/home/user/.aws/credentials\n```\n\nDownstream consumers then perform real filesystem operations without `O_NOFOLLOW`:\n\n- `src/McpContext.ts:720-738` `saveFile()` uses `fs.mkdir({recursive: true})` and `fs.writeFile()`.\n- `src/tools/input.ts:454-497` `upload_file` calls `puppeteer.uploadFile(filePath)` or `fileChooser.accept([filePath])`.\n- Other filePath-writing tools include screenshots, heap snapshots, network response save paths, snapshots, screencasts, Lighthouse output, and performance trace saves.\n\nThis 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.\n\nPreconditions:\n\n- The MCP client declares `roots` and supplies at least one workspace root.\n- A symlink exists inside the workspace and points outside the workspace.\n- For the remote prompt-injection chain, the user processes untrusted page content while chrome-devtools-mcp is connected.\n\nA 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.\n\n### PoC\n\nConceptual exploitation with a configured root:\n\n```text\nConfigured roots:\n  file:///workspace\n\nWorkspace path:\n  /workspace/project/cache/profile -> /home/user/.aws/credentials\n\nTool call:\n  upload_file({\n    filePath: \"/workspace/project/cache/profile\",\n    uid: \"<file input element on current page>\"\n  })\n\nResult:\n  validatePath() accepts the path because it textually starts with /workspace.\n  Puppeteer follows the symlink and uploads the target file to the page.\n```\n\nLab-only PoC that replicates the exact validation logic and subsequent write. It writes only inside a fresh temporary directory and touches no system paths:\n\n```js\nconst path = require('node:path');\nconst fs = require('node:fs');\nconst os = require('node:os');\nconst {pathToFileURL, fileURLToPath} = require('node:url');\n\nconst lab = fs.mkdtempSync(path.join(os.tmpdir(), 'cdtmcp-lab-'));\n\ntry {\n  fs.chmodSync(lab, 0o755);\n\n  const workspace = path.join(lab, 'workspace');\n  fs.mkdirSync(workspace);\n\n  const outside = path.join(lab, 'outside-secret.txt');\n  fs.writeFileSync(outside, 'sensitive outside content\\n');\n\n  const symlinkInside = path.join(workspace, 'innocent.txt');\n  fs.symlinkSync(outside, symlinkInside);\n\n  function validatePath(filePath, roots) {\n    const absolutePath = path.resolve(filePath);\n    for (const root of roots) {\n      const rootPath = path.resolve(fileURLToPath(root.uri));\n      if (\n        absolutePath === rootPath ||\n        absolutePath.startsWith(rootPath + path.sep)\n      ) {\n        return true;\n      }\n    }\n    throw new Error(`Access denied: ${filePath}`);\n  }\n\n  const roots = [{uri: pathToFileURL(workspace).href, name: 'workspace'}];\n  validatePath(symlinkInside, roots);\n\n  fs.writeFileSync(symlinkInside, 'OVERWRITTEN BY MCP\\n');\n\n  console.log(fs.readFileSync(outside, 'utf8'));\n  // -> \"OVERWRITTEN BY MCP\"\n} finally {\n  fs.rmSync(lab, {recursive: true, force: true});\n}\n```\n\nObserved result:\n\n```text\nvalidatePath() accepts the in-root symlink path.\nThe subsequent write follows the symlink and modifies the out-of-root target.\n```\n\nI 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.\n\n### Impact\n\nWho can exploit:\n\n- A local process/user or trusted workflow that can create a symlink inside the workspace.\n- A remote page/prompt-injection attacker, if a suitable workspace-internal symlink already exists or can be created by another trusted workflow/tool.\n\nSecurity impact:\n\n- Integrity: tools that write to `filePath` can overwrite files outside the configured workspace root through an in-root symlink.\n- 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.\n- 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.\n\nExample sensitive files reachable if symlinked into the workspace:\n\n- Cloud credentials such as `~/.aws/credentials`, `~/.config/gcloud/...`, or `~/.azure/...`.\n- SSH private keys or `.ssh` files readable by the user.\n- Project secrets such as `.env`, `.npmrc`, `.netrc`, `secrets.json`, and API tokens.\n- Out-of-workspace source files or configuration files.\n\nSeverity:\n\n- Suggested GitHub severity: Moderate.\n- CVSS v3.1 chain estimate: `CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N`.\n- AC:H reflects that a workspace-internal symlink must exist at validation time.\n\nSuggested fix:\n\nCanonicalize 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.\n\n```ts\nasync validatePath(filePath?: string): Promise<void> {\n  if (filePath === undefined) return;\n  const roots = this.roots();\n  if (roots === undefined) return;\n\n  const abs = path.resolve(filePath);\n  let canonical;\n  try {\n    canonical = await fs.realpath(abs);\n  } catch (err) {\n    if (err.code === 'ENOENT') {\n      const parent = await fs.realpath(path.dirname(abs));\n      canonical = path.join(parent, path.basename(abs));\n    } else {\n      throw err;\n    }\n  }\n\n  for (const root of roots) {\n    const canonicalRoot = await fs.realpath(fileURLToPath(root.uri));\n    if (\n      canonical === canonicalRoot ||\n      canonical.startsWith(canonicalRoot + path.sep)\n    ) {\n      return;\n    }\n  }\n\n  throw new Error(\n    `Access denied: ${filePath} (canonical: ${canonical}) is not within any workspace root.`,\n  );\n}",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "chrome-devtools-mcp"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.24.0"
      }, {
        "fixed" : "1.1.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 1.0.1"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/ChromeDevTools/chrome-devtools-mcp/security/advisories/GHSA-8qf9-62x2-82pp"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-53766"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/ChromeDevTools/chrome-devtools-mcp/pull/2127"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/176eb695137d9c46a61e2d4d5571880c5145cf46"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/ChromeDevTools/chrome-devtools-mcp"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/ChromeDevTools/chrome-devtools-mcp/releases/tag/chrome-devtools-mcp-v1.1.0"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-22", "CWE-59" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-08-17T21:56:12Z",
    "nvd_published_at" : "2026-06-24T22:16:47Z"
  }
}