T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/wizard.mjs:286
- Finding
- Unauthenticated Localhost API Permits Cross-Site State-Changing Requests<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wizard.mjs:286-296, 373-400` **Vulnerability Type**: Missing authentication and CSRF protection on privileged localhost endpoints **Risk Level**: High ### Complete Code Snippet ```js const server = http.createServer(async (req, res) => { try { const u = new URL(req.url, `http://${req.headers.host}`); if (req.method === 'GET' && u.pathname === '/') { return text(res, 200, htmlPage(), 'text/html; charset=utf-8'); } if (u.pathname.startsWith('/api/')) { let body = ''; req.on('data', c => (body += c)); await new Promise(r => req.on('end', r)); const payload = body ? JSON.parse(body) : {}; ``` ```js if (req.method === 'POST' && u.pathname === '/api/finalize') { if (!state.accountId) return json(res, 400, { ok: false, error: 'set accountId first' }); if (!state.guildId) return json(res, 400, { ok: false, error: 'pick a guild first' }); if (!state.userId) return json(res, 400, { ok: false, error: 'pick your user first' }); await writeBaselineConfig(); await openclaw(['gateway', 'restart']); // Wait for pairing request for THIS account. const deadline = Date.now() + 5 * 60 * 1000; while (Date.now() < deadline) { const { stdout } = await openclaw(['pairing', 'list', 'discord', '--account', state.accountId, '--json']); let data; try { data = JSON.parse(stdout); } catch { data = null; } const reqs = data?.requests || data || []; if (Array.isArray(reqs) && reqs.length) { const code = reqs[0]?.code; if (code) { state.pairingCode = code; await openclaw(['pairing', 'approve', 'discord', code]); return json(res, 200, { ok: true, pairingApproved: true }); } } await wait(2000); } return json(res, 408, { ok: false, error: 'Timeout waiting for pairing. Ensure Discord allows DMs from server members, then DM the bot and retry.' }); } ``` ### Technical Analysis The service is ...[truncated 2130 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Generate a cryptographically random secret at startup using `crypto.randomBytes()`. - Require that secret on every API request, preferably through a SameSite-protected session cookie and an additional CSRF token for state-changing operations. - Validate `Origin` against the exact expected localhost origin. - Validate the `Host` header against an explicit allowlist such as `127.0.0.1:8787` and reject unexpected values. - Require `Content-Type: application/json` for JSON API requests. - Apply strict request-body size limits. - Separate read-only and state-changing routes and require explicit user confirmation immediately before configuration writes, restarts, and pairing approvals. - Consider shutting down the wizard automatically after successful completion or a short inactivity timeout. ]]>
