Back to skill

Security audit

Browser Automation Core

Security checks for vulnerabilities and agentic risk

Overview

This is a real browser automation library, but it needs Review because it combines broad browser/session authority with concrete unsafe network and file-write behaviors.

Install only if you are comfortable with a skill that can drive browser sessions, capture page contents, submit forms, and write screenshots locally. Use it in a constrained environment, avoid authenticated or sensitive sites unless necessary, pin install versions, restrict CDP to localhost, and treat screenshot filenames and URLs as untrusted inputs until the package adds validation and consent controls.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
lib/browser_core.py:31
Finding
Unrestricted CDP Endpoint Enables SSRF and Attacker-Controlled WebSocket Connections## Vulnerability Details **File Locations**: - `lib/browser_core.py:31,45-50,55-61` - `lib/browser_core_fixed.py:23,38-43,47-58` **Vulnerability Type**: Server-Side Request Forgery and untrusted WebSocket endpoint connection **Risk Level**: High ### Vulnerable Code `lib/browser_core.py`: ```python self.cdp_http_url = self.config.get( "cdp_http_url", "http://localhost:18800/json" ) def _get_websocket_url(self) -> Optional[str]: """Get WebSocket URL from CDP HTTP endpoint.""" import requests try: response = requests.get(self.cdp_http_url, timeout=5) targets = response.json() for target in targets: if target.get("type") == "page": return target["webSocketDebuggerUrl"] return None except Exception as e: logger.error(f"Failed to get WebSocket URL: {e}") return None def connect(self) -> bool: """Connect to browser via WebSocket.""" ws_url = self._get_websocket_url() if not ws_url: logger.error("No WebSocket URL available") return False try: self.ws = websocket.create_connection(ws_url, timeout=self.timeout) ``` `lib/browser_core_fixed.py`: ```python self.cdp_http_url = self.config.get( "cdp_http_url", "http://localhost:18800/json" ) def _get_websocket_url(self) -> Optional[str]: """Get WebSocket URL from CDP HTTP endpoint.""" try: response = requests.get(self.cdp_http_url, timeout=5) targets = response.json() for target in targets: if target.get("type") == "page": return target["webSocketDebuggerUrl"] return None except Exception as e: logger.error(f"Failed to get WebSocket URL: {e}") return None def connect(self) -> bool: """Connect to browser via WebSocket with proper headers.""" ws_url = self._get_websocket_url() i ...[truncated 3064 chars]
Remediation
## Remediation Suggestions 1. Restrict CDP access to an explicit allowlist. If only local OpenClaw CDP is supported, require exactly an approved loopback host and port, such as `http://127.0.0.1:18800/json`. 2. Reject user-info components, fragments, non-HTTP schemes, unexpected ports, and malformed URLs. 3. Resolve the hostname before connecting and reject unspecified, private, loopback, link-local, multicast, and reserved addresses unless a specific address is explicitly approved. 4. Disable automatic redirects with `allow_redirects=False`, or validate every redirect destination before following it. 5. Set a maximum response size and verify the response content type and expected JSON structure. 6. Validate `webSocketDebuggerUrl` independently. Permit only `ws` or `wss` and require its resolved host and port to match the approved CDP service. 7. Prefer deriving the WebSocket destination from a trusted endpoint rather than accepting an arbitrary host from response data. 8. Apply outbound network controls so the process cannot access cloud metadata or unrelated internal services. 9. Add tests covering IPv4, IPv6, encoded loopback addresses, DNS rebinding, redirect-based bypasses, and a JSON response containing a remote WebSocket URL.

T09 · Insecure Skill Coding Practices

