Back to skill

Security audit

X Extract

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a public X/Twitter post extractor, but its URL and download handling are too loosely scoped for the browser and file-writing powers it asks agents to use.

Review before installing. Use it only with explicit public x.com or twitter.com status URLs, avoid running the standalone script on untrusted input, and require confirmation plus a controlled output folder before allowing media downloads. I found no evidence of persistence, exfiltration, destructive behavior, or hidden credential collection.

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
] [--download-media]'); process.exit(1); } // Extract tweet ID from URL const tweetIdMatch = url.match(/status\/(\d+)/); const tweetId = tweetIdMatch ? tweetIdMatch[1] : 'unknown'; ``` The insufficiently validated value is subsequently embedded in browser instructions: ```javascript const instructions = ` # Tweet Extraction Instructions Since this is a standalone script, you need to use OpenClaw's browser tool directly. Run these commands in your OpenClaw session: \`\`\` 1. Open the URL: browser ...[truncated 3517 chars]:23
Finding

Weak URL Validation Allows Navigation to Attacker-Controlled Origins

Content
View full analysis

Vulnerability Details

File Location: scripts/extract.mjs, lines 23–37 and 59–105
Vulnerability Type: Insufficient URL validation
Risk Level: Medium

Vulnerable Code

javascript
// Parse arguments
const args = process.argv.slice(2);
const url = args[0];
const outputIndex = args.indexOf('--output');
const outputFile = outputIndex !== -1 ? args[outputIndex + 1] : null;
const downloadMedia = args.includes('--download-media');

if (!url || !url.includes('x.com')) {
  console.error('Usage: ./extract.mjs <x.com-url> [--output <file>] [--download-media]');
  process.exit(1);
}

// Extract tweet ID from URL
const tweetIdMatch = url.match(/status\/(\d+)/);
const tweetId = tweetIdMatch ? tweetIdMatch[1] : 'unknown';

The insufficiently validated value is subsequently embedded in browser instructions:

javascript
const instructions = `
# Tweet Extraction Instructions

Since this is a standalone script, you need to use OpenClaw's browser tool directly.

Run these commands in your OpenClaw session:

\`\`\`
1. Open the URL:
   browser action=open profile=openclaw targetUrl="${url}"

2. Take a snapshot to extract content:
   browser action=snapshot targetId=<TARGET_ID> snapshotFormat=aria

3. Look for these elements in the snapshot:
   - Tweet text: role=article
   - Author: role=link with author name
   - Timestamp: role=time
   - Media: role=img or role=link[href contains /photo/]
   - Metrics: like count, retweet count, reply count
\`\`\`

## Expected Output Format

\`\`\`markdown
# Tweet by @username

**Posted:** YYYY-MM-DD HH:MM

Tweet text goes here...

**Media:**
- [Image 1](url)
- [Image 2](url)

**Engagement:**
- Likes: 123
- Retweets: 45
- Replies: 67

**Source:** ${url}
\`\`\`

## Integration Note

This script requires OpenClaw's browser tool. For full automation, run via OpenClaw agent with browser tool access.
`;

Technical Analysis

The script validates the supplied URL using only url.includes('x.com'). This is a substri ...[truncated 2466 chars]

Remediation
View remediation

Remediation Suggestions

Replace substring validation with strict parsing and allowlisting:

javascript
function validateTweetUrl(value) {
  let parsed;

  try {
    parsed = new URL(value);
  } catch {
    throw new Error('Invalid URL');
  }

  const allowedHosts = new Set([
    'x.com',
    'www.x.com',
    'twitter.com',
    'www.twitter.com',
  ]);

  if (parsed.protocol !== 'https:') {
    throw new Error('Only HTTPS URLs are allowed');
  }

  if (!allowedHosts.has(parsed.hostname.toLowerCase())) {
    throw new Error('Unsupported hostname');
  }

  if (parsed.username || parsed.password || parsed.port) {
    throw new Error('Credentials and custom ports are not allowed');
  }

  const match = parsed.pathname.match(/^\/[^/]+\/status\/(\d+)\/?$/);
  if (!match) {
    throw new Error('URL must identify a valid tweet status');
  }

  return {
    url: parsed.toString(),
    tweetId: match[1],
  };
}

