Back to skill

Security audit

Steel Browser

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent browser automation tooling, but its shell helpers can be tricked by crafted inputs into running local Python code, so it belongs in Review before installation.

Install only after reviewing or fixing the heredoc argument-interpolation issue. Until fixed, run it in an isolated environment, avoid passing selectors, URLs, text, output paths, or JavaScript derived from untrusted webpages, avoid sensitive logged-in sessions, pin dependencies, and protect or remove ~/.steel_state after use.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/click.sh:15
Finding
Arbitrary Local Python Code Execution Through Unquoted Heredoc Argument Interpolation## Vulnerability Details **File Location**: `scripts/click.sh:15-24` **Additional Affected Locations**: `scripts/click_coords.sh:22-31`, `scripts/eval_js.sh:13-22`, `scripts/get_content.sh:21-36`, `scripts/hover.sh:10-19`, `scripts/navigate.sh:12-22`, `scripts/press_key.sh:16-25`, `scripts/screenshot.sh:20-30`, `scripts/scroll.sh:12-35`, `scripts/select.sh:12-23`, `scripts/start_session.sh:25-55`, `scripts/type.sh:12-21`, and `scripts/wait_for.sh:12-21` **Vulnerability Type**: Python source-code injection **Risk Level**: High ### Vulnerable Code A representative vulnerable implementation is: ```bash python3 - <<PYEOF import os, sys HELPER = "$SCRIPT_DIR/_connect.py" exec(open(HELPER).read()) try: page.click("""$SELECTOR""") print(f"Clicked: $SELECTOR") finally: cleanup() PYEOF ``` The same unsafe pattern is used for other caller-controlled values. Representative examples include: ```bash page.goto("$URL", wait_until="$WAIT_UNTIL") ``` ```bash result = page.evaluate("""$JS""") ``` ```bash page.fill("""$SELECTOR""", """$TEXT""") ``` ```bash page.screenshot(path="$OUTPUT", full_page=full_page) ``` ```bash page.wait_for_selector("""$SELECTOR""", timeout=int("$TIMEOUT_MS")) ``` ### Technical Analysis The scripts construct a Python program in an unquoted shell heredoc and directly interpolate command-line arguments into Python string literals. Triple-quoted strings do not safely serialize arbitrary input. A value containing a closing triple quote can terminate the intended literal and append new Python statements. For example, a malicious selector shaped like the following can break out of the generated string: ```text x"""); __import__("os").system("id"); # ``` It causes the generated Python source to contain an attacker-controlled statement conceptually equivalent to: ```python page.click("""x""") __import__("os").system("id") ``` The inje ...[truncated 2215 chars]
Remediation
## Remediation Suggestions Do not generate Python source code by interpolating shell arguments. Pass each value as a normal process argument and use a single-quoted heredoc so the shell cannot expand its contents: ```bash python3 - "$SCRIPT_DIR/_connect.py" "$SELECTOR" <<'PY' import sys helper = sys.argv[1] selector = sys.argv[2] exec(compile(open(helper, encoding="utf-8").read(), helper, "exec")) try: page.click(selector) print(f"Clicked: {selector}") finally: cleanup() PY ``` Apply the same design to every affected script: 1. Pass selectors, URLs, text, JavaScript, paths, keys, and option values through `sys.argv` or a safely encoded data channel such as JSON. 2. Quote heredoc delimiters as `<<'PY'`. 3. Validate numeric fields in Bash and Python before use. 4. Restrict enumerated values such as mouse buttons and page load states to explicit allowlists. 5. Validate navigation schemes and screenshot destinations according to the intended trust boundary. 6. Add regression tests containing quotes, triple quotes, newlines, backslashes, Unicode, and attempted Python statements. 7. Prefer a shared Python command-line program using `argparse` over separate shell-generated Python fragments.

T08 · Insecure Dependencies