Error
Location
lib/browser_core.py:159
Finding
Path Traversal in Screenshot Filenames Enables Arbitrary File Overwrite## Vulnerability Details **File Locations**: - `lib/browser_core.py:159-181` - `lib/browser_core_fixed.py:157-179` - `lib/browser_cli.py:104-110` - `lib/browser_simple.py:73-81` **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: High ### Vulnerable Code `lib/browser_core.py`: ```python def take_screenshot(self, filename: str, full_page: bool = False) -> Optional[str]: """Take screenshot of current page.""" try: self._send_command("Page.enable") params = {"format": "png"} if full_page: metrics = self._send_command("Page.getLayoutMetrics") content_size = metrics.get("contentSize", {}) params["clip"] = { "x": 0, "y": 0, "width": content_size.get("width", 1280), "height": content_size.get("height", 720), "scale": 1 } result = self._send_command("Page.captureScreenshot", params) screenshot_data = result.get("data", "") if not screenshot_data: logger.error("No screenshot data received") return None screenshot_path = self.screenshot_dir / filename import base64 with open(screenshot_path, "wb") as f: f.write(base64.b64decode(screenshot_data)) ``` `lib/browser_core_fixed.py` contains the same unsafe path construction and write: ```python screenshot_path = self.screenshot_dir / filename import base64 with open(screenshot_path, "wb") as f: f.write(base64.b64decode(screenshot_data)) ``` `lib/browser_cli.py`: ```python def take_screenshot(self, filename: str) -> Optional[str]: """Take screenshot using OpenClaw browser CLI.""" screenshot_path = self.screenshot_dir / filename logger.info(f"Taking screenshot: {screenshot_path}") result = self._run_browser_command( ["sc ...[truncated 2840 chars]
Remediation
## Remediation Suggestions 1. Accept only a filename, not an arbitrary path. Reject absolute paths and values containing directory separators or `..` components. 2. Enforce a strict allowlist such as `[A-Za-z0-9_.-]+\.png`, with a reasonable maximum length. 3. Canonicalize and verify containment before writing: ```python base = self.screenshot_dir.resolve() destination = (base / filename).resolve() if destination.parent != base: raise ValueError("Screenshot path must remain inside screenshot_dir") ``` 4. If subdirectories are intentionally supported, use `destination.is_relative_to(base)` and create only approved directory structures. 5. Reject symlink destinations or open files using platform protections such as `O_NOFOLLOW` where available. 6. Use exclusive creation when overwriting is unnecessary, preventing accidental truncation of existing files. 7. Generate filenames internally with a random identifier rather than trusting externally supplied names. 8. Run browser automation under a dedicated, least-privileged account with write access limited to its screenshot directory. 9. Apply the same validation before passing any `MEDIA:` path to the OpenClaw CLI. 10. Add regression tests for absolute paths, `../` traversal, nested traversal, symbolic-link escapes, path separators for supported platforms, and attempts to overwrite existing files.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (20)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documents browser automation, shell commands, network access, session handling, and form submission, but declares no explicit tool scope or permission boundaries. In an agent setting, this creates ambient authority: any agent invoking the skill may gain broad ability to browse, submit data, store cookies, and run installation or test commands without clear restriction.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The description says the skill should be used when "any agent needs to automate web browser interactions," which is an overly broad activation condition for a high-capability skill. Broad invocation language increases the chance that unrelated tasks will trigger browser, network, session, and form-submission behaviors without sufficient narrowing or review.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The skill advertises cookie management, authentication persistence, screenshots, HTML capture, and text extraction without explicit safeguards for secrets, tokens, or personal data. These features can expose session cookies, account state, page contents, and identifiers if used broadly or stored insecurely.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding
Using `npx clawhub@latest` pulls a moving target at execution time, which can introduce unreviewed code changes or a compromised upstream package into the environment. For a skill that can automate browsers and interact with user data, this expands supply-chain risk and makes builds non-reproducible.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The documentation explicitly supports filling forms, submitting entries, and capturing proof, but does not pair these actions with clear user-facing warnings about privacy, consent, or irreversible external side effects. In an autonomous-agent context, that omission can lead to unauthorized data submission or collection of sensitive records.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
## Error Recovery Patterns

### CORS Issues (Screenshots/Evaluate Not Working)
**Problem:** Browser automation fails with CORS errors when taking screenshots or evaluating JavaScript.

