T09 · Insecure Skill Coding Practices
- Location
scripts/xreader.mjs:446- Finding
Unrestricted URL Handling Enables Server-Side Request Forgery and Local Content Extraction
- Content
View full analysis
{}); await page.waitForTimeout(1500); ``` ```js async function main() { const { mode, debug, thread, url } = parseArgs(process.argv.slice(2)); const auth = loadAuth(); if (!auth) { console.log(JSON.stringify({ ok: false, error: 'auth_missing', message: `Missing auth file. Expected ${NEW_AUTH_PATH} (legacy fallback: ${LEGACY_AUTH_PATH}).`, }, null, 2)); process.exit(2); } let canonicalUrl = url; let resolvedToArticle = false; if (/^https?:\/\/t\.co\//i.test(url)) { canonicalUrl = await resolveUrl(url); resolvedToArticle = /x\.com\/i\/article\//i.test(canonicalUrl); } if (/x\.com\/i\/article\//i.test(canonicalUrl)) { const article = await extractArticleWithPlaywright(canonicalUrl, auth, mode, debug); console.log(JSON.stringify(article, null, 2)); return; } ``` ```js const tweetText = tweet.full_text || tweet.text || ''; if (/^https?:\/\/t\.co\//i.test(tweetText.trim())) { try { const maybe = await resolveUrl(tweetText.trim()); if (/x\.com\/i\/article\//i.test(maybe)) articleUrl = maybe; } catch {} } ``` ### Technical Analysis The Skill determines whether a URL represents an X article by applying the substring regular expression `/x\.com\/i\/article\//i` to the complete URL. It does not parse the URL and verify that its hostname is exactly an approved X domain. Consequently, an attacker-controlled URL such as: ```text http://127.0.0.1/x.com/i/ ...[truncated 3008 chars]- Remediation
View remediation
{ const requestUrl = new URL(route.request().url()); const allowedHosts = new Set(['x.com', 'www.x.com', 'abs.twimg.com', 'pbs.twimg.com']); if (requestUrl.protocol ! ...[truncated 595 chars]
