Back to skill

Security audit

Luke Chrome Devtools Mcp

Security checks for vulnerabilities and agentic risk

Overview

The skill appears purpose-aligned, but it gives agents powerful browser control while repeatedly downloading and running an unpinned npm package, so it should be reviewed before installation.

Install only if you are comfortable giving an agent control over a Chrome session. Prefer a pinned, reviewed chrome-devtools-mcp version instead of @latest, run it in an isolated browser profile or container, avoid logged-in or sensitive browsing sessions, and disable telemetry/CrUX features when privacy matters.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T08 · Insecure Dependencies

Error
Location
scripts/setup_chrome_mcp.py:39
Finding
Unpinned Remote npm Package Download and Execution<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup_chrome_mcp.py:39`, `scripts/setup_chrome_mcp.py:70-77`, `scripts/setup_chrome_mcp.py:131-136`; `SKILL.md:34-38`, `SKILL.md:42-52`, `SKILL.md:56-64` **Vulnerability Type**: Unpinned third-party dependency execution **Risk Level**: High ### Vulnerable Code Snippets `scripts/setup_chrome_mcp.py:39`: ```python code, out, err = run("npx -y chrome-devtools-mcp@latest --help", timeout=60) ``` `scripts/setup_chrome_mcp.py:70-77`: ```python config = { "mcp": { "servers": { "chrome-devtools": { "command": "npx", "args": ["-y", "chrome-devtools-mcp@latest", "--headless", "--no-usage-statistics"] } } } } ``` `scripts/setup_chrome_mcp.py:131-136`: ```python proc = subprocess.Popen( ["npx", "-y", "chrome-devtools-mcp@latest", "--headless", "--no-usage-statistics"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) ``` `SKILL.md:34-38`: ```bash npx -y chrome-devtools-mcp@latest --help ``` `SKILL.md:42-52`: ```bash # Standard (launches Chrome automatically) npx -y chrome-devtools-mcp@latest # Headless mode (for servers) npx -y chrome-devtools-mcp@latest --headless # Connect to existing Chrome (must be started with --remote-debugging-port=9222) npx -y chrome-devtools-mcp@latest --browser-url=http://127.0.0.1:9222 # Disable telemetry npx -y chrome-devtools-mcp@latest --no-usage-statistics --no-performance-crux ``` `SKILL.md:56-64`: ```json { "mcp": { "servers": { "chrome-devtools": { "command": "npx", "args": ["-y", "chrome-devtools-mcp@latest", "--headless", "--no-usage-statistics"] } } } } ``` ### Technical Analysis The setup script, test command, documentation, and generated MCP configuration all invoke `chrome-devtools-mcp@latest`. The `latest` npm tag is mutable and therefore does not identify the exact package version that was reviewed. The `-y` option ...[truncated 2470 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace every use of `chrome-devtools-mcp@latest` with an exact, reviewed version, for example: ```python CHROME_DEVTOOLS_MCP_VERSION = "X.Y.Z" package = f"chrome-devtools-mcp@{CHROME_DEVTOOLS_MCP_VERSION}" ``` The same exact version must be used in setup, testing, documentation, and generated OpenClaw configuration. 2. Do not use version ranges, mutable tags, or automatically advancing aliases. Upgrade only through an explicit review process. 3. Maintain a lockfile where the deployment model supports it, commit it to the reviewed package, and use deterministic installation such as `npm ci`. 4. Verify package integrity against a trusted, reviewed lockfile or expected registry integrity hash before execution. Treat integrity changes as requiring security review. 5. Separate dependency acquisition from runtime execution. Install the reviewed version during a controlled setup phase and configure OpenClaw to invoke the locally installed, verified executable rather than causing `npx` to resolve a remote package on every launch. 6. Run the MCP server with least privilege in an isolated account or container. Restrict filesystem access, inherited environment variables, network access, and access to sensitive browser profiles. 7. Add automated checks that reject `@latest`, wildcard versions, and other unpinned executable dependencies in scripts, documentation, and generated configurations. 8. Document a controlled upgrade procedure that includes source review, provenance and integrity validation, functional testing, and rollback to the previously approved version. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (12)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
def run(cmd, capture=True, timeout=30):
    try:
        r = subprocess.run(cmd, shell=True, capture_output=capture, text=True, timeout=timeout)
        return r.returncode, r.stdout.strip() if capture else "", r.stderr.strip() if capture else ""
    except subprocess.TimeoutExpired:
        return 1, "", "timeout"
Confidence
99% confidence
Finding
Using shell=True in a reusable command runner is dangerous because any future non-constant input passed into cmd can become shell syntax and execute unintended commands. In a setup utility for developer tooling, this is especially risky because the script is intended to run on user workstations with meaningful local privileges and access to environment variables, files, and credentials.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill exposes shell execution pathways through setup and launch commands but does not declare any tool scope restrictions such as permissions or allowed-tools. In an agent environment, this increases the chance that the skill can invoke broader command execution than reviewers or users expect, especially given it launches a browser automation server with powerful local capabilities.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
Using an unpinned package reference allows the installed MCP server version to change over time, including pulling compromised, malicious, or breaking releases at execution time. Because this skill grants browser control and can interact with local files, network requests, and page scripting, a supply-chain compromise could lead to significant agent or host impact.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
The command starts the MCP server from the latest available package rather than a vetted fixed release. This creates a supply-chain risk where future upstream changes or a compromised publication could execute arbitrary code in the environment when the skill is run.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
Connecting to an existing browser with an unpinned MCP package still relies on fetching and executing whatever package version is current at runtime. Since the tool can inspect traffic, run page JavaScript, and automate browser actions, any malicious update would have broad access within the browsing session.

Rp1

Medium
Category
MCP Rug Pull
Confidence
98% confidence
Finding
The OpenClaw integration example instructs users to configure `chrome-devtools-mcp@latest`, causing every deployment to trust the newest registry version automatically. In a long-lived agent platform, this is especially dangerous because the MCP server has extensive browser automation and observation capabilities, magnifying the impact of a compromised release.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd, capture=True, timeout=30):
    try:
        r = subprocess.run(cmd, shell=True, capture_output=capture, text=True, timeout=timeout)
        return r.returncode, r.stdout.strip() if capture else "", r.stderr.strip() if capture else ""
    except subprocess.TimeoutExpired:
        return 1, "", "timeout"
Confidence
98% confidence
Finding
The helper wraps subprocess.run with shell=True, which executes the provided string through the shell. Although current call sites use hardcoded commands, this utility makes future or indirect command injection far easier, and also exposes execution to PATH manipulation and shell metacharacter interpretation.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
99% confidence
Finding
The script executes chrome-devtools-mcp@latest via npx, which fetches and runs whatever package version is current at execution time. That creates a real supply-chain risk: a compromised upstream package or breaking change could lead to arbitrary code execution on the host during setup.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Try starting with --headless
    print("Starting MCP server (headless)...")
    proc = subprocess.Popen(
        ["npx", "-y", "chrome-devtools-mcp@latest", "--headless", "--no-usage-statistics"],
        stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
    )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.