Additional hardening measures:

  1. Use the normalized URL returned by URL, not the original untrusted string.
  2. Keep the hostname allowlist narrow and explicit; do not use suffix or substring matching.
  3. Reject non-status URLs rather than assigning the tweet ID unknown.
  4. Ensure the browser layer independently enforces the same destination allowlist and blocks redirects to unapproved origins where supported.
  5. Clearly delimit untrusted URL data when generating agent instructions.
  6. Add negative tests for deceptive inputs, including:
    • https://attacker.example/?x.com/status/123
    • https://x.com.attacker.example/user/status/123
    • http://x.com/user/status/123
    • javascript:x.com/status/123
    • https://user:password@x.com/user/status/123
    • https://x.com:8443/user/status/123
  7. Add positive tests for the explicitly supported x.com and twitter.com hostnames.
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (9)

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

Both the file header and manifest describe a credential-free extraction tool for x.com content, but the implementation never invokes a browser automation API or parses tweet data. Instead, it generates instructional text telling a human or external agent how to do the extraction manually, which is a material mismatch between claimed behavior and actual behavior.

Content

No source excerpt is available for this finding.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
90% confidence
Finding

The script embeds untrusted user input directly into a generated instruction/prompt block without validation or escaping. An attacker can supply crafted input containing quotes, newlines, or additional pseudo-commands so that when a human or agent follows the emitted instructions, they may execute unintended browser actions or be misled into operating on attacker-controlled targets.

Content

Scanner excerpt · scripts/extract.mjs (reported line 56)May include surrounding context.

js
console.log(JSON.stringify(snapshotRequest));
`;

// For now, output instructions for manual extraction
// In production, this would integrate with OpenClaw's browser tool via API

const instructions = `

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The manifest advertises tweet content extraction, but the documented behavior extends to downloading files and extracting full threads, which broadens the operational scope beyond what a caller would reasonably expect. This scope drift is dangerous because it can cause downstream agents or users to authorize a seemingly low-risk extraction skill that actually performs additional network access, storage writes, and higher-volume scraping behavior.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill’s core purpose is tweet extraction, but it also instructs the agent to invoke shell tools like curl/wget to download remote media and write files locally. This unnecessarily expands capability from passive extraction to active file/network operations, increasing the risk of unsafe command usage, unintended local writes, and abuse of attacker-controlled URLs embedded in scraped content.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The media download workflow explicitly uses exec with curl/wget to fetch remote resources and save them as local files, but it provides no user-facing warning or consent step about shell execution or filesystem writes. In this context, extracted media URLs originate from untrusted page content, so using them directly in shell-driven downloads increases the risk of unsafe execution patterns, unexpected local side effects, and downloading malicious or oversized content.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The comment 'Use OpenClaw browser tool to extract content' and the surrounding setup imply active browser-tool integration, yet the constructed command is never executed and the script explicitly falls back to manual instructions. This is an intent/documentation contradiction, not just an omitted implementation detail.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger list contains several generic phrases such as "get x.com", "fetch tweet", and "download tweet" that could match routine user requests more broadly than intended. Overly broad routing can cause this skill to activate unexpectedly, sending users into browser-automation-based scraping flows when they did not explicitly request that behavior, which increases the chance of misuse, privacy issues, or incorrect tool selection.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The trigger examples include broad phrases such as "extract this tweet for me," "pull tweet data," and "scrape tweet content" without requiring a URL or stronger x.com/twitter.com scoping. That can cause the skill to activate on loosely related user requests and route web-scraping/browser-automation behavior where the user intent is ambiguous, increasing the chance of unintended browsing or data extraction actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The README advertises an optional media download feature but does not warn users that enabling it will write files to local storage. In an agent/tooling context, undocumented file-write behavior can surprise operators, bypass user expectations, and increase the chance of unsafe automation or unreviewed artifact creation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.