T08 · Insecure Dependencies
- Location
scripts/setup_chrome_mcp.py:43- Finding
Mutable npm Dependency Is Downloaded and Executed Without Version or Integrity Pinning
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill is not clearly malicious, but it should be reviewed because it persistently runs a mutable browser-control MCP package with broad access to Chrome sessions.
Install only if you trust the external npm package and are comfortable giving an agent control over the browser pages you expose. Prefer pinning a reviewed exact version instead of @latest, run it with a fresh or isolated Chrome profile, keep --no-usage-statistics and --no-performance-crux when privacy matters, and avoid connecting it to sessions with sensitive accounts or data. Upstream documentation also notes default usage statistics, optional CrUX behavior, and update checks. ([github.com](https://github.com/ChromeDevTools/chrome-devtools-mcp?utm_source=openai))
scripts/setup_chrome_mcp.py:43Mutable npm Dependency Is Downloaded and Executed Without Version or Integrity Pinning
The description claims the skill is the Chrome DevTools MCP browser automation/testing server itself, with capabilities like clicking, navigating, screenshots, tracing, and debugging. However, this code chunk does not implement those browser-control features. Instead, it is an auxiliary setup/status/test utility for installing and verifying availability of the external chrome-devtools-mcp package and checking local configuration. That is a materially different primary purpose from the declared browser automation server behavior, so this is a description/behavior mismatch.
This is a genuine tool-parameter abuse risk because the helper is designed to execute arbitrary shell command strings through subprocess.run(..., shell=True). In an agent or automation context, such a generic wrapper can easily become a dangerous sink if later wired to external input, enabling arbitrary OS command execution.
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"
The skill exposes shell-capable behavior through installation and setup commands but does not declare any tool scope or allowed-tools boundaries. In an agent environment, missing explicit permission scoping increases the chance that the skill can invoke shell operations more broadly than users expect, weakening sandboxing and review controls.
Using npx with an unpinned package reference causes the environment to fetch whatever version is current at execution time, creating a supply-chain risk. If the upstream package, a dependency, or the publishing account is compromised, agents may execute attacker-controlled code automatically.
This command launches a browser-control MCP server from an unpinned npm package, so the code executed can change over time without review. Because the server gains powerful browser automation capabilities, a compromised release could exfiltrate data, drive authenticated sessions, or run arbitrary postinstall/runtime logic.
Connecting to an existing browser via a package resolved at runtime still requires trusting remote npm content at execution time. Since the package mediates access to a live Chrome debugging endpoint, compromise of the package could result in interception of page data, cookies, network traffic, and browser actions.
The OpenClaw integration example embeds an unpinned npx package into persistent configuration, making future executions silently consume whatever release is newest. This turns a one-time documentation issue into a recurring supply-chain exposure across all later agent sessions.
The helper wraps subprocess.run with shell=True and accepts an arbitrary cmd string, creating a reusable command-execution sink. Although current call sites are constant strings, this pattern is dangerous because future changes or indirect input flow could turn it into shell injection, and shell metacharacters would be interpreted by the system shell.
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"
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.
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.
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.
The script executes chrome-devtools-mcp@latest via npx, which fetches and runs the newest package version at runtime. Using @latest creates a supply-chain risk: behavior can change unexpectedly, a compromised upstream release would execute immediately, and the script's setup role makes users more likely to trust and run it.
A setup/status helper is expected to inspect configuration or print instructions, but here it actively executes remote package code through npx during setup. That broadens trust requirements and creates unexpected code-execution risk in a script users may run for simple environment checks.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# 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
)
No suspicious patterns detected.