Back to skill

Security audit

Openclaw Skill Browser Use

Security checks for vulnerabilities and agentic risk

Overview

The skill provides the browser automation it advertises, but its installer and autonomous wrapper have serious under-disclosed credential, privilege, and local code-execution risks.

Review before installing. Use only in an isolated, unprivileged environment, avoid running the installer as root unless you have audited the dependency steps, do not rely on stored root OpenClaw provider keys, pass narrowly scoped API keys explicitly, and avoid saving cookies, auth state, screenshots, PDFs, or recordings from sensitive accounts unless you can protect and delete them.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/browser-use-agent.sh:7
Finding
Arbitrary Python Code Execution Through Unsafely Generated Source Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/browser-use-agent.sh:7-18, 32-54`; duplicated in the wrapper generated by `scripts/install.sh:63-78, 89-117` **Vulnerability Type**: Python source-code injection through unsanitized shell arguments **Risk Level**: High ### Vulnerable Code ```bash TASK="${1:?Usage: $0 \"task description\" [--model MODEL] [--max-steps N]}" shift MODEL="gpt-4o-mini" MAX_STEPS=12 while [[ $# -gt 0 ]]; do case "$1" in --model) MODEL="$2"; shift 2 ;; --max-steps) MAX_STEPS="$2"; shift 2 ;; *) echo "Unknown option: $1"; exit 1 ;; esac done if [[ "$MODEL" == claude* ]] || [[ "$MODEL" == anthropic* ]]; then LLM_IMPORT="from langchain_anthropic import ChatAnthropic" LLM_INIT="ChatAnthropic(model='$MODEL', api_key=os.environ['ANTHROPIC_API_KEY'])" else LLM_IMPORT="from langchain_openai import ChatOpenAI" LLM_INIT="ChatOpenAI(model='$MODEL', api_key=os.environ['OPENAI_API_KEY'])" fi cat > /tmp/_bu_task.py << PYEOF import asyncio, os $LLM_IMPORT from browser_use import Agent async def run(): llm = $LLM_INIT agent = Agent(task="""$TASK""", llm=llm) result = await agent.run(max_steps=$MAX_STEPS) final = result.final_result() if final: print(final.extracted_content if hasattr(final, 'extracted_content') else str(final)) else: for r in result.all_results: if r.extracted_content: print(r.extracted_content) asyncio.run(run()) PYEOF xvfb-run "$VENV_DIR/bin/python3" /tmp/_bu_task.py ``` ### Technical Analysis The wrapper constructs a Python program by directly interpolating three caller-controlled values: - `TASK` is inserted inside a triple-quoted Python string. - `MODEL` is inserted inside a single-quoted Python string. - `MAX_STEPS` is inserted as an unrestricted Python expression. None of these values are escaped or validated for the context in which they are inserted. A crafted task can terminate the tripl ...[truncated 1851 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace generated Python source with a fixed, reviewed Python entry point. 2. Pass the task and model as ordinary command-line arguments or through a serialized JSON document, then read them using `argparse` or `json`. 3. Parse `--max-steps` as a decimal integer in the shell or Python and enforce a reasonable range. 4. Validate the model against an explicit allowlist instead of placing an arbitrary model string into source code. 5. Do not use `eval`, dynamic imports, or shell interpolation as an alternative. 6. Apply the fix both to `scripts/browser-use-agent.sh` and to the wrapper template in `scripts/install.sh`. A safe design would invoke a fixed program in the following form: ```bash exec xvfb-run "$VENV_DIR/bin/python3" "$SCRIPT_DIR/browser_use_agent.py" \ --task "$TASK" \ --model "$MODEL" \ --max-steps "$MAX_STEPS" ``` The Python program should consume these as data rather than executable source. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/browser-use-agent.sh:20
Finding
Silent Retrieval of Provider API Keys from a Root-Owned Configuration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/browser-use-agent.sh:20-27`; duplicated in the wrapper generated by `scripts/install.sh:80-87` **Vulnerability Type**: Excessive credential access and violation of least privilege **Risk Level**: High ### Vulnerable Code ```bash if [ -z "${OPENAI_API_KEY:-}" ] && [ -f "/root/.openclaw/openclaw.json" ]; then export OPENAI_API_KEY=$(python3 -c "import json; print(json.load(open('/root/.openclaw/openclaw.json'))['models']['providers']['openai']['apiKey'])" 2>/dev/null || true) fi if [ -z "${ANTHROPIC_API_KEY:-}" ] && [ -f "/root/.openclaw/openclaw.json" ]; then export ANTHROPIC_API_KEY=$(python3 -c "import json; print(json.load(open('/root/.openclaw/openclaw.json'))['models']['providers']['anthropic']['apiKey'])" 2>/dev/null || true) fi ``` ### Technical Analysis When a provider key is not explicitly supplied, the wrapper probes the fixed privileged path `/root/.openclaw/openclaw.json` and extracts OpenAI and Anthropic API keys. It then exports the retrieved secret into the environment of the generated Python process. Browser automation legitimately requires a provider credential when an external LLM is used. However, automatically searching a root-owned configuration is not necessary for that functionality. The minimum-privilege design is to require an explicitly supplied, narrowly scoped credential or retrieve one through an approved secret manager. The behavior is also insufficiently disclosed by the README, which instructs users to set an API key explicitly. Exporting the key broadens its exposure to the Python process, dependencies, subprocesses, and any code executed through the source-injection vulnerability. Provider libraries are expected to send the key to the selected OpenAI or Anthropic service as an authentication credential. The security concern is the undeclared privileged discovery and broad propagation of the secret, not provider authentication itself. ### Attack Path 1 ...[truncated 1230 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove automatic reads from `/root/.openclaw/openclaw.json`. 2. Require the selected provider key to be supplied explicitly through a documented environment variable or secure secret-manager integration. 3. Use provider keys scoped specifically to this Skill, with restricted permissions, quotas, and billing limits. 4. Avoid exporting secrets globally where possible. Pass the selected credential only to the process that requires it. 5. Run browser automation as an unprivileged, dedicated account that cannot read root-owned configuration files. 6. Clearly document which external provider receives task and page context and obtain user approval before processing sensitive authenticated content. 7. Add secret redaction to logs and ensure exceptions cannot print credentials. 8. Rotate any credentials that may have been exposed through prior use of the vulnerable wrapper. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/browser-use-agent.sh:42
Finding
Predictable Shared Temporary File Enables Symlink, Race, and Code-Substitution Attacks<![CDATA[ ## Vulnerability Details **File Location**: `scripts/browser-use-agent.sh:42-54`; duplicated in the wrapper generated by `scripts/install.sh:92-117` **Vulnerability Type**: Unsafe temporary-file creation and time-of-check/time-of-use exposure **Risk Level**: Medium ### Vulnerable Code ```bash cat > /tmp/_bu_task.py << PYEOF import asyncio, os $LLM_IMPORT from browser_use import Agent async def run(): llm = $LLM_INIT agent = Agent(task="""$TASK""", llm=llm) result = await agent.run(max_steps=$MAX_STEPS) final = result.final_result() if final: print(final.extracted_content if hasattr(final, 'extracted_content') else str(final)) else: for r in result.all_results: if r.extracted_content: print(r.extracted_content) asyncio.run(run()) PYEOF xvfb-run "$VENV_DIR/bin/python3" /tmp/_bu_task.py ``` The installed wrapper uses the same fixed path: ```bash echo "$SCRIPT" > /tmp/_bu_task.py xvfb-run "$VENV_DIR/bin/python3" /tmp/_bu_task.py ``` ### Technical Analysis Every invocation writes executable Python to the same predictable path, `/tmp/_bu_task.py`. Shared temporary directories are generally writable by local users. The wrapper does not: - Create the file atomically. - Reject symbolic links. - Assign restrictive permissions explicitly. - Verify file ownership or type. - Prevent concurrent invocations. - Remove the generated file after execution. An attacker may pre-create the path as a symbolic link, potentially causing the wrapper to overwrite another file writable by the caller. There is also a race between writing the file and opening it for execution: a local attacker may replace or modify it before the Python interpreter reads it. Concurrent legitimate executions can overwrite one another's tasks, causing one invocation to execute another invocation's generated program. The residual file also discloses the submitted task and selected configuration to users who can read it unde ...[truncated 1373 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer a fixed Python entry point and eliminate runtime-generated executable files entirely. 2. If a temporary file remains necessary, create it atomically with `mktemp`: ```bash TASK_FILE="$(mktemp "${TMPDIR:-/tmp}/browser-use-task.XXXXXXXX.py")" chmod 600 "$TASK_FILE" trap 'rm -f -- "$TASK_FILE"' EXIT HUP INT TERM ``` 3. Ensure the temporary file is owned by the invoking user, is a regular file, and is not a symbolic link. 4. Set a restrictive `umask`, such as `umask 077`, before creating files containing task data. 5. Never reuse a fixed path across concurrent invocations. 6. Execute the exact file created by `mktemp`, then delete it reliably. 7. Run the wrapper as an unprivileged dedicated account and use a private runtime directory where possible. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/install.sh:10
Finding
Unpinned Third-Party Packages Are Installed into Privileged and Global Locations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/install.sh:10-53` **Vulnerability Type**: Uncontrolled dependency resolution and privileged supply-chain exposure **Risk Level**: Medium ### Vulnerable Code ```bash if command -v apt-get &>/dev/null; then sudo apt-get update -qq sudo apt-get install -y -qq python3 python3-venv xvfb \ libglib2.0-0 libnss3 libnspr4 libdbus-1-3 libatk1.0-0 \ libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 \ libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 \ libatspi2.0-0 2>/dev/null || true elif command -v brew &>/dev/null; then echo " macOS detected — Chromium ships with Playwright, skipping system deps" else echo " WARNING: Unknown package manager. Ensure python3, chromium deps are installed." fi if command -v agent-browser &>/dev/null; then echo " agent-browser already installed: $(agent-browser --version 2>/dev/null || echo 'unknown')" else npm install -g agent-browser echo " Installed agent-browser" fi agent-browser install --with-deps 2>/dev/null || agent-browser install 2>/dev/null || { npx playwright install chromium 2>/dev/null || true } if [ ! -d "$VENV_DIR" ]; then python3 -m venv "$VENV_DIR" fi "$VENV_DIR/bin/pip" install -q --upgrade pip "$VENV_DIR/bin/pip" install -q browser-use langchain-anthropic langchain-openai "$VENV_DIR/bin/python3" -m playwright install chromium 2>/dev/null || true ``` ### Technical Analysis The installer resolves and installs mutable current versions of several third-party components without a lockfile, exact versions, package hashes, or integrity verification: - Global npm package `agent-browser`. - The package selected by the `npx playwright` fallback. - Python packages `browser-use`, `langchain-anthropic`, and `langchain-openai`. - An upgraded current version of `pip`. - Browser binaries downloaded by Playwright. The npm package is installed globally, the Python e ...[truncated 1659 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct dependency to a reviewed exact version. 2. Commit an npm lockfile and use `npm ci` rather than an unpinned global installation. 3. Use a Python requirements lockfile with hashes and install with: ```bash pip install --require-hashes -r requirements.lock ``` 4. Pin Playwright and its browser revision; remove the unpinned `npx playwright` fallback. 5. Avoid globally installing npm packages. Install dependencies in a project-local, unprivileged directory. 6. Create the Python virtual environment under a dedicated unprivileged service account rather than defaulting to a root-owned `/opt` path. 7. Separate system package installation from application dependency installation and require explicit administrative approval. 8. Verify package provenance, registry configuration, checksums, and signatures where available. 9. Scan pinned direct and transitive dependencies and establish a controlled update process. 10. Avoid suppressing installation errors with `|| true`, because doing so can conceal incomplete or unexpected dependency states. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
Findings (20)

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding
The skill uses 'env' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding
The skill uses 'shell' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README tells users to export API keys directly for autonomous browser operation but provides no warning that these credentials are sensitive or should be scoped and protected. Because this skill drives browser automation and may run in shared shells, logs, CI jobs, or agent environments, exposed API keys could be stolen and abused for account access or billing fraud.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README advertises persistence of cookies and authentication state without warning that these files may contain bearer tokens, session cookies, or other reusable credentials. In a browser automation skill, users may persist highly privileged sessions to third-party sites, so mishandling or sharing this state can enable account takeover or unauthorized access.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill advertises autonomous interaction with authenticated web apps, form filling, and complex multi-step flows without warning that an agent may submit forms, modify account state, or act within privileged user sessions. That omission is risky because users may invoke the skill in sensitive environments without realizing it can take consequential authenticated actions.

Context Window Stuffing

Medium
Category
Memory Poisoning
Content
# Interact using refs
agent-browser click @e3            # Click element
agent-browser fill @e2 "text"      # Fill input (clears first)
agent-browser press Enter          # Press key

# Extract data
Confidence
85% confidence
Finding
Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The screenshot, PDF, and recording features can capture visible secrets, personal data, internal documents, and authenticated application content, yet the skill provides no caution about handling or retaining these artifacts. In practice, users or downstream agents may store or share these files assuming they are harmless, leading to unintentional data disclosure.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly supports saving/restoring browser state, inspecting cookies, and reading or setting localStorage, but it does not warn users that these artifacts can contain active session tokens, authentication cookies, and other secrets. In an agentic context, this omission increases the chance that sensitive session data will be persisted, exposed in outputs, or reused beyond the user's intent.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The guide instructs users to save and reload browser session state, including cookies and authentication material, to a file in /tmp without warning that this can expose live authenticated sessions to other local users, logs, backups, or later unintended reuse. In a browser automation skill, persisted auth state can directly enable account takeover within the automated sites if the file is copied or mishandled.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The autonomous browser agent section describes an agent that decides what to do on pages but does not warn that it may navigate, click, submit forms, or otherwise act on the user's behalf across external websites. In this skill context, that omission is meaningful because the tool is specifically designed for autonomous multi-step browser interaction, which increases the chance of unintended transactions, data disclosure, or policy violations if operators assume it is passive.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The wrapper silently reads API credentials from a fixed root-scoped config file and injects them into the environment, even though browser task execution does not inherently require reading arbitrary local secrets from disk. In an autonomous browser skill, this increases risk because an operator may invoke the tool expecting browser automation, while the script also harvests credentials from a privileged local file path without explicit consent.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
Reading OpenAI and Anthropic API keys from a root-owned configuration file and exporting them without notice is unsafe secret handling. In this skill's context, the browser agent is autonomous and may interact with untrusted web content, so silently broadening secret availability to child processes heightens the chance of accidental exposure through logs, subprocesses, debugging, or future code changes.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# --- System dependencies ---
echo "[1/5] Installing system dependencies..."
if command -v apt-get &>/dev/null; then
    sudo apt-get update -qq
    sudo apt-get install -y -qq python3 python3-venv xvfb \
        libglib2.0-0 libnss3 libnspr4 libdbus-1-3 libatk1.0-0 \
        libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 \
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# --- System dependencies ---
echo "[1/5] Installing system dependencies..."
if command -v apt-get &>/dev/null; then
    sudo apt-get update -qq
    sudo apt-get install -y -qq python3 python3-venv xvfb \
        libglib2.0-0 libnss3 libnspr4 libdbus-1-3 libatk1.0-0 \
        libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 \
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Rp1

Medium
Category
MCP Rug Pull
Confidence
84% confidence
Finding
Using `npx playwright install chromium` without pinning a version makes execution depend on whatever package version the registry resolves at install time. In an installer that may run with elevated privileges and fetch remote code, this increases supply-chain risk and reduces reproducibility.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The wrapper silently reads API keys from `/root/.openclaw/openclaw.json` and exports them into the process environment. That is an unjustified credential-access behavior for a browser automation skill and expands the blast radius: any browser task or dependent library running under this wrapper can use those secrets without explicit user consent.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
Reading root-scoped provider credentials without warning is a covert secret-handling behavior. In this skill context, the danger is heightened because the wrapper launches an autonomous LLM/browser agent, so the imported credentials could be used for unintended external requests or abused by downstream code.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The entire guide is written in French and directly instructs the agent in that language, with no indication that users may choose another language or that French is required for a region-specific purpose. This can violate language/locale policy when a skill imposes a specific language without opt-in.

Missing User Warnings

Low
Confidence
91% confidence
Finding
The script generates Python code in /tmp using untrusted inputs like TASK and MODEL, then executes it. This is dangerous because /tmp is a shared location and, more critically, interpolating shell-controlled values directly into Python source can enable code injection if the task text or model string contains characters that break out of the intended string literal.

Missing User Warnings

Low
Confidence
89% confidence
Finding
The wrapper writes a generated Python script containing the user task and model configuration to a predictable `/tmp/_bu_task.py` path. Temporary files in shared locations can expose sensitive task contents, be overwritten, or be raced/symlinked by another local user depending on execution context.

Static analysis

No suspicious patterns detected.