T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/run.js:122
- Finding
- Remote-Provided Browser Links Are Not Restricted to Trusted HTTPS Origins<![CDATA[ ## Vulnerability Details **File Location**: `scripts/run.js:122-131`, with unsafe values used at `scripts/run.js:1154, 1192, 1203, 1506-1539, 1564-1579` **Vulnerability Type**: Insufficient validation of security-sensitive browser links **Risk Level**: Medium ### Vulnerable Code ```js function pickLink(data) { if (!data || typeof data !== 'object') return ''; const candidates = [ data.url, data.link, data.configureUrl, data.configUrl, data.verifyUrl ]; return candidates.find((value) => typeof value === 'string' && /^https?:\/\//.test(value)) || ''; } ``` The WeChat configuration handler passes the remotely supplied value directly to the user and embeds it in an instruction intended for the Agent: ```js const link = pickLink(data); if (!link) { console.error(JSON.stringify({ success: false, error: '服务器没有返回可打开的 AppID/AppSecret 配置短链', responseKeys: data && typeof data === 'object' ? Object.keys(data) : [] }, null, 2)); process.exit(1); } process.stderr.write('\n请在浏览器中打开以下链接填写 AppID / AppSecret:\n'); process.stderr.write(` ${link}\n`); console.log(JSON.stringify({ success: true, url: link, serverIps, instruction: `[AI必读] 两件事都必须做完,缺一不可:(1)把下面这个完整 URL 原文粘贴给用户,不要改写、不要只输出 Markdown 超链接文字、不要用"点击此处"替代。用户需要在浏览器中打开这个 URL 填写 AppID/AppSecret。URL:${link}${ipBlock}` }, null, 2)); ``` The login flow also consumes and prints `verifyUrl` without applying `pickLink()` or equivalent origin validation: ```js const { deviceCode, userCode, verifyUrl, expiresInSec } = initResp.data; const expiresAt = Date.now() + expiresInSec * 1000; flushStderr('\n请在浏览器中打开以下链接,确认绑定到你的账号:\n'); flushStderr(` ${verifyUrl}\n`); flushStdout(JSON.stringify({ success: true, pendingCheckpoint: true, verifyUrl, userCode, expiresInSec, expiresAt, pendingPath: LOGIN_PENDING_PATH, credentialsPath: CREDENTIALS_PATH, instruction: '[AI必读] 请把 verifyUrl 完整 URL 原文交给用户,并附上 userCode。用户在浏览器点击确认后,调用 `web-publisher login-status` 完成登录 ...[truncated 2409 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Create one validator for every browser-facing URL returned by the service: ```js function trustedBrowserLink(value, allowedPaths) { if (typeof value !== 'string') return ''; let url; try { url = new URL(value); } catch { return ''; } if ( url.protocol !== 'https:' || url.origin !== 'https://tools.siping.me' || url.username || url.password || url.hash ) { return ''; } if (!allowedPaths.some((prefix) => url.pathname.startsWith(prefix))) { return ''; } return url.href; } ``` 2. Apply it separately with narrowly scoped paths: - Login: `/skill/bind` - WeChat configuration: the exact expected WeChat configuration route - Wrapper configuration: the exact expected wrapper route 3. Reject HTTP links rather than relying on users or browsers to upgrade them. 4. Validate `verifyUrl` before writing the pending checkpoint or displaying any login instructions. 5. Do not emit “relay verbatim” instructions unless the URL has passed local origin and path validation. 6. Add regression tests covering: - HTTP downgrade links. - Foreign origins. - Look-alike domains. - Embedded credentials. - protocol-relative URLs. - unexpected paths. - malformed and encoded URLs. ]]>
