Back to skill

Security audit

Browser Relay

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed local browser-control relay, but it gives an agent powerful access to a real Chromium session and handles tokens/screenshots in ways that need careful review.

Install only if you intend to let an agent control a local Chromium browser. Use a dedicated browser profile with no personal logins, stop the relay when done, protect the bearer token, avoid sending screenshots with sensitive content to Telegram, and review whether bypassing platform IP restrictions is acceptable for your use case.

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
start.sh:6
Finding
Predictable Temporary Files Permit Token Disclosure, Symlink Attacks, and PID Manipulation<![CDATA[ ## Vulnerability Details **File Location**: `start.sh:6-8`, `start.sh:11-30`, `start.sh:68-69`, `relay.py:359-363` **Vulnerability Type**: Unsafe predictable temporary files and plaintext secret logging **Risk Level**: High ### Vulnerable Code ```bash RELAY_PID_FILE="/tmp/browser-relay.pid" TOKEN_FILE="/tmp/browser-relay-token" LOG_FILE="/tmp/relay.log" # Stop an existing process using the PID file stop_relay() { if [ -f "$RELAY_PID_FILE" ]; then local pid=$(cat "$RELAY_PID_FILE") if kill -0 "$pid" 2>/dev/null; then kill "$pid" 2>/dev/null for i in $(seq 1 10); do kill -0 "$pid" 2>/dev/null || break sleep 0.5 done if kill -0 "$pid" 2>/dev/null; then kill -9 "$pid" 2>/dev/null fi echo "已停止旧进程 (PID: $pid)" fi rm -f "$RELAY_PID_FILE" fi } ``` ```bash nohup python3 -u relay.py > "$LOG_FILE" 2>&1 & RELAY_PID=$! echo "$RELAY_PID" > "$RELAY_PID_FILE" ``` ```python print(f" Auth token: {AUTH_TOKEN}") token_path = Path("/tmp/browser-relay-token") token_path.touch(mode=0o600, exist_ok=True) token_path.write_text(AUTH_TOKEN) os.chmod(token_path, stat.S_IRUSR | stat.S_IWUSR) ``` ### Technical Analysis The relay uses fixed, globally predictable paths under `/tmp` for its token, PID, and log files. Neither the shell script nor the Python implementation verifies that these paths are regular files owned by the current user and not symbolic links. The token file is opened through `Path.touch()` and `Path.write_text()`, both of which follow symbolic links. Applying `chmod()` afterward does not prevent the attack because the target may already have been overwritten. The operation can also unexpectedly change the permissions of the symlink target. The token is printed to standard output, while `start.sh` redirects standard output to `/tmp/relay.log`. The log is not explicitly created with restrictive perm ...[truncated 2262 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store runtime files in a private per-user directory such as `$XDG_RUNTIME_DIR/browser-relay`, created with mode `0700`. 2. Set `umask 077` before creating any token, PID, or log file. 3. Create security-sensitive files atomically with `O_CREAT | O_EXCL | O_NOFOLLOW` and mode `0600`. 4. Use `lstat()` or equivalent checks to reject symbolic links and verify that existing files are regular files owned by the current user. 5. Never print the bearer token to standard output or logs. Return only the token-file location. 6. Open the log explicitly with mode `0600`, or use a logging facility that enforces per-user access. 7. Validate PID-file content as a positive integer and verify `/proc/<pid>/cmdline`, executable identity, process owner, and preferably process start time before sending a signal. 8. Use a supervisor or process handle instead of trusting a reusable numeric PID where possible. 9. Remove token and PID files during orderly shutdown and rotate the token after any suspected disclosure. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
relay.py:211
Finding
Unrestricted JavaScript Evaluation Exposes Browser Session Data and Authenticated Actions<![CDATA[ ## Vulnerability Details **File Location**: `relay.py:211-229` **Vulnerability Type**: Unrestricted browser-context code execution **Risk Level**: High ### Vulnerable Code ```python async def handle_evaluate(request): body = await request.json() expression = body["expression"] tab_id = body.get("tab_id") await_promise = body.get("await_promise", False) params = {"expression": expression, "returnByValue": True} if await_promise: params["awaitPromise"] = True result = await cdp_send("Runtime.evaluate", params, tab_id) r = result.get("result", {}) if r.get("subtype") == "error" or result.get("exceptionDetails"): return web.json_response({ "error": result.get("exceptionDetails", {}).get("text", str(r)) }, status=400) return web.json_response({"ok": True, "value": r.get("value"), "type": r.get("type")}) ``` The documented policy in `SKILL.md:48-57` prohibits agents from extracting cookies, storage credentials, and password values, but this policy is not enforced by the relay. ### Technical Analysis The `/evaluate` endpoint accepts an arbitrary JavaScript expression and forwards it directly to Chrome DevTools Protocol `Runtime.evaluate`. The result is serialized and returned to the caller. Bearer-token authentication restricts who can call the endpoint, but it does not constrain what an authenticated caller can evaluate. Instruction-level restrictions in `SKILL.md` are advisory and can neither control a non-agent API client nor reliably prevent a compromised agent from issuing prohibited expressions. An authenticated caller can therefore query browser-accessible data such as non-HttpOnly cookies, DOM content, form values, and web storage, or invoke application functionality in the context of an authenticated page. HttpOnly cookies cannot be read directly through ordinary page JavaScript, but authenticated actions may still be performed because requests initiated from the p ...[truncated 1835 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `/evaluate` from production deployments unless unrestricted evaluation is essential. 2. Replace it with narrowly scoped endpoints for required operations, such as reading visible text, obtaining element geometry, checking page state, and dispatching approved interactions. 3. If evaluation must remain available, disable it by default and require a separate, short-lived capability token granted through explicit user approval. 4. Apply origin and tab allowlists so evaluation cannot target arbitrary authenticated sites. 5. Do not rely on string-based filtering of JavaScript expressions; it is readily bypassed through computed property access, encoding, indirect evaluation, and equivalent browser APIs. 6. Run Chromium with a dedicated, isolated profile that contains no unrelated credentials or authenticated sessions. 7. Rotate tokens frequently, stop the relay immediately after the task, and avoid caching the token in persistent memory. 8. Require user confirmation before operations that submit forms, publish content, transfer data, or otherwise produce external side effects. 9. Add audit logging for endpoint names and target origins without logging tokens, evaluated source containing secrets, or returned sensitive values. ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Unpinned Python Dependencies Create a Mutable Supply-Chain Boundary<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-2` **Vulnerability Type**: Unpinned third-party dependencies without integrity verification **Risk Level**: Medium ### Vulnerable Code ```text aiohttp websockets ``` The documented installation process in `README.md:39-42` executes: ```bash python3 -m venv venv && source venv/bin/activate pip install -r requirements.txt python3 relay.py ``` ### Technical Analysis The requirements file specifies package names without exact versions or cryptographic hashes. Each installation can therefore resolve to different releases according to the active package index, dependency resolver, platform, and installation time. No evidence was found that either listed package is malicious. The vulnerability is the absence of reproducibility and integrity controls: a future compromised release, compromised package index, maliciously configured mirror, dependency confusion condition, or unexpected incompatible version can introduce code that was not part of this audit. Python packages and their transitive dependencies execute code within the relay's environment when imported and may execute build-related code during installation. Because the relay controls Chromium and handles its bearer token, a compromised dependency would occupy a highly trusted position. ### Attack Path 1. An attacker compromises a future release, transitive dependency, configured Python package mirror, or package-index resolution path. 2. A user follows the documented `pip install -r requirements.txt` command. 3. Pip resolves the unrestricted package name to the attacker-controlled or compromised artifact. 4. Malicious code executes during package build or installation, or later when `relay.py` imports `aiohttp`. 5. The dependency executes with the privileges of the user running installation or the relay. 6. It can access relay data, the local filesystem available to that user, browser-control traffic, and network resources availa ...[truncated 752 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin all direct and transitive dependencies to reviewed versions. 2. Generate a lock file using a reproducible dependency-management process. 3. Record SHA-256 hashes for every accepted distribution and install with `pip --require-hashes`. 4. Explicitly configure and document the trusted package index rather than relying on ambient pip configuration. 5. Prefer prebuilt, reviewed wheels from the trusted index and prevent unexpected source builds where practical. 6. Add automated dependency vulnerability and provenance scanning to the release process. 7. Periodically update pins through reviewed changes rather than allowing versions to change at install time. 8. Install into an unprivileged virtual environment and never run the documented installation command as root. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (33)

External Script Fetching

High
Category
Supply Chain
Content
TOKEN=$(cat /tmp/browser-relay-token)

# Health check
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:18792/health

# List browser tabs
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:18792/tabs
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The description understates the actual power of the skill. Beyond screenshots, it exposes full browser automation plus arbitrary JavaScript execution in the user's logged-in browser context, which can access page content, cookies via JS where available, local/session storage, and drive authenticated actions on websites. In this context, the mismatch is dangerous because it can mislead users or upstream agents into granting a tool that effectively enables local browser takeover and data exfiltration.

External Script Fetching

High
Category
Supply Chain
Content
### 1. 检查 Chromium 是否运行

```bash
curl -s http://127.0.0.1:9222/json/version
```

如果连接失败,说明 Chromium 未启动,需要启动它(见下方"启动 Chromium")。
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
### 1. 检查 Chromium 是否运行

```bash
curl -s http://127.0.0.1:9222/json/version
```

如果连接失败,说明 Chromium 未启动,需要启动它(见下方"启动 Chromium")。
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
# 1. 截图并保存
TOKEN=$(cat /tmp/browser-relay-token)
SCREENSHOT_PATH="/tmp/relay_screenshot.png"
curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"quality":80}' http://127.0.0.1:18792/screenshot \
  | python3 -c "
import sys, json, base64
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
The /evaluate endpoint exposes arbitrary JavaScript execution in whatever page the user's local Chromium has open. In a browser-relay skill, this enables full DOM access, data extraction, session abuse, and arbitrary in-page actions far beyond simple navigation or screenshots, making the relay effectively a remote browser RCE primitive within the user's authenticated web context.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
Arbitrary Runtime.evaluate access is an unjustified and dangerous capability for a relay advertised mainly for browser control and screenshots. It allows reading page content, invoking privileged web APIs available to the page, manipulating authenticated sessions, and bypassing any intended higher-level safety constraints in the relay API.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The documentation explicitly frames the skill as a way for AI agents to control the user's local Chromium browser and bypass platform IP restrictions, but it does not clearly warn that this enables interaction with authenticated sessions, local browsing context, and user data. In this context, missing safety warnings materially increases the risk of misuse because an agent or operator may treat the relay as routine infrastructure rather than a highly privileged browser-control channel.

External Transmission

Medium
Category
Data Exfiltration
Content
TOKEN=$(cat /tmp/browser-relay-token)

# Health check
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:18792/health

# List browser tabs
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:18792/tabs
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README explicitly documents a workflow that captures browser screenshots and uploads them to Telegram, but it does not warn users that screenshots may contain sensitive page content, session information, personal data, or other confidential material. In the context of a browser-control relay, this is materially risky because the tool is designed to operate on a user's real local browser session, making accidental third-party disclosure more likely.

External Transmission

Medium
Category
Data Exfiltration
Content
# 2. Send to Telegram
TG_BOT_TOKEN="your-bot-token"
TG_CHAT_ID="your-chat-id"
curl -s -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendPhoto" \
  -F "chat_id=${TG_CHAT_ID}" \
  -F "photo=@/tmp/relay_screenshot.png"
```
Confidence
95% confidence
Finding
The direct reference to `https://api.telegram.org/` confirms use of an external third-party endpoint for screenshot delivery. In a browser automation relay, this increases the risk of accidental data leakage because captured content from local browsing sessions can leave the host environment without adequate warning or consent controls.

External Transmission

Medium
Category
Data Exfiltration
Content
# 2. Send to Telegram
TG_BOT_TOKEN="your-bot-token"
TG_CHAT_ID="your-chat-id"
curl -s -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendPhoto" \
  -F "chat_id=${TG_CHAT_ID}" \
  -F "photo=@/tmp/relay_screenshot.png"
```
Confidence
95% confidence
Finding
The direct reference to `https://api.telegram.org/` confirms use of an external third-party endpoint for screenshot delivery. In a browser automation relay, this increases the risk of accidental data leakage because captured content from local browsing sessions can leave the host environment without adequate warning or consent controls.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The manifest description and the entire operational guidance are written in Chinese, and the example input text also assumes Chinese usage. This creates a locale/language constraint for the skill without documenting user choice, opt-in, or a justified region-specific limitation.

External Transmission

Medium
Category
Data Exfiltration
Content
### 1. 检查 Chromium 是否运行

```bash
curl -s http://127.0.0.1:9222/json/version
```

如果连接失败,说明 Chromium 未启动,需要启动它(见下方"启动 Chromium")。
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Session Persistence

Medium
Category
Rogue Agent
Content
### 2. 启动 Chromium(如果未运行)

```bash
DISPLAY=:0 nohup chromium --remote-debugging-port=9222 --remote-allow-origins=* > /dev/null 2>&1 &
```

注意事项:
Confidence
88% confidence
Finding
Launching Chromium with `nohup ... &` creates a persistent background browser session beyond the immediate interaction. In this skill's context, that persistence matters because the browser may remain logged into user sites with remote debugging enabled, increasing the window during which local malware, another process, or later agent actions can control or inspect the session.

External Transmission

Medium
Category
Data Exfiltration
Content
# 1. 截图并保存
TOKEN=$(cat /tmp/browser-relay-token)
SCREENSHOT_PATH="/tmp/relay_screenshot.png"
curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"quality":80}' http://127.0.0.1:18792/screenshot \
  | python3 -c "
import sys, json, base64
Confidence
92% confidence
Finding
The screenshot flow extracts browser-rendered content from the user's local browser and writes it to a file, preparing it for later transmission. Even though the capture request is local, the skill context explicitly couples this with Telegram upload, so sensitive on-screen information from authenticated sessions can be collected and forwarded outside the host.

External Transmission

Medium
Category
Data Exfiltration
Content
# 2. 通过 Telegram Bot API 发送图片
TG_BOT_TOKEN="<从 session_state 或 memory 获取>"
TG_CHAT_ID="<从 session_state 或 memory 获取>"
curl -s -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendPhoto" \
  -F "chat_id=${TG_CHAT_ID}" \
  -F "photo=@${SCREENSHOT_PATH}"
```
Confidence
97% confidence
Finding
The hardcoded use of `https://api.telegram.org/` confirms an external data sink. Combined with browser automation and screenshot capture, this creates a direct path to move data from the user's local browser session to a remote service outside the user's machine and outside the platform's control.

External Transmission

Medium
Category
Data Exfiltration
Content
# 2. 通过 Telegram Bot API 发送图片
TG_BOT_TOKEN="<从 session_state 或 memory 获取>"
TG_CHAT_ID="<从 session_state 或 memory 获取>"
curl -s -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendPhoto" \
  -F "chat_id=${TG_CHAT_ID}" \
  -F "photo=@${SCREENSHOT_PATH}"
```
Confidence
97% confidence
Finding
The hardcoded use of `https://api.telegram.org/` confirms an external data sink. Combined with browser automation and screenshot capture, this creates a direct path to move data from the user's local browser session to a remote service outside the user's machine and outside the platform's control.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The documentation and manifest indicate support for sending screenshots to Telegram, yet this file only returns base64 screenshot data over HTTP and contains no Telegram API calls or message-sending logic. This is a direct mismatch between the advertised behavior and the implemented functionality.

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Content
async def get_tabs():
    session = await get_http_session()
    async with session.get(f"http://{CDP_HOST}:{CDP_PORT}/json") as resp:
        tabs = await resp.json()
    return [t for t in tabs if t.get("type") == "page"]
Confidence
60% confidence
Finding
Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Content
async def get_tabs():
    session = await get_http_session()
    async with session.get(f"http://{CDP_HOST}:{CDP_PORT}/json") as resp:
        tabs = await resp.json()
    return [t for t in tabs if t.get("type") == "page"]
Confidence
60% confidence
Finding
Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Content
async def get_tabs():
    session = await get_http_session()
    async with session.get(f"http://{CDP_HOST}:{CDP_PORT}/json") as resp:
        tabs = await resp.json()
    return [t for t in tabs if t.get("type") == "page"]
Confidence
60% confidence
Finding
Request target host is built from a dynamic or untrusted value. If the host is attacker-influenced, this enables SSRF to arbitrary internal or metadata endpoints.

Dynamic Request Target

Medium
Category
Server-Side Request Forgery
Content
body = await request.json() if request.can_read_body else {}
    url = body.get("url", "about:blank") if body else "about:blank"
    session = await get_http_session()
    async with session.get(f"http://{CDP_HOST}:{CDP_PORT}/json/new?{url}") as resp:
        tab = await resp.json()
    return web.json_response({"id": tab["id"], "url": tab.get("url", "")})
Confidence
80% confidence
Finding
The /tab/new handler concatenates untrusted user input directly into the CDP /json/new query string. While this does not create classic arbitrary-destination SSRF, it does let an authenticated caller force the local browser to open arbitrary URLs, including internal network resources or sensitive local services, using the user's machine and browser context.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script prints the relay token directly to stdout when the service is already running, which can expose a bearer secret to shell history, terminal scrollback, shared logs, or calling processes. In this skill's context, the token appears to gate access to a relay that controls the user's local Chromium instance, so disclosure could enable unauthorized browser control and access to browsing context or screenshots.

Static analysis

No suspicious patterns detected.