Back to skill

Security audit

safeTok ↔ OpenClaw Bridge

Security checks for vulnerabilities and agentic risk

Overview

This bridge does what it says, but it exposes an OpenClaw agent to remotely supplied encrypted DMs with weak access controls and sensitive plaintext logging.

Install only if you are comfortable exposing a dedicated, minimally privileged OpenClaw session to messages arriving through public Nostr relays. Use a separate gateway token and bot key, restrict allowed senders outside or inside the bridge before use, remove plaintext logging, keep the gateway loopback-only unless you deliberately secure remote access, and pin dependencies with a lockfile before running it in production.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (5)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
bridge.mjs:288
Finding

Unauthenticated Nostr Senders Can Submit Prompts to the OpenClaw Agent

Content
View full analysis
console.error("[bridge] queue error:", e.message)); } ``` The queued message is subsequently submitted to the agent without an authorization check: ```js async function getReply(plaintext, senderPub) { const prompt = `[safeTok DM from ${senderPub.slice(0, 16)}…]\n${plaintext}`; // ... const ack = await gwRequest("chat.send", { sessionKey: SESSION, message: prompt, deliver: false, idempotencyKey: randomUUID(), }); ``` The relay subscription accepts events for the bridge from every sender: ```js ws.send(JSON.stringify(["REQ", sub, { kinds: [4], "#p": [MY_PUB], since: SINCE }])); ``` ### Technical Analysis The bridge does not maintain or enforce an authorized-sender allowlist. Any sender able to construct a decryptable Nostr DM addressed to the bridge can cause its plaintext to be inserted directly into the dedicated OpenClaw session. The README recommends setting an OpenClaw `dmPolicy`, but this implementation communicates directly with the Gateway WebSocket through `chat.send`. No corresponding policy enforcement is present in the bridge code. Consequently, documentation alone does not establish an access-control boundary. Because message plaintext becomes an agent prompt, an unauthorized sender may provide adversarial instructions or prompt-injection content. The eventual effect depends on the tools and permissions assigned to the OpenClaw agent. ### Attack Path 1. An attacker obtains ...[truncated 990 chars]
Remediation
View remediation
key.trim().toLowerCase()) .filter(Boolean), ); function handleDm(ev) { if (!ALLOWED_SENDERS.has(ev.pubkey?.toLowerCase())) { console.warn("[bridge] rejected DM from unauthorized sender"); return; } // Continue only after event validation and authorization. } ``` ]]>

T09 · Insecure Skill Coding Practices

Error
Location
bridge.mjs:323
Finding

Incoming Nostr Events Are Processed Without Cryptographic Validation

Content
View full analysis
{ console.log(`[bridge] subscribed to ${url}`); ws.send(JSON.stringify(["REQ", sub, { kinds: [4], "#p": [MY_PUB], since: SINCE }])); }); ws.addEventListener("message", ({ data }) => { let m; try { m = JSON.parse(data); } catch { return; } if (m[0] === "EVENT" && m[1] === sub) handleDm(m[2]); }); ``` The event is then trusted and persisted based on its supplied ID: ```js function handleDm(ev) { if (seen.has(ev.id)) return; if (ev.pubkey === MY_PUB) { seen.add(ev.id); saveSeen(seen); return; } const age = Math.floor(Date.now() / 1000) - ev.created_at; console.log(`\n[bridge] ← DM from ${ev.pubkey.slice(0, 16)}… (${age}s ago)`); seen.add(ev.id); saveSeen(seen); dmQueue.push(ev); drainQueue().catch((e) => console.error("[bridge] queue error:", e.message)); } ``` ### Technical Analysis The bridge trusts events received from a relay without: - Recomputing and comparing the Nostr event ID. - Verifying the BIP-340 Schnorr signature. - Validating the types and lengths of `id`, `pubkey`, `sig`, `created_at`, and `content`. - Independently requiring `kind === 4`. - Independently confirming that the event contains the expected recipient `p` tag. Relay subscription filters are not a security boundary because a malicious or compromised relay can return data that does not satisfy the requested filter. Deduplication also relies on the unverified, relay-supplied event ID. The authenticated encryption layer can prevent arbitrary ciphertext modification from producing accepted plaintext, but it does not replace validation of the enclosing Nostr event metadata. In particular, replayed valid ciphertext may be wrapped in altered ...[truncated 1123 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
bridge.mjs:96
Finding

Bridge Requests Broad Gateway Operator Read and Write Scopes

Content
View full analysis
{ gw.send( JSON.stringify({ type: "req", id: randomUUID(), method: "connect", params: { minProtocol: 1, maxProtocol: 5, client: { id: "gateway-client", displayName: "safeTok Bridge", version: "1.0.0", platform: "node", mode: "backend", }, auth: { token: GW_TOKEN }, scopes: ["operator.read", "operator.write"], }, }), ); }); ``` ### Technical Analysis The implementation requests both `operator.read` and `operator.write`. Its demonstrated gateway operations are limited to creating a dedicated session, submitting chat messages, and reading that session's history. Broad operator-level access increases the privilege of the bridge beyond a narrowly isolated DM transport. This is particularly concerning because remote Nostr input is submitted to the agent and because the gateway token is held in the same process. Whether narrower method- or session-specific scopes are available depends on the Gateway's authorization model. Where supported, the current scopes violate least-privilege design. ### Attack Path 1. The bridge authenticates to the Gateway using a token with operator read/write scopes. 2. An unauthorized sender reaches the agent through the exposed Nostr input path, or the bridge process/dependency is compromised. 3. The attacker attempts to use the compromised execution context or agent behavior to interact with gateway functionality. 4. Broad operator scopes increase the operations potentially available compared with a token restricted to one session and the required chat methods. ### Impact Assessment The excessive scopes enlarge the blast radius of prompt injection, process compromise, token disc ...[truncated 217 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
bridge.mjs:251
Finding

