T09 · Insecure Skill Coding Practices
- Location
scripts/wizard.mjs:286- Finding
Unauthenticated Localhost API Permits Cross-Site State-Changing Requests
- Content
View full analysis
{ 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
View remediation
