T09 · Insecure Skill Coding Practices
Error
- Location
- bin/common.js:3
- Finding
- Configurable API Base URL Breaks the Declared Localhost Trust Boundary<![CDATA[ ## Vulnerability Details **File Location**: `bin/common.js:3-4, 57-105` **Vulnerability Type**: Arbitrary cleartext API destination and sensitive request disclosure **Risk Level**: High ### Vulnerable Code ```javascript const DEFAULT_BASE_URL = process.env.MORELOGIN_LOCAL_API_URL || 'http://127.0.0.1:40000'; const DEFAULT_TIMEOUT_MS = Number.parseInt(process.env.MORELOGIN_LOCAL_API_TIMEOUT_MS || '10000', 10); function requestApi(endpoint, { method = 'POST', body, baseUrl = DEFAULT_BASE_URL, timeoutMs = DEFAULT_TIMEOUT_MS } = {}) { return new Promise((resolve, reject) => { const url = new URL(endpoint, baseUrl); const payload = body === undefined ? undefined : JSON.stringify(body); const options = { hostname: url.hostname, port: url.port || 80, path: `${url.pathname}${url.search}`, method, headers: { 'Content-Type': 'application/json', }, timeout: timeoutMs, }; if (payload) { options.headers['Content-Length'] = Buffer.byteLength(payload); } const req = http.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { let parsed; try { parsed = JSON.parse(data); } catch (error) { parsed = { raw: data }; } resolve({ statusCode: res.statusCode, ok: res.statusCode >= 200 && res.statusCode < 300, body: parsed, }); }); }); req.on('timeout', () => { req.destroy(); reject(new Error(`Request timeout after ${timeoutMs}ms`)); }); req.on('error', reject); if (payload) { req.write(payload); } req.end(); }); } ``` ### Technical Analysis The Skill declares that it communicates only with the MoreLogin Local API at `http://127.0.0.1:40000`. However, `MORELOGIN_LOCAL_API_URL` can replace that destination with an arbitrary hostname, and `request ...[truncated 1918 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Enforce a strict loopback destination policy before issuing a request: ```javascript function validateLocalApiUrl(baseUrl) { const url = new URL(baseUrl); const allowedHosts = new Set(['127.0.0.1', 'localhost', '::1']); if (url.protocol !== 'http:' || !allowedHosts.has(url.hostname)) { throw new Error('MoreLogin Local API URL must use a loopback address'); } return url; } ``` 2. Remove `MORELOGIN_LOCAL_API_URL` entirely if endpoint customization is not required by the declared functionality. 3. If customization is retained, require explicit user confirmation before accepting any non-default port. 4. Reject credentials embedded in a URL and reject redirects to non-loopback destinations. 5. Use a dedicated local socket or authenticated local transport if supported by MoreLogin. 6. Add tests confirming that public IP addresses, wildcard addresses, encoded loopback bypasses, and remote DNS names are rejected. 7. Document the exact supported environment variable; current documentation also refers to the inconsistent name `MORELOGIN_API_URL`. ]]>