Warning
Location
SKILL.md:13
Finding
Unpinned Third-Party Dependencies Permit Mutable Supply-Chain Resolution## Vulnerability Details **File Location**: `SKILL.md:13-16` **Vulnerability Type**: Unpinned runtime dependencies **Risk Level**: Medium ### Vulnerable Code ```bash pip install steel-sdk playwright export STEEL_API_KEY=your_key_here ``` ### Technical Analysis The documented installation command installs `steel-sdk` and `playwright` without exact versions, hashes, a lock file, or an explicitly trusted package index. Consequently, the code installed at deployment time can differ from the code reviewed during the audit. Python package installation can process package-controlled metadata, build backends, and installation artifacts. A compromised upstream release, compromised package-index account, unsafe index configuration, or unexpectedly incompatible future release could therefore introduce code that executes during installation or later when imported by the Skill. No evidence was found that the currently named packages are malicious. The issue is the absence of controls that bind installation to reviewed artifacts. ### Attack Path 1. A user follows the prerequisite command in `SKILL.md`. 2. `pip` resolves the latest acceptable releases from its configured package index or indexes. 3. A dependency release has changed since the Skill was reviewed, or the configured source serves a compromised artifact. 4. The package is installed and its code is subsequently imported by `_connect.py` or another script. 5. Malicious package code executes with the installing or invoking user's privileges and can access the Steel API key and local files. ### Impact Assessment A compromised dependency could execute arbitrary code with the privileges of the user performing installation or running the Skill. Depending on how installation is performed, this may expose: - The local account's files and environment variables. - `STEEL_API_KEY` and active Steel session identifiers. - Browser-session data and automation results. - Sy ...[truncated 223 chars]
Remediation
## Remediation Suggestions 1. Pin direct and transitive dependencies to reviewed versions. 2. Maintain a lock file or hashed requirements file. 3. Install with hash verification, for example: ```bash python -m pip install --require-hashes -r requirements.txt ``` 4. Configure and document the intended HTTPS package index rather than relying on ambient `pip` configuration. 5. Install dependencies in a dedicated virtual environment under an unprivileged account. 6. Use dependency scanning and controlled update reviews before changing pinned versions. 7. Avoid elevated installation unless it is operationally necessary.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/start_session.sh:48
Finding
Active Session Identifier Is Stored Without Explicit Restrictive File Permissions## Vulnerability Details **File Location**: `scripts/start_session.sh:48-51` **Vulnerability Type**: Insecure sensitive state-file creation **Risk Level**: Low ### Vulnerable Code ```python state = {"session_id": str(session.id)} state_path = os.path.expanduser("~/.steel_state") with open(state_path, "w") as f: json.dump(state, f) ``` ### Technical Analysis The active Steel session identifier is written using Python's ordinary `open()` function without explicitly enforcing owner-only permissions. The resulting mode is determined by the process umask. Under a permissive or misconfigured umask, another local user may be able to read the state file. The code also does not explicitly protect creation against symbolic-link substitution. In a normally protected home directory, exploitation by another user may be constrained, but the implementation does not itself enforce that assumption. The session ID is not sufficient by itself to establish the CDP connection shown in `_connect.py`, because the Steel API key is also required. Nevertheless, disclosure gives an attacker a valid identifier for an active session and increases the impact of any separately acquired API access. ### Attack Path 1. The Skill is run in an environment with a permissive umask or inadequately protected home directory. 2. `start_session.sh` creates `~/.steel_state` with permissions that permit another local principal to read it. 3. The local principal obtains the active Steel session ID. 4. If that principal also obtains usable Steel API credentials, it can attempt to connect to or release the identified session. 5. Depending on session contents, this may expose browser data or disrupt active automation. ### Impact Assessment The directly exposed data is the active session identifier. Potential consequences include: - Disclosure of an identifier associated with a live cloud-browser session. - Easier targeting of the session if the API ...[truncated 277 chars]
Remediation
## Remediation Suggestions Create the state file atomically with owner-only permissions and reject symbolic links: ```python import json import os import stat state_path = os.path.expanduser("~/.steel_state") flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW fd = os.open(state_path, flags, 0o600) try: os.fchmod(fd, stat.S_IRUSR | stat.S_IWUSR) with os.fdopen(fd, "w", encoding="utf-8") as f: json.dump(state, f) fd = None finally: if fd is not None: os.close(fd) ``` Additional hardening should include: 1. Store state in an owner-only directory with mode `0700`. 2. Verify that an existing path is a regular file owned by the current user. 3. Avoid following symbolic links. 4. Remove stale state after session release or failed session creation. 5. Avoid printing session identifiers or viewer URLs into logs that may be accessible to other users.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill exposes capabilities that rely on environment variables and persistent local state, but it does not declare any tool scope or permissions metadata. That makes the capability boundary implicit rather than explicit, increasing the chance an agent or user invokes the skill without understanding it can read secrets from env and write session state to disk.

Context Window Stuffing

Medium
Category
Memory Poisoning
Content
| `screenshot.sh` | `[OUTPUT.png] [--full-page]` | Take screenshot |
| `click.sh` | `SELECTOR` | Click by CSS/text/aria selector |
| `click_coords.sh` | `X Y [--right] [--double]` | Click at pixel coords (fallback) |
| `type.sh` | `SELECTOR "text"` | Fill input field |
| `press_key.sh` | `KEY` | Press key (e.g. `Enter`, `Control+a`) |
| `scroll.sh` | `AMOUNT\|--to-bottom\|--to-top\|SELECTOR` | Scroll page |
| `hover.sh` | `SELECTOR` | Hover over element |
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
86% confidence
Finding
This code initiates a network connection to a remote browser session and transmits the API key and session ID as part of the connection URL. While the module docstring describes its purpose, there is no runtime confirmation, warning, or user-facing notice about the outbound connection or credential use.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The manifest emphasizes that this skill uses Playwright selectors instead of pixel coordinates for reliability in pure web tasks. This script explicitly performs clicks by raw screen coordinates as a fallback, which is a different interaction model than the one the manifest claims as the skill's operating approach.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This script takes arbitrary JavaScript from a shell argument and passes it directly into page.evaluate, which executes in the context of the active browser page. In a browser-automation skill, that creates a powerful code-execution primitive against any site the agent is logged into, enabling DOM manipulation, data extraction, or triggering sensitive actions without any guardrails, confirmation, or policy checks.

Session Persistence

Medium
Category
Rogue Agent
Content
#!/usr/bin/env bash
# start_session.sh - Create a new Steel browser session
# Usage: start_session.sh [--proxy] [--captcha] [--timeout MS] [--ua USER_AGENT]
#
# Saves session ID to ~/.steel_state for use by other scripts.
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Low
Confidence
84% confidence
Finding
The skill persists browser session identifiers in ~/.steel_state but does not warn users that session state is stored locally across invocations. Persistent session artifacts can enable unintended session reuse, confusion between tasks, or exposure of active browser session identifiers to other local processes or users.

Static analysis

No suspicious patterns detected.