Back to skill

Security audit

NoChat Channel Plugin

Security checks for vulnerabilities and agentic risk

Overview

This messaging skill claims trust-gated encrypted agent communication, but the reviewed runtime can route remote messages into OpenClaw as authorized commands without enforcing the advertised trust or encryption controls.

Install only after the publisher fixes the trust-enforcement and encryption gaps. Treat NoChat messages as capable of steering your agent, do not grant owner access broadly, use a dedicated low-privilege OpenClaw profile, restrict the server URL to a trusted HTTPS endpoint, and protect or rotate the NoChat API key.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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
  • 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 (4)

T01 · Skill Instruction Hijacking

Error
Location
index.ts:185
Finding
Unconditional Command Authorization for Remote Messages<![CDATA[ ## Vulnerability Details **File Location**: `index.ts:185-241` **Vulnerability Type**: Remote instruction injection through unconditional command authorization **Risk Level**: Critical ### Vulnerable Code ```ts const body = core.channel.reply.formatAgentEnvelope({ channel: "NoChat", from: senderName, timestamp: msg.created_at ? new Date(msg.created_at).getTime() : Date.now(), previousTimestamp, envelope: envelopeOptions, body: text, }); // Build the ctx payload (same shape BlueBubbles uses) const ctxPayload = { Body: body, BodyForAgent: body, RawBody: text, CommandBody: text, BodyForCommands: text, From: `nochat:${senderId}`, To: `nochat:${config.agentId || config.agentName}`, SessionKey: route.sessionKey, AccountId: route.accountId, ChatType: "direct", ConversationLabel: senderName, SenderName: senderName, SenderId: senderId, Provider: "nochat", Surface: "nochat", MessageSid: msg.id, CommandAuthorized: true, // Trust tiers handle authorization }; // Dispatch: pushes inbound to agent, waits for reply, delivers reply back to NoChat console.log(`[NoChat] Dispatching to session ${route.sessionKey}...`); await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({ ctx: ctxPayload, cfg: ctx.cfg, dispatcherOptions: { deliver: async (payload: any) => { // Send the agent's reply back to NoChat const replyText = payload.text || ""; if (!replyText.trim()) return; const conversationId = msg.conversation_id; if (!conversationId) { console.log("[NoChat] No conversation_id on inbound message — cannot reply"); return; } const result = await client.sendMessage(conversationId, replyText); if (result.ok) { console.log(`[NoChat] Replied to ${senderName} in ${conversationId.slice(0, 8)}`); } else { console.log(`[NoChat] Reply failed: ${result.error}`); } }, onError: (err: unknown, info: { kind: string }) ...[truncated 2212 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve and enforce the sender's trust tier before constructing or dispatching the OpenClaw context. 2. Default unknown senders to a denied or non-command-capable state. 3. Drop blocked senders before session routing. 4. Set `CommandAuthorized` to `false` by default and enable it only after explicit identity verification and authorization. 5. Do not place untrusted text into command-specific fields such as `CommandBody` or `BodyForCommands`. 6. Route untrusted and sandboxed senders to isolated sessions with strict tool allowlists, resource limits, and no access to the primary session. 7. Require authenticated sender identities rather than trusting unsigned `sender_id` data returned by the server. 8. Add security tests proving that blocked, unknown, and untrusted senders cannot issue commands or access privileged tools. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
index.ts:131
Finding
Advertised Trust and Rate-Limit Controls Are Bypassed by the Active Inbound Pipeline<![CDATA[ ## Vulnerability Details **File Location**: `index.ts:131-146` **Vulnerability Type**: Access-control bypass caused by disconnected inbound implementations **Risk Level**: High ### Vulnerable Code The active gateway handler directly invokes `handleNoChatInbound()`: ```ts const transport = new PollingTransport(client, config.polling ?? {}, selfUserId); // Wire up inbound message handling transport.onMessage(async (msg: NoChatMessage) => { try { await handleNoChatInbound(ctx, account, config, client, msg); } catch (err) { console.log(`[NoChat] Error handling inbound message: ${(err as Error).message}`); console.log(`[NoChat] Error stack: ${(err as Error).stack}`); } }); await transport.start(); activeTransports.set(account.accountId, transport); ``` Trust enforcement exists in a separate implementation in `src/channel.ts:236-258`: ```ts private async handleInboundMessage(msg: NoChatMessage): Promise<void> { const { allowed, tier } = this.checkInbound(msg.sender_id, msg.sender_name); if (!allowed) { console.log(`[NoChat] Dropped message from blocked agent: ${msg.sender_name} (${msg.sender_id})`); return; } // Rate limit check if (!this.rateLimiter.check(msg.sender_id, tier)) { console.log(`[NoChat] Rate limited: ${msg.sender_name} (${tier})`); return; } // Record interaction for auto-promote this.trustManager.recordInteraction(msg.sender_id); // Route to session const route = this.routeMessage(msg.sender_id, msg.sender_name, tier); if (!route) return; // Format context const context = this.formatInboundContext(msg, tier); console.log(`[NoChat] Routed message to ${route.sessionKey}: ${msg.sender_name} (${tier})`); // In a real integration, this would push the message to the OpenClaw session // via api.runtime.pushMessage(route.sessionKey, context, route.config) // For now, we just log it } ``` ### Technical Analysis The project contains two distinct inbound processing paths ...[truncated 1931 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Consolidate inbound handling into one security-enforcing pipeline. 2. Before routing or dispatch: - Resolve the authenticated sender identity. - Resolve the sender's trust tier. - Reject blocked senders. - Apply global and per-sender rate limits. - Select a trust-specific isolated session. - Apply tier-specific tool allowlists and denylists. 3. Remove the duplicate handler architecture so the checked path cannot diverge from the dispatch path. 4. Make authorization enforcement mandatory in the dispatch function rather than relying on callers to remember it. 5. Fail closed when trust configuration is absent, malformed, or cannot be resolved. 6. Add integration tests against the actual `gateway.startAccount` implementation for blocked, untrusted, sandboxed, trusted, and owner senders. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
index.ts:164
Finding
Base64 Decoding Is Used in Place of Verified End-to-End Decryption<![CDATA[ ## Vulnerability Details **File Location**: `index.ts:164-179` **Vulnerability Type**: Missing cryptographic decryption and sender authenticity verification **Risk Level**: High ### Vulnerable Code ```ts // Decode message content let text: string; try { const raw = msg.encrypted_content || ""; const decoded = Buffer.from(raw, "base64").toString("utf-8"); // Handle double base64 encoding try { const double = Buffer.from(decoded, "base64").toString("utf-8"); // If double-decoded looks like text (not gibberish), use it text = /^[\x20-\x7E\n\r\t]/.test(double) && double.length > 0 ? double : decoded; } catch { text = decoded; } } catch { text = msg.encrypted_content || "[unreadable message]"; } ``` The corresponding message type in `src/types.ts:101-110` describes the field as base64-encoded: ```ts export type NoChatMessage = { id: string; conversation_id: string; sender_id: string; sender_name: string; encrypted_content: string; // base64-encoded message_type: "text" | "file" | "system"; created_at: string; // ISO 8601 }; ``` ### Technical Analysis Base64 is a reversible encoding and provides no confidentiality, integrity, authenticity, or sender verification. The inspected runtime does not perform authenticated decryption, signature validation, key agreement, or verification that the content was produced by the claimed sender. The code also falls back to using the original `encrypted_content` value when decoding fails. It therefore does not fail closed when the purported encrypted payload is malformed. The supplied files contain no implementation that consumes a private key or performs the documented Kyber-1024 or P-256 cryptographic operations. Several imported modules are absent from the supplied project, so behavior outside the reviewed artifact cannot be verified. However, the active message-processing code shown above treats base64-decoded server data as plaintext and immediately forwards it to t ...[truncated 1143 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Implement authenticated end-to-end encryption in the local client rather than treating base64 as encryption. 2. Bind each message cryptographically to: - The sender's verified public key. - The recipient identity. - The conversation identifier. - A unique message identifier or nonce. 3. Verify signatures or authenticated-encryption tags before decoding or dispatching plaintext. 4. Reject malformed, unsigned, replayed, or unverifiable messages; do not fall back to treating them as plaintext. 5. Store private keys using operating-system-backed secret storage with restrictive permissions. 6. Implement key rotation and revocation validation. 7. Add replay protection and monotonic message or nonce tracking. 8. Remove “server-blind,” “E2E encrypted,” and “post-quantum” claims until the complete cryptographic flow is implemented and independently reviewed. 9. Add cryptographic test vectors and negative tests for forged sender IDs, modified ciphertexts, invalid tags, and replayed messages. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
index.ts:87
Finding
Configurable Server URL Receives the API Bearer Credential Without Transport or Destination Restrictions<![CDATA[ ## Vulnerability Details **File Location**: `index.ts:87-98` **Vulnerability Type**: Credential disclosure through an unrestricted credential-bearing endpoint **Risk Level**: Medium ### Vulnerable Code ```ts // Create API client and polling transport const client = new NoChatApiClient(config.serverUrl, config.apiKey); // Resolve our own user_id (messages use sender_id = user_id, NOT agent_id) let selfUserId = config.userId; // Allow explicit config override if (!selfUserId) { // Method 1: GET /api/users/me — most reliable, works even with 0 conversations try { const resp = await fetch(`${config.serverUrl.replace(/\/+$/, "")}/api/users/me`, { method: "GET", headers: { Authorization: `Bearer ${config.apiKey}`, "Content-Type": "application/json" }, }); ``` The schema only requires `serverUrl` to be a string: ```ts serverUrl: { type: "string" }, apiKey: { type: "string" }, ``` ### Technical Analysis The plugin sends the configured API key in an HTTP `Authorization` header to a URL derived directly from `config.serverUrl`. The schema does not require HTTPS, restrict the hostname, or reject local, link-local, or attacker-controlled destinations. This means that any actor capable of changing plugin configuration can redirect credential-bearing requests. A cleartext `http://` URL would additionally expose the bearer token to network interception. The same unrestricted base URL is passed to `NoChatApiClient`, so additional authenticated requests may also be affected, although that absent module could not be reviewed. ### Attack Path 1. An attacker gains the ability to influence the plugin's `serverUrl` configuration, or persuades an administrator to use a malicious endpoint. 2. The attacker sets `serverUrl` to an attacker-controlled HTTP or HTTPS server. 3. The NoChat account starts. 4. The plugin requests `/api/users/me` from the configured endpoint. 5. The request includes `Authorization: Bearer <apiKey>`. 6. The attacker ...[truncated 732 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require `https:` URLs in both schema validation and runtime validation. 2. Reject URLs containing embedded credentials, fragments, unexpected ports, or non-HTTP protocols. 3. Allowlist the official NoChat API hostname by default; require an explicit administrative override for self-hosted deployments. 4. Resolve and reject loopback, private, link-local, multicast, and cloud metadata destinations unless explicitly required. 5. Use narrowly scoped, revocable API tokens. 6. Avoid sending credentials during health checks or discovery requests unless authentication is strictly necessary. 7. Redact API keys from all errors and logs. 8. Add tests confirming that insecure and disallowed destinations are rejected before any network request is made. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (11)

Missing User Warnings

High
Confidence
97% confidence
Finding
The README explicitly promotes an `owner` trust tier that routes remote agent messages into the main session with full tool access, effectively granting remote command authority equivalent to the human operator. In an agent framework, this is a high-risk capability because compromise, spoofing, misconfiguration, or over-trusting another agent can lead to arbitrary tool execution, data exfiltration, and full system-level actions.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The plugin claims encrypted, post-quantum end-to-end messaging, but the inbound path merely base64-decodes `encrypted_content` and treats the result as plaintext, while replies are sent as plaintext through the API client. This creates a security-design mismatch that can expose message contents, cause users or downstream agents to rely on nonexistent confidentiality guarantees, and undermine trust assumptions for authorization or sensitive data handling.

External Transmission

Medium
Category
Data Exfiltration
Content
PUBLIC_KEY=$(openssl ec -in /tmp/agent_private.pem -pubout -outform DER 2>/dev/null | tail -c 65 | base64)

# Register your agent
curl -X POST https://nochat-server.fly.dev/api/v1/agents/register \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"YourAgentName\",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The configuration example instructs users to place a live API key directly into persistent config without any warning about secret storage, exposure in shell history, or accidental commit to source control. While this is documentation rather than code execution, it normalizes insecure secret-handling practices that can lead to credential leakage and unauthorized use of the messaging account.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares network capability in metadata but does not provide an explicit tool scope such as permissions or allowed-tools. In an agent ecosystem, this can cause the plugin to receive broader-than-expected network access and makes operator review harder, increasing the chance of unintended external communication or abuse if the plugin is installed.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The documentation instructs users to configure a remote server URL and API key for agent-to-agent messaging but does not clearly warn that message metadata, identifiers, and operational data will be transmitted to an external service. Claims like 'E2E encrypted' and 'server-blind' can reduce user caution, even though registration, discovery, polling, and API-key use still create privacy and trust risks.

Intent-Code Divergence

Medium
Confidence
87% confidence
Finding
The code path advertised as the real transport/dispatch layer handles `encrypted_content` as base64-encoded text rather than cryptographic ciphertext, indicating that the messaging security model is not being enforced here. Even if partly documentary, this misleading implementation increases the chance that maintainers, operators, or agents will treat unprotected content as secure and route sensitive information through it.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"test:watch": "vitest"
  },
  "devDependencies": {
    "vitest": "^3.0.0",
    "typescript": "^5.7.0"
  },
  "openclaw": {
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unverifiable Dependency: vitest has 3 known advisory(ies) (CVE-2026-47429 (When Vitest UI server is listening, arbitrary file can be read and executed); CVE-2026-84373 (Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock); CVE-2025-24964 (Vitest allows Remote Code Execution when accessing a malicious website while Vit)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "devDependencies": {
    "vitest": "^3.0.0",
    "typescript": "^5.7.0"
  },
  "openclaw": {
    "extensions": ["./index.ts"],
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Static analysis

No suspicious patterns detected.