T09 · Insecure Skill Coding Practices
- 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: MediumVulnerable 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:
- Use the normalized URL returned by
URL, not the original untrusted string. - Keep the hostname allowlist narrow and explicit; do not use suffix or substring matching.
- Reject non-status URLs rather than assigning the tweet ID
unknown. - Ensure the browser layer independently enforces the same destination allowlist and blocks redirects to unapproved origins where supported.
- Clearly delimit untrusted URL data when generating agent instructions.
- Add negative tests for deceptive inputs, including:
https://attacker.example/?x.com/status/123https://x.com.attacker.example/user/status/123http://x.com/user/status/123javascript:x.com/status/123https://user:password@x.com/user/status/123https://x.com:8443/user/status/123
- Add positive tests for the explicitly supported
x.comandtwitter.comhostnames.
- Use the normalized URL returned by
