Back to skill

Security audit

Cdp Browser

Security checks for vulnerabilities and agentic risk

Overview

This browser-control skill is disclosed, but it needs review because it can use logged-in browser sessions and its optional Telegram approval flow can post to X from weak shared pending state.

Install only if you are comfortable giving the skill control over a Chrome profile that may already be logged into sensitive sites. Keep remote debugging bound to localhost, avoid using it with sensitive browser profiles, and avoid the Telegram confirm-button posting flow until approvals are tied to a specific draft/session and expire after one use.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
pw.js:119
Finding

Telegram Tweet Approval Is Not Bound to the Reviewed Draft or Session

Content
View full analysis

Vulnerability Details

File Location: pw.js:119-126 and SKILL.md:63-75
Vulnerability Type: Shared mutable approval state / confused-deputy vulnerability
Risk Level: Medium

Vulnerable Code

js
const workspace = process.env.OPENCLAW_WORKSPACE || path.join(
  process.env.HOME || process.env.USERPROFILE || '.',
  '.openclaw',
  'workspace'
);
const pendingDir = path.join(workspace, '.cdp-browser');
const pendingPath = path.join(pendingDir, 'pending-tweet.json');

if (!fs.existsSync(pendingDir)) {
  fs.mkdirSync(pendingDir, { recursive: true });
}

fs.writeFileSync(
  pendingPath,
  JSON.stringify({ text: draftText, tabId, timestamp: Date.now() }, null, 2)
);

The approval instructions use a constant callback and direct the Agent to trust the shared file:

markdown
--buttons '[[{"text":"Confirm Post","callback_data":"cdp:tweet:confirm"}]]'

On confirm:
- Read `~/.openclaw/workspace/.cdp-browser/pending-tweet.json`
  for `text` and `tabId`
- Run `tweet-post <tabId> --confirm "<text>"`

Technical Analysis

Every pending draft in a workspace is written to the same pending-tweet.json path. A later draft silently overwrites the earlier draft. The stored state contains only the tweet text, tab ID, and timestamp; it is not bound to the Telegram chat, approving user, Agent session, confirmation message, or a unique approval nonce.

The confirmation button also always uses the same cdp:tweet:confirm callback value. Therefore, a confirmation event does not identify the specific draft that the user reviewed. The documented handler instead reads whatever content happens to occupy the shared pending file at confirmation time.

There is also no documented expiration check, one-time atomic consumption, content-hash verification, or integrity protection. Any concurrent session—or another process with write access to the workspace—can replace the approved state before it is consumed.

Attack Path

  1. A legitimate user creates draft A ...[truncated 1715 chars]
Remediation
View remediation

Remediation Suggestions

  1. Generate a cryptographically random approval identifier for every draft, for example with crypto.randomUUID() or crypto.randomBytes().
  2. Store each pending draft separately using that identifier rather than a single shared filename.
  3. Bind the stored record to:
    • Telegram chat ID;
    • approving user ID where available;
    • OpenClaw session or conversation ID;
    • confirmation message ID;
    • browser tab ID;
    • exact tweet text or a cryptographic hash of it;
    • creation and expiration times.
  4. Include the unique approval identifier in callback_data, such as cdp:tweet:confirm:&lt;approval-id&gt;.
  5. On confirmation, verify that the callback sender, chat, session, message, draft hash, and tab match the stored record.
  6. Enforce a short expiration period and reject expired approvals.
  7. Atomically consume or rename the approval record before posting so the same approval cannot be replayed.
  8. Create the state directory and files with restrictive permissions, such as directory mode 0700 and file mode 0600.
  9. Avoid trusting tweet text or a tab ID read from mutable shared state without validating its association with the confirmation event.
  10. Display the exact text or its digest at confirmation time and verify it again immediately before posting.
  11. Add tests covering concurrent drafts, cross-chat confirmation, callback replay, expired approvals, and state replacement between review and confirmation.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (11)

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · SECURITY.md (reported line 9)May include surrounding context.

