Back to skill

Security audit

Remote Relay

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed remote-control relay, but its implementation weakens key security guarantees around authentication state and encrypted transport.

Install only if you trust the relay operator and can enforce wss:// configuration yourself. Before using this in production, the maintainer should reject plaintext ws:// URLs and enforce authenticated online state before accepting prompt, workflow, status, or restart commands.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
relayClient.ts:87
Finding
Operational Commands Are Accepted Before Relay Authentication Completes<![CDATA[ ## Vulnerability Details **File Location**: `relayClient.ts:87-102` **Vulnerability Type**: Missing authentication-state enforcement **Risk Level**: High ### Vulnerable Code ```ts private handleMessage(msg: { type: string; data?: unknown }): void { switch (msg.type) { case "hello_ok": console.log("[remote-relay] Authenticated"); this.backoff = INITIAL_BACKOFF; this.connectionState = "online"; this.startHeartbeat(); break; case "prompt": case "status": case "restart": case "workflow": this.dispatch(msg as { type: string; data: IncomingMessage }); break; ``` ### Technical Analysis The client changes its connection state to `online` only after receiving `hello_ok`, but operational command handling does not verify that this authentication transition has occurred. Any `prompt`, `status`, `restart`, or `workflow` message received over the socket is immediately passed to `dispatch()`. Consequently, the documented requirement that commands are accepted only while the node is authenticated and online is not enforced by the client. A malicious, compromised, incorrectly configured, or intercepted relay connection can send commands immediately after the WebSocket is established and before sending `hello_ok`. The affected commands map directly to privileged host-runtime methods: - `prompt` invokes `runtime.executePrompt()`. - `workflow` invokes `runtime.executeWorkflow()`. - `restart` invokes `runtime.restart()`. - `status` exposes runtime health information. TypeScript type assertions do not provide runtime validation, so malformed or attacker-controlled command envelopes also reach the dispatcher without schema verification. ### Attack Path 1. The OpenClaw node establishes a WebSocket connection to a malicious, compromised, or intercepted relay endpoint. 2. The endpoint does not complete the expected application-level authentication sequence and does not send `hello_ok`. 3. The endpoint ...[truncated 1130 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Enforce an explicit authenticated session state before dispatching any operational message. ```ts private authenticated = false; private handleMessage(msg: { type: string; data?: unknown }): void { if (msg.type === "hello_ok") { this.authenticated = true; this.backoff = INITIAL_BACKOFF; this.connectionState = "online"; this.startHeartbeat(); return; } const operationalTypes = new Set([ "prompt", "status", "restart", "workflow", ]); if (operationalTypes.has(msg.type)) { if (!this.authenticated || this.connectionState !== "online") { console.warn( `[remote-relay] Rejecting command before authentication: ${msg.type}` ); return; } // Validate the complete envelope with a runtime schema before dispatch. this.dispatch(msg as { type: string; data: IncomingMessage }); return; } } ``` Additional hardening should include: 1. Reset `authenticated` to `false` before opening a socket and whenever the socket closes or errors. 2. Use a runtime schema validator to verify message type, `request_id`, prompt length, workflow identifier, and parameter structure. 3. Reject missing or empty request identifiers instead of assigning `"unknown"`. 4. Add command authorization controls so each relay identity is limited to explicitly enabled capabilities. 5. Add replay protection or request nonces if the relay protocol supports them. 6. Add tests confirming that all four operational message types are rejected before `hello_ok` and after disconnection. 7. Consider closing the socket when operational commands are received before authentication, because this indicates a protocol violation. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
config.ts:7
Finding
Plaintext WebSocket Configuration Exposes Credentials and Remote-Control Traffic<![CDATA[ ## Vulnerability Details **File Location**: `config.ts:7-21` **Vulnerability Type**: Insecure transport permitted for sensitive authentication and command traffic **Risk Level**: High ### Vulnerable Code ```ts export function validateConfig(config: Partial<RemoteRelayConfig>): RemoteRelayConfig { if (!config.relay_url) throw new Error("remote-relay: relay_url is required"); if (!config.node_id) throw new Error("remote-relay: node_id is required"); if (!config.auth_token) throw new Error("remote-relay: auth_token is required"); // Normalize URL: ensure wss:// for secure connections let url = config.relay_url.replace(/\/+$/, ""); url = url.replace(/^https:\/\//, "wss://"); url = url.replace(/^http:\/\//, "ws://"); return { relay_url: url, node_id: config.node_id, auth_token: config.auth_token, }; } ``` The insecure behavior is also explicitly accepted by the test at `config.test.ts:18-21`: ```ts it("converts http to ws", () => { const result = validateConfig({ ...valid, relay_url: "http://localhost:8080" }); expect(result.relay_url).toBe("ws://localhost:8080"); }); ``` ### Technical Analysis The validator converts `http://` URLs into plaintext `ws://` URLs and does not reject an explicitly supplied `ws://` URL. Despite the comment stating that the function ensures secure connections, no requirement that the final protocol be `wss:` is enforced. The WebSocket carries highly sensitive data: - The authentication token and node identifier in the `hello` message. - Remote prompts and streamed responses. - Workflow identifiers and parameters. - Runtime status and last-error information. - Restart commands. When `ws://` is used, these values have no transport confidentiality or integrity. A network-positioned attacker can observe the authentication token and application data or modify the bidirectional command stream. This behavior contradicts the TLS-only guarantees in `README.md` and `SKILL.md`. The implementati ...[truncated 1732 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Parse the configured endpoint with the standard `URL` API and require `wss:` after normalization. ```ts export function validateConfig( config: Partial<RemoteRelayConfig> ): RemoteRelayConfig { if (!config.relay_url) { throw new Error("remote-relay: relay_url is required"); } if (!config.node_id) { throw new Error("remote-relay: node_id is required"); } if (!config.auth_token) { throw new Error("remote-relay: auth_token is required"); } let parsed: URL; try { parsed = new URL(config.relay_url); } catch { throw new Error("remote-relay: relay_url must be a valid URL"); } if (parsed.protocol === "https:") { parsed.protocol = "wss:"; } if (parsed.protocol !== "wss:") { throw new Error("remote-relay: relay_url must use wss://"); } if (parsed.username || parsed.password) { throw new Error( "remote-relay: relay_url must not contain embedded credentials" ); } parsed.pathname = parsed.pathname.replace(/\/+$/, ""); return { relay_url: parsed.toString().replace(/\/$/, ""), node_id: config.node_id, auth_token: config.auth_token, }; } ``` Additional hardening should include: 1. Remove the test that treats conversion to `ws://` as valid and replace it with tests requiring rejection of `http://` and `ws://`. 2. Reject unsupported protocols and malformed URLs explicitly. 3. If plaintext localhost communication is essential for development, require a clearly named development-only opt-in that defaults to disabled and rejects non-loopback hosts. 4. Keep TLS certificate validation enabled and avoid options that accept self-signed or invalid certificates in production. 5. Rotate authentication tokens that may previously have been transmitted through plaintext WebSocket connections. 6. Update documentation so transport guarantees exactly match enforced runtime behavior. 7. Consider short-lived, node-bound tokens to limit the impact of credential in ...[truncated 17 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (4)

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The declared description frames the skill as a secure outbound-only relay and emphasizes what it does not expose (ports, SSH, Telegram), but the actual code chunk shows a remote control protocol and runtime contract with significant control capabilities. Incoming messages include prompt, workflow, status, and restart types, and the runtime must support executing prompts, executing workflows, returning runtime state, and restarting OpenClaw. These are substantive remote administration capabilities not clearly disclosed in the description. While the code does not contradict the 'no exposed ports, no SSH, no Telegram' claims, it materially expands the skill’s functional purpose beyond a generic relay into an active remote execution and restart mechanism.

Ae1

High
Category
analysis-evasion
Content
files: ["relayClient.ts", "config.ts", "capabilities.ts", "index.ts"]
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Missing User Warnings

Medium
Confidence
88% confidence
Finding
On connection, the client sends the configured auth token to the relay as part of the hello message. Although this is likely functionally necessary, the file lacks any user-facing disclosure or comment warning that credentials are transmitted to an external endpoint.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The code executes a remotely received prompt and streams generated token content back over the WebSocket relay. While the file logs connection and authentication events, it does not provide a user-facing warning or confirmation that prompt contents and model output will be transmitted to a remote service.