T09 · Insecure Skill Coding Practices
Error
- Location
- civic-tool-runner.ts:35
- Finding
- Bearer Token Disclosure Through an Unvalidated MCP Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `civic-tool-runner.ts`, lines 35-43 and 197-198 **Vulnerability Type**: Credential exposure through an unvalidated destination **Risk Level**: High ### Vulnerable Code ```ts constructor(url: string, token: string) { const userAgent = `openclaw/1.0.0 node/${process.version.slice(1)} (${process.platform}; ${process.arch})`; this.transport = new StreamableHTTPClientTransport(new URL(url), { requestInit: { headers: { Authorization: `Bearer ${token}`, "User-Agent": userAgent, }, }, }); ``` The destination is obtained directly from the environment: ```ts const url = process.env.CIVIC_URL ?? "https://nexus.civic.com/hub/mcp"; const token = process.env.CIVIC_TOKEN; ``` ### Technical Analysis The runner accepts `CIVIC_URL` as an arbitrary URL and unconditionally places `CIVIC_TOKEN` in the `Authorization` header. `new URL(url)` validates only that the value is syntactically a URL; it does not enforce HTTPS, verify that the destination belongs to Civic, reject embedded URL credentials, restrict ports, or establish an allowed origin. Consequently, anyone who can modify the skill environment or its OpenClaw configuration can redirect the MCP connection to an attacker-controlled endpoint. When the runner connects, the bearer token is transmitted to that endpoint. A plaintext HTTP URL would additionally expose the credential to network observers. The unsafe destination also receives subsequent MCP requests and any arguments supplied to tools advertised by that server. Endpoint control is a prerequisite; there is no evidence that an unauthenticated remote user can alter the environment through this code alone. ### Attack Path 1. An attacker gains the ability to modify the skill configuration, deployment environment, or `CIVIC_URL` value. 2. The attacker sets `CIVIC_URL` to an endpoint under their control, such as `https://attacker.example/mcp`. 3. A user or agent invokes t ...[truncated 1012 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse and validate `CIVIC_URL` before constructing the transport. 2. Require `url.protocol === "https:"`; reject plaintext HTTP and all non-HTTP schemes. 3. Maintain an explicit allowlist of approved Civic hostnames, preferably requiring an exact hostname such as `nexus.civic.com` rather than using a suffix check. 4. Reject URL usernames, passwords, unexpected ports, and fragments. 5. Ensure redirects cannot forward the `Authorization` header to a different origin. Disable redirects where supported or revalidate every redirect destination before attaching credentials. 6. Separate endpoint selection from credential attachment: only add the bearer token after the destination origin has passed validation. 7. Consider removing the configurable URL in production deployments or requiring an explicit opt-in for non-production endpoints with separate, non-production credentials. 8. Rotate the Civic token immediately if it may have been sent to an untrusted endpoint. 9. Apply least-privilege scopes and short token lifetimes to limit the consequences of disclosure. Example validation logic: ```ts function validateCivicUrl(value: string): URL { const url = new URL(value); if (url.protocol !== "https:") { throw new Error("CIVIC_URL must use HTTPS"); } if (url.hostname !== "nexus.civic.com") { throw new Error("CIVIC_URL must use an approved Civic hostname"); } if (url.username || url.password || (url.port && url.port !== "443")) { throw new Error("CIVIC_URL contains disallowed authority components"); } return url; } ``` ]]>
