T08 · Insecure Dependencies
- Location
scripts/setup_chrome_mcp.py:39- Finding
Unpinned npm Package Is Automatically Downloaded and Executed
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This is a real Chrome browser automation integration, but it needs Review because it runs a mutable external package with broad browser-control authority.
Install only if you are comfortable with a browser-control MCP server and can pin or preinstall a reviewed chrome-devtools-mcp version. Avoid using it on sensitive logged-in sessions, financial/admin workflows, or production sites unless you explicitly authorize each action and understand that page content and browser actions may be exposed to the agent/server.
scripts/setup_chrome_mcp.py:39Unpinned npm Package Is Automatically Downloaded and Executed
The description claims the skill is the Chrome DevTools MCP server used to control Chrome and perform browser automation/testing tasks. However, this code chunk does not implement those browser-control capabilities. Instead, it acts as an installer/status checker/test harness for the external chrome-devtools-mcp package. It checks local dependencies, searches for Chrome binaries, prints configuration for openclaw.json, reads ~/.openclaw/openclaw.json to inspect MCP configuration, and briefly launches the MCP server process to see if it starts. That is a materially different primary purpose from being the automation server itself. While these behaviors are related support tooling, the declared description specifically represents the skill as the browser automation/testing server, which this code chunk is not.
Using shell=True in a reusable command wrapper is dangerous because it turns any caller-provided string into a shell command, enabling metacharacter expansion, chaining, redirection, and environment/PATH manipulation. In an agent skill setup script, this becomes more sensitive because such scripts are often run in semi-automated environments where future modifications may pass external input into the wrapper.
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 documents shell-based installation and execution of an MCP server but does not declare any explicit tool scope or allowed-tools restrictions. In an agent environment, undeclared shell capability increases the chance that the skill can be invoked with broader-than-expected execution privileges, reducing policy enforceability and auditability.
The description is broad enough to encourage invocation in many contexts without clear constraints, while the underlying capability grants full browser control. In an agent setting, vague triggering language can lead to overuse on live sites, authenticated sessions, or sensitive internal applications where automated clicks, form fills, and script execution are high-risk.
The skill advertises powerful browser actions—clicking, form filling, file upload, script execution, and dialog handling—but does not clearly warn that these actions can modify live accounts, submit transactions, change settings, or leak session-bound data. Because the context is full browser automation, missing safety guidance materially increases the chance of destructive or privacy-impacting misuse.
Using npx with @latest causes the skill to fetch and execute whatever package version is current at runtime, which creates a supply-chain risk and defeats reproducibility. If the upstream package or one of its dependencies is compromised, the agent could execute attacker-controlled code immediately during installation or launch.
This invocation also relies on @latest, meaning the executed code can change over time without review. In an automated agent workflow, dynamic package resolution materially increases the risk of remote code execution through a malicious or newly vulnerable upstream release.
Connecting to an existing browser does not reduce the package execution risk introduced by npx @latest; the package itself is still fetched and run dynamically. That creates a persistent supply-chain exposure in addition to the browser-control capability described by the skill.
The OpenClaw integration example embeds npx @latest directly into persistent configuration, which normalizes repeated execution of unpinned remote code. This is especially risky because MCP servers typically run with broad automation powers and may access browser sessions, page content, and local resources exposed to the agent.
The helper uses subprocess.run with shell=True on arbitrary command strings. Even though current call sites are hardcoded, this pattern creates a command-injection sink: any future use with user-controlled or config-derived input could execute unintended shell metacharacters, and shell resolution also broadens PATH-based hijack risk.
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 'npx -y chrome-devtools-mcp@latest --help', which fetches and runs the latest package version at setup time. Using @latest creates a supply-chain risk: a compromised upstream release, typo-squatted dependency path, or unexpected breaking change would be executed immediately on the host.
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
)