T09 · Insecure Skill Coding Practices
- Location
scripts/xhs.js:40- Finding
Unrestricted Configurable Endpoint Can Receive Bearer Credentials and User Content
- Content
View full analysis
Vulnerability Details
File Location:
scripts/xhs.js:40-59andscripts/client.js:2-23, 34-48
Vulnerability Type: Unvalidated network destination used for sensitive-data transmission
Risk Level: MediumVulnerable Code
scripts/xhs.js:40-59:js const { token, urls } = loadClient(); const url = urls.xhs_generate; if (!url) throw new Error('AIZNT_PROXY_URLS 缺少 xhs_generate'); let body = {}; if (opts['body-file']) { const fs = require('fs'); body = JSON.parse(fs.readFileSync(opts['body-file'], 'utf8')); } else if (opts.body) { body = readJson(opts.body, '--body'); } const data = await fetchJson(url, { method: 'POST', headers: authHeaders(token, { 'Content-Type': 'application/json' }), body: JSON.stringify(body), }); console.log(JSON.stringify(data, null, 2));scripts/client.js:2-23:js function loadClient() { const token = (process.env.TS_TOKEN || '').trim(); if (!token) { throw new Error('缺少 TS_TOKEN(天树对话凭证 ts_xxx,对应 skills.entries 的 apiKey)'); } const raw = process.env.AIZNT_PROXY_URLS; if (!raw || !String(raw).trim()) { throw new Error('缺少 AIZNT_PROXY_URLS(JSON 字符串,与 GET /miniapp/ai/chat/credentials 返回的 aiznt_proxy_urls 一致)'); } let urls; try { urls = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { throw new Error('AIZNT_PROXY_URLS 不是合法 JSON'); } if (!urls || typeof urls !== 'object') { throw new Error('AIZNT_PROXY_URLS 必须是对象'); } return { token, urls }; }scripts/client.js:34-48:js function authHeaders(token, extra = {}) { return { Authorization: `Bearer ${token}`, ...extra, }; } async function fetchJson(url, options = {}) { const res = await fetch(url, options); const text = await res.text(); let data; try { data = text ? JSON.parse(text) : {}; } catch { throw new Error(`非 JSON 响应 (${res.status}): ${text.slice(0, 200)}`); }Technical Analysis
The Skill obtains the ...[truncated 3167 chars]
- Remediation
View remediation
Remediation Suggestions
- Parse
xhs_generatewith the standardURLclass before making a request. - Require
https:and reject all other protocols, includinghttp:,file:, and non-HTTP schemes. - Maintain an explicit allowlist of documented service hostnames and approved ports. Compare normalized hostnames exactly rather than using substring or suffix checks that can be bypassed.
- Reject URLs containing embedded usernames or passwords.
- If the service must never access internal resources, resolve the destination and reject loopback, link-local, private, multicast, and otherwise prohibited address ranges. Account for DNS rebinding where applicable.
- Disable redirects with
redirect: 'error', or manually validate every redirect destination against the same scheme, hostname, port, and network-range policy before following it. - Use a narrowly scoped, short-lived token that is valid only for the XHS generation operation. Apply expiration, rotation, revocation, and server-side audience restrictions.
- Keep the trusted endpoint in administrator-controlled configuration that ordinary Skill input cannot modify. Protect the configuration-delivery mechanism with integrity and access controls.
- Document the exact external service receiving the content and clearly disclose that submitted JSON leaves the local system.
- Add automated tests confirming rejection of HTTP URLs, unapproved domains, embedded credentials, private-network destinations, malformed URLs, and redirect attempts.
A hardened validation pattern should be applied before
authHeaders()is called:js function validateServiceUrl(value) { const url = new URL(value); const allowedHosts = new Set(['approved-service.example']); if (url.protocol !== 'https:') { throw new Error('The service URL must use HTTPS'); } if (!allowedHosts.has(url.hostname)) { throw new Error('The service hostname is not approved'); } if (url.username || url.password) { throw new Erro ...[truncated 518 chars]- Parse
