T09 · Insecure Skill Coding Practices
Error
- Location
- dashboard/server.py:151
- Finding
- Network-Exposed Dashboard Uses a Bearer Token in Cleartext URLs<![CDATA[ ## Vulnerability Details **File Location**: `dashboard/server.py:151-162`; `dashboard/index.html:808-833`; `dashboard/index.html:1232` **Vulnerability Type**: Bearer-token exposure and excessive network exposure **Risk Level**: High ### Vulnerable Code ```python def main(): import argparse parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, default=3737) args = parser.parse_args() print(f"\n{'=' * 50}") print(f" clawctl dashboard") print(f"{'=' * 50}") print(f"\n Local URL:") print(f" http://localhost:{args.port}/?token={TOKEN}") print(f"\n For Tailscale/LAN access, use your IP:") print(f" http://<your-ip>:{args.port}/?token={TOKEN}") print(f"\n Token: {TOKEN}") print(f"\n{'=' * 50}\n") app.run(host="0.0.0.0", port=args.port, threaded=True) ``` ```javascript const state = { tasks: [], agents: [], selectedTask: null, token: new URLSearchParams(location.search).get('token'), sseRetries: 0, eventSource: null, effectsOn: localStorage.getItem('cc_effects') === 'true', matrixRaf: null, sheetFocusTrap: null, previousFocus: null, }; const api = { async get(endpoint) { const res = await fetch(`${endpoint}?token=${state.token}`); if (res.status === 401) throw new Error('Unauthorized'); if (!res.ok) throw new Error(`API error: ${res.status}`); return res.json(); }, async post(endpoint, body = {}) { const res = await fetch(`${endpoint}?token=${state.token}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (res.status === 401) throw new Error('Unauthorized'); if (!res.ok) throw new Error(`API error: ${res.status}`); return res.json(); } }; ``` ```javascript const es = new EventSource(`/api/heartbeat?token=${state.token}`); ``` ### Technical Analysis The Flask server listens on `0.0.0.0`, making the dashboard reachable through every availab ...[truncated 2398 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Bind to `127.0.0.1` by default: ```python app.run(host="127.0.0.1", port=args.port, threaded=True) ``` 2. Require an explicit, clearly documented option such as `--listen-address` or `--allow-remote` before exposing the service on LAN interfaces. 3. Require HTTPS for non-loopback access, either directly or through a correctly configured authenticated reverse proxy. 4. Remove credentials from query strings. For ordinary API requests, use an `Authorization: Bearer ...` header. 5. For browser access and SSE, prefer a secure server-established session cookie with `Secure`, `HttpOnly`, and an appropriate `SameSite` policy. 6. Remove the token from the browser URL after establishing a session, using `history.replaceState`. 7. Do not print the raw persistent token or a token-bearing URL unless explicitly requested by the user. 8. Separate read-only monitoring privileges from task-mutation privileges. Mutation endpoints should require stronger authorization and should enforce task ownership rather than unconditionally using `force=True`. 9. Rotate existing tokens after deploying the corrected authentication mechanism. 10. Add `Cache-Control: no-store` and a restrictive `Referrer-Policy` as defense-in-depth controls. ]]>