Decrypted End-to-End Encrypted Messages Are Exposed in Process Logs

Content
View full analysis
100 ? "…" : ""}"`); } catch (e) { console.warn(`[bridge] decrypt failed: ${e.message}`); return; } ``` ### Technical Analysis After decrypting an end-to-end encrypted DM, the bridge writes up to the first 100 characters of plaintext to standard output. The README recommends deployment under process managers such as `pm2` or `systemd`, both of which commonly persist or forward standard output. Encryption protects the message while it is transported through Nostr relays, but logging introduces a separate plaintext copy outside the encrypted channel. Message prefixes can contain passwords, tokens, personal information, private questions, or other sensitive content. ### Attack Path 1. A user sends a confidential encrypted DM through safeTok. 2. The bridge decrypts the message locally. 3. The first 100 characters are written to stdout. 4. A process manager, logging agent, terminal recorder, monitoring service, or backup persists the output. 5. A user or service with access to those logs obtains plaintext message content without requiring the Nostr private key. ### Impact Assessment The issue compromises the confidentiality expected from end-to-end encrypted messaging. Exposure is limited to the logged prefix of each message, but that prefix may contain complete short messages or sensitive values. The affected scope includes local log readers and any external logging, monitoring, or backup systems that receive process output. ]]>
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
README.md:61
Finding

Third-Party Cryptographic Dependency Is Installed Without Version Pinning or a Lockfile

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

The setup instructions tell users to export OPENCLAW_TOKEN and SAFETOK_PRIVATE_KEY, which are highly sensitive credentials, but the README does not provide an immediate warning in that section about shell history, process inspection, or accidental disclosure. Although later production tips say not to commit the private key, the primary credential-handling instructions still lack a clear warning at the point of use.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill instructs users to provide sensitive environment variables, including OPENCLAW_TOKEN and SAFETOK_PRIVATE_KEY, but does not declare any explicit tool scope or permissions boundary in the skill manifest. In a bridge skill that handles authentication and encrypted messaging, undeclared environment access increases the risk that secrets are overexposed to code paths or future modifications without clear least-privilege constraints.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This bridge decrypts incoming safeTok/Nostr DMs and forwards the plaintext to the OpenClaw gateway as a chat message, which exposes private message content to another service boundary. Even if the gateway is local by default, the code provides no consent flow, warning, minimization, or policy guard, so users may reasonably believe their encrypted DMs remain only between Nostr endpoints when they are actually being relayed into an agent session.

Content

No source excerpt is available for this finding.