Back to skill

Security audit

PrivaClaw

Security checks for vulnerabilities and agentic risk

Overview

This remote-control skill is mostly coherent, but its code contradicts its security claims in ways that could expose node control and tokens.

Review this carefully before installing. Use only a relay operator you trust, require wss://, keep the auth token secret and rotated, and avoid production use until the maintainer adds an authenticated-state gate before command dispatch and clearer command-level authorization controls.

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:91
Finding

Privileged Relay Commands Are Accepted Before Authentication

Content
View full analysis

Vulnerability Details

File Location: relayClient.ts:58-62, 91-101
Vulnerability Type: Missing authentication-state authorization check
Risk Level: High

Vulnerable Code

ts
this.ws.onmessage = (event) => {
  try {
    const msg = JSON.parse(String(event.data));
    this.handleMessage(msg);
  } catch {
    console.error("[privaclaw] Invalid message received");
  }
};
ts
private handleMessage(msg: { type: string; data?: unknown }): void {
  switch (msg.type) {
    case "hello_ok":
      console.log("[privaclaw] 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 marks the connection as authenticated only after receiving hello_ok, but privileged messages are dispatched without checking connectionState === "online" or a separate authenticated-session flag.

Consequently, the remote WebSocket peer can send a prompt, status, restart, or workflow message immediately after the socket opens and before authentication succeeds. The command is passed directly to the host runtime through executePrompt, executeWorkflow, or restart.

This violates the documented guarantee that relay commands are accepted only while the node is authenticated and online. TypeScript type assertions do not provide runtime authorization or message validation.

Attack Path

  1. The node establishes a WebSocket connection to a relay peer.
  2. The client sends its authentication hello message.
  3. Before returning hello_ok, the peer sends a validly formatted prompt, workflow, or restart envelope.
  4. onmessage parses the envelope and calls handleMessage.
  5. handleMessage dispatches the command without checking authenticated state.
  6. The corresponding pri ...[truncated 703 chars]
Remediation
View remediation

Remediation Suggestions

  • Maintain an explicit authentication flag that starts as false, becomes true only after a valid hello_ok, and is reset on connection close, disconnect, and reconnection.
  • Before dispatching any privileged message, require both an authenticated flag and the expected online connection state.
  • Reject or close the connection when privileged commands arrive before authentication.
  • Treat repeated or out-of-order authentication messages as protocol violations.
  • Validate incoming envelopes at runtime rather than relying on TypeScript casts.
  • Add regression tests proving that prompt, status, restart, and workflow messages received before hello_ok do not invoke runtime methods.

Example hardening pattern:

ts
private authenticated = false;

private handleMessage(msg: { type: string; data?: unknown }): void {
  if (msg.type === "hello_ok") {
    this.authenticated = true;
    this.connectionState = "online";
    this.startHeartbeat();
    return;
  }

  if (!this.authenticated || this.connectionState !== "online") {
    console.warn("[privaclaw] Rejecting command before authentication");
    return;
  }

  switch (msg.type) {
    case "prompt":
    case "status":
    case "restart":
    case "workflow":
      void this.dispatch(msg as { type: string; data: IncomingMessage });
      break;
  }
}

T09 · Insecure Skill Coding Practices

Error
Location
config.ts:7
Finding

Plaintext WebSocket Configuration Exposes Authentication and Control Traffic

Content
View full analysis

Vulnerability Details

File Location: config.ts:7-21
Vulnerability Type: Plaintext transmission of sensitive authentication and remote-control data
Risk Level: High

Vulnerable Code

ts
export function validateConfig(config: Partial<RemoteRelayConfig>): RemoteRelayConfig {
  if (!config.relay_url) throw new Error("privaclaw: relay_url is required");
  if (!config.node_id) throw new Error("privaclaw: node_id is required");
  if (!config.auth_token) throw new Error("privaclaw: 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 plaintext behavior is explicitly confirmed by config.test.ts:17-20:

ts
it("converts http to ws", () => {
  const result = validateConfig({ ...valid, relay_url: "http://localhost:8080" });
  expect(result.relay_url).toBe("ws://localhost:8080");
});

The credential is subsequently sent over the selected connection in relayClient.ts:40-51:

ts
this.ws.onopen = () => {
  console.log("[privaclaw] Connected, sending auth");
  this.send({
    type: "hello",
    data: {
      node_id: this.config.node_id,
      token: this.config.auth_token,
      meta: { kind: "openclaw" },
    },
  });
};

Technical Analysis

Configuration validation accepts http:// and deliberately converts it to ws://. WebSocket connections using ws:// do not provide TLS confidentiality, server authentication, or transport integrity.

The client then sends the authentication token and node ID in the initial application message. The same connection carries remote commands, heartbeat information, prompt responses, workflow status, and error data.

A network-positioned attacker can therefore observe or modify traffic, steal the reusable authenticatio ...[truncated 1392 chars]

Remediation
View remediation

Remediation Suggestions

  • Parse the relay address with the standard URL API.
  • Permit only the wss: scheme in production.
  • Reject http:, ws:, malformed URLs, URL-embedded credentials, fragments, and all unsupported schemes.
  • Do not silently downgrade http:// to ws://.
  • If plaintext transport is required for development, place it behind an explicit development-only option and restrict it to verified loopback hosts such as 127.0.0.1, [::1], or localhost.
  • Clearly warn when any development exception is enabled.
  • Consider certificate pinning or mutually authenticated TLS for deployments with stronger relay-authentication requirements.
  • Replace the existing plaintext-acceptance test with tests asserting that insecure schemes are rejected.

Example validation pattern:

ts
const parsed = new URL(config.relay_url);

if (parsed.protocol !== "wss:") {
  throw new Error("privaclaw: relay_url must use wss://");
}

if (parsed.username || parsed.password) {
  throw new Error("privaclaw: relay_url must not contain credentials");
}

parsed.pathname = parsed.pathname.replace(/\/+$/, "");

return {
  relay_url: parsed.toString().replace(/\/$/, ""),
  node_id: config.node_id,
  auth_token: config.auth_token,
};
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The code chunk does support a relay/control scenario, but the declared description is materially incomplete about what the relay actually enables. The interfaces explicitly provide remote command-and-control capabilities: executing prompts, executing workflows/tasks, streaming prompt tokens back, reporting status/errors, and restarting the OpenClaw process. The description frames this mainly as a 'secure outbound-only relay' and mentions networking constraints ('no exposed ports, no SSH, no Telegram'), but it does not clearly disclose these powerful remote execution and restart capabilities. There is no evidence here about exposed ports, SSH, or Telegram one way or the other, since this chunk only defines types/interfaces. The main mismatch is omission of the concrete remote execution/control behaviors present in the code.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 11)May include surrounding context.

md
files: ["relayClient.ts", "config.ts", "capabilities.ts", "index.ts"]

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest advertises broad remote-control capabilities such as sending prompts, triggering workflows, checking health, and restarting the node, but it does not describe any authorization boundaries, allowed callers, or operational constraints. For a remote administration skill, this omission is security-relevant because it normalizes powerful control actions without clarifying safeguards, increasing the risk of unsafe deployment or accidental overexposure of node control.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The client accepts privileged remote actions from any party that can send valid WebSocket messages after connection, including prompt execution, workflow execution, and restart, with no local authorization gate, operator confirmation, or command-level access control. In this skill’s context, the feature is intentionally designed for remote OpenClaw control, which makes the behavior expected, but it also makes compromise of the relay, token theft, or misconfiguration immediately translate into remote code/task execution capability on the node.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.