**Solution:** Ensure browser is started with `--remote-allow-origins=*` flag:
Confidence
98% confidence
Finding
Recommending `--remote-allow-origins=*` weakens browser remote-debugging protections by allowing any origin to access the DevTools endpoint, which can expose page content, cookies, local storage, screenshots, and arbitrary browser control. In this skill, that is especially dangerous because the browser is used for authenticated sessions, form submissions, and data capture.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The document advises never hardcoding credentials, yet includes hardcoded example login/account data and personal information elsewhere in the skill. Even if illustrative, this normalizes unsafe handling of secrets and may leak real or reusable identifiers into logs, demos, training data, or downstream copies.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This example drives real browser navigation and simulates a competition entry workflow while embedding realistic personal data, but it does not clearly warn users that adapting the example to a live target would transmit that data to an external site. In an automation skill context, examples are often copied directly into production-like use, so this can lead to unintended disclosure of personal information or accidental interaction with real third-party services.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def _check_browser_status(self) -> bool:
        """Check if browser is running."""
        try:
            result = subprocess.run(
                ["openclaw", "browser", "status"],
                capture_output=True,
                text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
full_command = ["openclaw", "browser"] + command
            logger.debug(f"Running command: {' '.join(full_command)}")
            
            result = subprocess.run(
                full_command,
                capture_output=True,
                text=True,
Confidence
75% confidence
Finding
This helper constructs and executes `openclaw browser` subcommands from caller-supplied `command` elements. While it avoids shell injection by passing a list to `subprocess.run`, it still creates a powerful command-execution boundary where untrusted inputs can trigger sensitive browser actions such as navigation, script evaluation, clicking, screenshots, or file-based operations, which is significant in an agent skill designed to automate external websites.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
Untrusted JavaScript is written to a temporary file and then executed in the browser context via `evaluate`. In this skill's context, that enables arbitrary script execution against whatever page the automated browser is visiting, which can exfiltrate page data, interact with authenticated sessions, or perform unintended actions; additionally, using `delete=False` leaves sensitive script contents on local disk until cleanup occurs.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
Form data is serialized to a temporary file on local storage before being passed to the browser CLI. In an automation skill, that data may include credentials, PII, or competition-entry secrets, and `delete=False` means secrets can remain on disk if cleanup fails or the process crashes.

Internal Network Request

Medium
Category
Server-Side Request Forgery
Content
# First check if browser is accessible via HTTP
    try:
        response = requests.get("http://localhost:18800/json", timeout=5)
        targets = response.json()
        print(f"✅ Browser HTTP endpoint accessible. Found {len(targets)} targets.")
Confidence
70% confidence
Finding
Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The library silently creates a screenshot directory under /tmp and later stores screenshots without any user-facing consent, retention controls, or sensitivity warning. In an agent context, screenshots can capture credentials, session cookies in visible pages, personal data, or proprietary content, and /tmp is often a shared or weakly controlled location.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The helper method executes an external command via subprocess.run, enabling browser actions such as focus, navigate, screenshot, open, and close. The code has technical logging for debugging and errors, but no clear safety disclosure that the skill will invoke external system commands affecting browser state.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = ["openclaw", "browser"] + args
            logger.debug(f"Running: {' '.join(cmd)}")
            
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The navigate method accepts arbitrary URLs and sends them to an external browser command without any safety disclosure, allowlisting, or confirmation. In this skill's context, that is more dangerous because it is designed for autonomous agent workflows, so a downstream agent could be induced to visit phishing, malware, or internal-network URLs without meaningful user awareness.

Intent-Code Divergence

Low
Confidence
79% confidence
Finding
The documentation claims URL sanitization is part of safe usage, but multiple examples show direct navigation to caller-provided URLs such as competition URLs and generic url variables with no validation or allowlist step. Because this is presented as recommended usage, it conflicts with the stated security intent.

Natural-Language Policy Violations

Low
Confidence
77% confidence
Finding
SQP-3 applies to all file types, including code comments and string literals. The embedded example data fixes the user context to South Africa (phone format and Johannesburg address) and presents it as the default example flow, with no indication that locale is optional or configurable for users in other regions.

Missing User Warnings

Low
Confidence
82% confidence
Finding
This code saves captured page content to a filesystem path, which can persist potentially sensitive visual data from the browser session. While the method name implies screenshot behavior, there is no confirmation prompt or explicit disclosure at the point of writing the file.

Static analysis

No suspicious patterns detected.