md
| Finding | Risk | Change | Rationale |
|---------|------|--------|------------|
| cdp.js execSync + curl | Shell injection | Replaced with Node `fetch()` | No shell invocation; eliminates injection vector |
| pw.js eval | Arbitrary JS execution; data exfiltration | Removed; added allowlisted `query` (getText, getHtml, getUrl) | Only safe, predefined ops; no user code interpolation |
| tweet (auto-post) | Unauthorized posting on X | Split into tweet-draft (default) + tweet-post (requires `--confirm` as second arg) | User must explicitly approve before posting |
| pw.js goto / cdp.js newTab,gotoTab | Browser-context RCE via javascript:, data:, file: URLs | Strict URL validation: http/https only | Blocks arbitrary code execution in browser context |

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The skill is presented as browser control, but it also introduces Telegram messaging and an approval workflow that extends its operational reach beyond CDP. Undeclared external messaging and approval channels are security-relevant because they can be abused for social engineering, unexpected data flow, or triggering sensitive actions such as posting from an authenticated browser session through a secondary interface.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill is presented as browser control, but it also introduces Telegram messaging and an approval workflow that extends its operational reach beyond CDP. Undeclared external messaging and approval channels are security-relevant because they can be abused for social engineering, unexpected data flow, or triggering sensitive actions such as posting from an authenticated browser session through a secondary interface.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill is presented as browser control, but it also introduces Telegram messaging and an approval workflow that extends its operational reach beyond CDP. Undeclared external messaging and approval channels are security-relevant because they can be abused for social engineering, unexpected data flow, or triggering sensitive actions such as posting from an authenticated browser session through a secondary interface.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The skill is presented as browser control, but it also introduces Telegram messaging and an approval workflow that extends its operational reach beyond CDP. Undeclared external messaging and approval channels are security-relevant because they can be abused for social engineering, unexpected data flow, or triggering sensitive actions such as posting from an authenticated browser session through a secondary interface.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill exposes networked browser-control capabilities against a local CDP endpoint and references workspace/config file access, yet declares no explicit tool scope or permissions boundary. In an agent setting, missing scope metadata can cause overbroad access to be granted implicitly, making high-impact actions like reading page contents from logged-in sessions or navigating tabs easier to invoke without policy enforcement.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

Persisting a pending tweet to disk creates cross-session actionable state that can outlive the original user context and be replayed or confirmed later. In a persistent logged-in browser session, this is dangerous because stale or tampered pending data could result in unintended posting, especially if approval is inferred from a generic callback or text like 'go ahead' without strong binding to the original request.

Content

Scanner excerpt · SKILL.md (reported line 38)May include surrounding context.

md
- **tweet-draft** (default): Fills the compose box; user reviews in browser and posts manually.
- **tweet-post**: Requires `--confirm` as second arg (strict). Use when user explicitly approves ("go ahead", "post it", or Telegram confirm button).
- **Optional Telegram confirm:** When `tweet.confirmButton` is enabled in config, the agent can run `tweet-draft --save-pending` to write pending state, then send a message with an inline "Confirm Post" button. On confirm, the agent runs `tweet-post --confirm`.

### Config (required for Telegram confirm button)

Internal Network Request

Medium
Category
Server-Side Request Forgery
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.

Content

Scanner excerpt · pw.js (reported line 43)May include surrounding context.

js
args = args.filter(a => a !== '--confirm' && a !== '--save-pending');

  // Fetch tabs to get wsEndpoint
  const tabsResp = await fetch('http://localhost:9222/json/list');
  if (!tabsResp.ok) throw new Error('Browser not available at localhost:9222');
  const tabs = await tabsResp.json();
  const tab = tabs.find(t => t.id === tabId);

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The code sends user-provided URLs to a local CDP service via fetch in newTab and gotoTab, which can affect browser state and disclose browsing targets to another process. While this behavior may be part of the wrapper's purpose, these functions contain no confirmation prompt and no inline user-facing warning beyond generic code comments.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency uses a caret range (^1.48.0), which allows installation of newer minor and patch releases rather than a single exact version. This can reduce build reproducibility and may introduce supply-chain risk if a later allowed release contains a vulnerability, breaking change, or compromised package.

Content

Scanner excerpt · package.json (reported line 7)May include surrounding context.

json
"description": "CDP browser control skill for OpenClaw",
  "main": "pw.js",
  "dependencies": {
    "playwright": "^1.48.0"
  },
  "keywords": ["openclaw", "skill", "cdp", "browser", "chromium"],
  "license": "MIT",

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code makes unauthenticated HTTP requests to a locally exposed Chrome DevTools Protocol service on localhost:9222, which can control a persistent browser session that may already be logged into sensitive sites. In the stated skill context, that enables powerful actions such as reading page content, executing JavaScript, navigating authenticated tabs, or posting as the user, and the lack of any user-facing warning or consent mechanism materially increases the risk of covert misuse.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
pw.js:119