T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/clawpulse-monitor.py:165
- Finding
- Unauthenticated Internal Status Endpoint Exposes Operational Metadata<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clawpulse-monitor.py:165-168` **Vulnerability Type**: Authentication bypass and sensitive information exposure **Risk Level**: High ### Vulnerable Code ```python def do_GET(self): if self.path == "/internal": with state_lock: self._json(200, dict(state)) return if self.path not in ["/health", "/status"]: self._json(404, {"error": "not_found"}) return auth = self.headers.get("Authorization", "") if APP_TOKEN and auth != f"Bearer {APP_TOKEN}": self._json(401, {"error": "unauthorized"}) return ``` The service also binds to every available network interface by default: ```python BIND_HOST = os.environ.get("MONITOR_BIND_HOST", "0.0.0.0") ``` ### Technical Analysis The `/internal` route is processed before the bearer-token authentication check. Consequently, requests to this endpoint return a complete copy of the monitor's internal state without verifying `MONITOR_TOKEN`. The disclosed state includes: - Assistant name - Online and work status - Current and daily token usage - Thought/status text - Source and monitor timestamps - Failure and recovery counters - Last state-change time - Raw usage counters - Activity-state transition counters Unlike the generated bridge, the monitor does not enforce a loopback or Tailscale source-address allowlist. Because its default bind address is `0.0.0.0`, `/internal` can be reached by any host with network access to TCP port 8788. This behavior contradicts the Skill's declared token-protected status model and exceeds the minimum access necessary for the application. ### Attack Path 1. The user starts the monitor through `setup_clawpulse_monitor.sh --apply`. 2. The monitor binds to `0.0.0.0:8788` by default. 3. An attacker on a reachable LAN, Tailscale network, exposed host interface, or forwarded port discovers TCP port 8788. 4. The attacker sends the following request without a ...[truncated 780 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Apply authentication before dispatching any endpoint that exposes state: ```python def do_GET(self): if self.path not in ["/health", "/status", "/internal"]: self._json(404, {"error": "not_found"}) return auth = self.headers.get("Authorization", "") if not APP_TOKEN or auth != f"Bearer {APP_TOKEN}": self._json(401, {"error": "unauthorized"}) return ``` 2. Prefer removing `/internal` if it is not required by the ClawPulse application. 3. If the endpoint is required for local diagnostics, restrict it to loopback clients and bind a separate diagnostic listener to `127.0.0.1`. 4. Add the same loopback and Tailscale source-IP validation used by the bridge. 5. Change the monitor's default bind address to `127.0.0.1`. Require an explicit option to expose it remotely. 6. Return a minimal diagnostic schema rather than the complete internal state. Exclude counters and metadata not required by the client. 7. Add tests confirming that `/internal`, `/health`, and `/status` all reject missing or invalid credentials. ]]>
