Back to skill

Security audit

x-twitter-browser

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed X/Twitter automation tool, but it needs Review because it stores reusable login cookies and has unsafe browser handling around real account actions.

Review carefully before installing. Use it only with an X account you are comfortable automating, keep ~/.openclaw/auth/x-twitter/cookies.json private, revoke the X session if the file may have been exposed, and manually confirm every public action. Prefer passing tweet IDs or verified x.com URLs only, and run browser automation in a constrained environment because the packaged browser disables sandbox protections.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/repost_post.py:35
Finding
Attacker-controlled URLs are opened in an authenticated browser with Chromium sandboxing disabled<![CDATA[ ## Vulnerability Details **File Location**: `scripts/session_lib.py:28-41`; `scripts/repost_post.py:35-38, 51-54, 112`; `scripts/like_post.py:31-34, 62`; `scripts/bookmark_post.py:31-34, 66` **Vulnerability Type**: Insufficient URL validation combined with disabled browser sandboxing **Risk Level**: High ### Vulnerable Code `scripts/session_lib.py:28-41`: ```python CHROMIUM_ARGS = [ "--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--disable-software-rasterizer", "--disable-setuid-sandbox", "--disable-background-networking", "--disable-default-apps", "--disable-sync", "--no-first-run", "--no-zygote", "--disable-features=TranslateUI", "--disable-blink-features=AutomationControlled", ] ``` `scripts/repost_post.py:35-38`: ```python def open_tweet_and_repost(page, tweet_url: str, tweet_id: str, timeout_ms: int) -> None: target = tweet_url if "/status/" in tweet_url else f"https://x.com/i/status/{tweet_id}" page.goto(target, wait_until="domcontentloaded", timeout=timeout_ms) page.wait_for_timeout(3000) ``` `scripts/repost_post.py:51-54`: ```python def open_tweet_and_quote(page, tweet_url: str, tweet_id: str, text: str, timeout_ms: int) -> None: target = tweet_url if "/status/" in tweet_url else f"https://x.com/i/status/{tweet_id}" page.goto(target, wait_until="domcontentloaded", timeout=timeout_ms) page.wait_for_timeout(3000) ``` `scripts/like_post.py:31-34`: ```python def open_tweet_and_like(page, tweet_url: str, tweet_id: str, undo: bool, timeout_ms: int) -> None: target = tweet_url if "/status/" in tweet_url else f"https://x.com/i/status/{tweet_id}" page.goto(target, wait_until="domcontentloaded", timeout=timeout_ms) page.wait_for_timeout(3000) ``` `scripts/bookmark_post.py:31-34`: ```python def open_tweet_and_bookmark(page, tweet_url: str, tweet_id: str, undo: bool, timeout_ms: int) -> None: target = tweet_url if "/status/" in tweet_url else f"h ...[truncated 2929 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never navigate to the original user-supplied URL. After extracting and validating the numeric tweet ID, always construct a canonical destination: ```python tweet_id = extract_tweet_id(tweet_input) target = f"https://x.com/i/status/{tweet_id}" page.goto(target, wait_until="domcontentloaded", timeout=timeout_ms) ``` 2. If preserving supplied URLs is necessary, parse them with `urllib.parse.urlsplit()` and enforce all of the following: - Scheme is exactly `https`. - Hostname is exactly `x.com` or another explicitly approved X hostname. - No embedded credentials are present. - The path matches a strict tweet-status path pattern. - The normalized destination is reconstructed rather than using the raw input. 3. Remove `--no-sandbox` and `--disable-setuid-sandbox`. If the deployment environment cannot run Chromium with sandboxing enabled, execute the browser inside a separately isolated, non-privileged container or VM with: - No host filesystem access except narrowly required files. - A read-only project directory. - Restricted network egress. - No Linux capabilities. - A dedicated unprivileged account. 4. Separate unauthenticated navigation from the authenticated X context. The authenticated context should only be permitted to visit allowlisted X origins. 5. Add regression tests covering malicious inputs such as: - `https://attacker.example/status/123` - `http://x.com/status/123` - `https://x.com.attacker.example/status/123` - `file:///tmp/status/123` ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/setup.sh:12
Finding
Setup installs mutable unpinned Playwright and browser dependencies<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.sh:12-22` **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Medium ### Vulnerable Code ```bash echo "Installing playwright (Python package)..." python3 -m pip install --user playwright echo "✓ Playwright package installed." echo "" if [[ "$(uname)" == "Darwin" ]] && [[ -d "/Applications/Google Chrome.app" ]]; then echo "Chrome detected. Skipping Chromium install (will use system Chrome)." else echo "Installing Chromium browser (first time may take 2-5 min, ~150MB)..." python3 -m playwright install chromium echo "✓ Chromium browser installed." fi ``` ### Technical Analysis The setup script installs `playwright` without a version constraint or package hash. Each execution can therefore resolve to a different release from the configured Python package index. The browser installation is then selected by that mutable Playwright package and is likewise not independently pinned or integrity-checked by the project. Package installation is a code-execution boundary. Python package installation may execute package build or installation logic, and the installed Playwright code is later imported and executed by every skill action. Although there is no evidence that the current upstream Playwright package is malicious, the project does not provide reproducible dependency resolution or protection against an upstream compromise, unsafe package-index configuration, or an unexpectedly vulnerable future release. On macOS, the script conditionally uses an existing system Chrome installation, which further makes the effective runtime dependent on an externally managed browser version. ### Attack Path 1. A user or agent runs `./scripts/setup.sh`. 2. `pip` resolves the mutable package name `playwright` using the environment's configured package indexes. 3. The selected package and its installation logic run with the privileges of the invoking user. 4. The installed ...[truncated 1100 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin Playwright to a reviewed exact version in a dependency file: ```text playwright==<reviewed-version> --hash=sha256:<verified-wheel-hash> ``` 2. Install dependencies with hash enforcement: ```bash python3 -m pip install --user --require-hashes -r requirements.txt ``` 3. Generate and review a lock file for every supported platform and Python version. Update it through a controlled dependency-review process rather than resolving the latest release during installation. 4. Use an explicitly configured trusted package index and prevent fallback to unexpected indexes. In controlled deployments, mirror reviewed wheels and browser artifacts internally. 5. Pin and verify the corresponding browser revision or container image. Record checksums or cryptographic provenance for downloaded artifacts where the Playwright tooling permits it. 6. Run installation and browser automation under a dedicated unprivileged account or isolated container with only the filesystem and network permissions required by the skill. 7. Add automated dependency scanning and scheduled review for Playwright, Chromium, and transitive dependencies. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (6)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The description claims a broader X/Twitter automation capability set: logging in and performing posting, replying, reposting, liking, and bookmarking. However, this code chunk is narrowly focused on composing and submitting a new post, plus verifying an existing session. There is no implementation for replying, reposting, liking, or bookmarking in the supplied code. Also, while the description emphasizes logging in, this chunk does not implement an interactive login flow; it relies on helper functions to launch a browser and verify session state. This is a material description-to-behavior mismatch because the declared skill capabilities significantly exceed what the code shown actually does.

Lp1

High
Category
MCP Least Privilege
Confidence
75% confidence
Finding
The skill uses 'file_read' 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 'env' capability that is not listed in its permissions. This may indicate deceptive intent or missing permission declarations.

Session Persistence

Medium
Category
Rogue Agent
Content
## Operational requirements

- Before the first action, check if session exists; if not, run the two-phase login flow (see "Session management" above)
- Run `--verify-only` before any write operation
- Confirm the action and content before executing
- Do not commit cookies to the repo (`~/.openclaw/auth/`)
- Call `scripts/*.py` directly
Confidence
95% confidence
Finding
The skill explicitly persists authenticated X/Twitter session cookies in a shared long-lived location under ~/.openclaw/auth/x-twitter/cookies.json for reuse across runs and skills. If that file is read by another local process, exfiltrated, or copied to another machine, an attacker may gain unauthorized access to the user’s X account without needing credentials or 2FA.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script performs a live reply action immediately after session verification and text entry, with no explicit confirmation gate, dry-run default, or strong user warning before clicking the post button. In a skill designed to control a real authenticated X/Twitter session, this increases the risk of unintended public posting if the caller supplies the wrong tweet ID, reply text, or invokes the script in error.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The browser context is hard-coded to `locale="en-US"`, and the injected script further forces `navigator.languages` to `['en-US', 'en']`. This imposes an English/US locale on all users without offering a choice or documenting a justified region-specific requirement.

Static analysis

No suspicious patterns detected.