- Security
- A
Prompt Worms Part 2: I Practiced and Found 31 Vulnerabilities in the AI Agent Ecosystem
In the first part, we discussed the theory of Prompt Worms—self-replicating attacks via AI agents. OpenClaw was named the "perfect carrier." In this part, I put it into practice: downloaded the repository, delved into the code, probed the infrastructure, and found 31 vulnerabilities across 4 layers of the ecosystem. Zero sanitization on 867 lines of code, timeout instead of approval, backdoor "Easter egg" in the code, leaked password hashes in marketplace SaaS, and 14 blind spots in their own threat model. Three days, ~4,500 lines of tracing, 14 kill chains with PoC
Prompt Worms Part 2: I Tested in Practice — 31 Vulnerabilities in the AI-Agent Ecosystem
This is a continuation of the article “Prompt Worms: How Agents Became the New Virus Carriers”. In the first part, we discussed the theory: Lethal Trifecta, Persistent Memory, infection chains through Moltbook. OpenClaw was labeled the “perfect carrier.” In this part, I checked how “perfect” it truly is — delved into the source code, probed the infrastructure, found ecosystem SaaS in their marketplace, and discovered that their own threat model covers only 70% of the real attack surface.
Background: From Theory to Practice
In the first article, we presented a table — why OpenClaw is the perfect carrier for Prompt Worms:
Condition | Implementation in OpenClaw |
|---|---|
Data Access | Full access to the file system, .env, SSH keys |
Untrusted Content | Moltbook, email, Slack, Discord, web pages |
External Comms | Email, API, shell commands, any tools |
Persistent Memory | Built-in long-term context storage |
Beautiful theory. But the question remains: how easy is it to exploit?
I downloaded the repository (~50 thousand lines of TypeScript), opened the code, and over three days followed a path I hadn’t planned:
Code → Website → Marketplace → Ecosystem SaaS → Their threat model
Each layer revealed the next. And each subsequent layer was worse than the previous one.
Layer 1: Source Code. Zero Sanitization on 867 Lines of Brain
The Only Filter
In the first article, I wrote: “extensions without moderation, malicious code executes.” Theory. Now — proof.
I traced the message path from the Telegram chat to the LLM. Four files:
dispatch.ts → envelope.ts → agent-runner-execution.ts → run.ts
At no stage — not one content check. And here is the only “sanitization” in 867 lines of brain core:
// run.ts:67-75 — EVERYTHING.
const ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL = "...";
function scrubAnthropicRefusalMagic(prompt: string): string {
return prompt.replaceAll(
ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL,
ANTHROPIC_MAGIC_STRING_REPLACEMENT,
);
}
One replacement of one string. For one provider. All other text goes to the LLM as is.
In the first article, we described Content Boundary Enforcement — separation of data and instructions through XML tags. OpenClaw does not do this. Data and instructions are mixed in one prompt. Prompt injection becomes not an attack, but a regular mode of operation.
SOUL_EVIL — a backdoor that doesn’t need to be injected
In the code, there is a file soul-evil.ts (line 281). It replaces the agent's "identity" based on a configurable trigger — replacing the system prompt with an alternative from the file SOUL_EVIL.md. It is designed as an "Easter egg." Implemented as a backdoor:
// soul-evil.ts — the replacement decision is logged on debug
// (disabled in production)
log.debug(`[soul-evil] decision: ${reason}`);
Activation through configuration: soulEvil.chance: 1 — the agent is always in "evil" mode. The user does not see any difference — the same interface, the same chat.
Timeout = Approval
Remember from the first article: the agent can execute shell commands? Before that, it "asks for permission." But:
// exec-approval-manager.ts:51-61
const timer = setTimeout(() => {
this.pending.delete(record.id);
resolve(null); // ⚠️ timeout = null = approval
}, timeoutMs);
If the user does not respond — the command is executed. Also: any WebSocket client can approve a request from any other client. There is no identity verification. We wrote a PoC script in 50 lines:
// Listening to exec.approval.requested → response: allow-always
// Result: One-Click RCE — all subsequent commands are auto-approved
config.patch — an omnipotent API
One endpoint allows changing any configuration field via JSON merge:
What we are patching | Effect |
|---|---|
| Activating SOUL_EVIL |
| Replacing authentication |
| Opening all channels |
| Loading malicious plugin |
| Indexing arbitrary paths |
No field-level ACL. No audit log.
Total vulnerabilities in code: 12
# | Vulnerability | Severity |
|---|---|---|
1 | Zero-sanitization pipeline (DM→LLM) | 🔴 Critical |
2 | SOUL_EVIL backdoor engine | 🔴 Critical |
3 | Timeout-as-approval (RCE) | 🔴 Critical |
4 | Config injection → system takeover | 🔴 Critical |
5 | Plugin system: jiti JIT arbitrary exec | 🔴 Critical |
6 | Default-open command gating | 🟠 High |
7 | Plaintext credential storage | 🟠 High |
8 | Memory exfiltration + injection | 🟠 High |
9 | System prompt injection surfaces | 🟠 High |
10 | Hook system: silent agent mutation | 🟠 High |
11 | Fast-lane privilege escalation | 🟠 High |
12 | BOOT.md persistent backdoor | 🟠 High |
5 Critical, 7 High. All with PoC. Chains from DM to Full Compromise — from 3 to 5 hops.
Layer 2: Infrastructure. CORS * and open API marketplace
After the code — it makes sense to look at what lives in production. Three domains:
openclaw.ai — CORS wildcard
Access-Control-Allow-Origin: * ← Any domain can make requests
Content-Security-Policy: ← ABSENT
X-Frame-Options: ← ABSENT
This is static on Vercel. But CORS * + absence of X-Frame-Options = clickjacking.
docs.openclaw.ai — CSP with unsafe-eval
The documentation on Mintlify returns:
Content-Security-Policy: worker-src * blob: data: 'unsafe-eval' 'unsafe-inline'
unsafe-eval means: if an XSS point is found — eval(), Function(), setTimeout(string) work. CSP does not protect.
Also — files /llms.txt and /llms-full.txt are advertised: full dump of documentation (~1.3 MB), including credential storage paths, config examples, Gateway architecture. One GET request — and the attacker has a complete map of the system.
clawhub.ai — the find that changed everything
ClawHub — a skills marketplace for OpenClaw. The API works without authentication:
GET https://clawhub.ai/api/search?q=email
Returns a complete catalog of community skills with descriptions, ratings, versions. No auth, no rate limiting.
And here is what was returned in the output:
{
"slug": "sendclaw",
"displayName": "SendClaw - Bot creates own email address & sends without human permission"
}
“Bot creates email address and sends without human permission?”
Remember the Lethal Trifecta from the first article? External Comms — the third condition. If this skill actually works, we just found an autonomous email-sending mechanism within the OpenClaw ecosystem. This turns the theoretical Prompt Worm into a practical threat: an infected agent can not only poison the memory of neighboring agents but also send malicious emails to real people.
I went to check.
Layer 3: SendClaw. Mass mailing that doesn’t work
5 minutes — 3 accounts
SendClaw (5Ducks) — a live SaaS at sendclaw.com. Express.js backend on Replit. I registered three accounts in 5 minutes:
No email verification
No CAPTCHA
No rate limiting
Sequential User ID: 99 → 100 → 101 (≈100 users on the entire platform)
Password hash leak
The very first request GET /api/user returned:
{
"id": 99,
"username": "sentinel-test",
"password": "533652d523a3c4daf...b30e80",
"email": "[email protected]",
"isAdmin": false
}
Password hash leaks during registration, login, and profile requests. All three accounts — confirmed.
The promised mailing
I tried to log in via Gmail OAuth:
GET /api/gmail/auth?userId=99
→ Error 400: redirect_uri_mismatch
Gmail OAuth is completely broken. Google rejects the Client ID due to an incorrect redirect URI.
The endpoint POST /api/send-gmail exists but is not working. The endpoints for daily outreach batch return HTTP 500 on any request.
Discrepancy: what is advertised vs what works
Promise (on ClawHub) | Reality |
|---|---|
“Bot creates own email address” | Does not create — requires OAuth authorization of your Gmail |
“Sends without human permission” | Does not send — OAuth is broken |
“Email outreach automation” | The only working endpoint is AI chat for strategy |
“Daily outreach batches” |
|
This is the key point of the entire story.
In the first article, I described a scary scenario: an infected agent sends a prompt worm via email. SendClaw is advertised on ClawHub precisely as a tool for this — “sends without human permission.” If this skill worked, it would be a ready delivery mechanism for Prompt Worms.
But it does not work. A bug in the OAuth configuration has saved users from themselves.
A bug that saved from vulnerability
The OAuth state parameter contains raw userId without a signature:
/api/gmail/auth?userId=99 → state=99
/api/gmail/auth?userId=1 → state=1
If OAuth worked, this would be a direct OAuth CSRF: replace the state in the callback → link your Gmail to someone else's account → send emails in someone else's name. The chain:
Attacker → /api/gmail/auth?userId=VICTIM
→ Google OAuth (state=VICTIM)
→ Callback with authorization code
→ Gmail token linked to VICTIM
→ POST /api/send-gmail (on behalf of VICTIM)
Broken OAuth → the chain does not close. Bug ≠ protection. If someone fixes the redirect URI tomorrow — the CSRF chain will work immediately.
12 vulnerabilities in SendClaw
# | Findings | Severity |
|---|---|---|
1 | Password hash leakage | 🔴 Critical |
2 | Unrestricted registration | 🔴 Critical |
3 | Sequential predictable IDs | 🔴 Critical |
4 | OAuth CSRF via raw userId | 🔴 Critical |
5 | Broken Gmail OAuth (core non-functional) | 🔴 Critical |
6 | Admin panel exposure | 🟠 High |
7 | Batch endpoint DoS (500 crash) | 🟠 High |
8 | Account lockout anomaly | 🟠 High |
9 | Dual auth inconsistency | 🟠 High |
10 | Multi-tenant exposure | 🟡 Medium |
11 | Infrastructure fingerprinting | 🟡 Medium |
12 | Partial mass assignment block | 🟡 Medium |
Layer 4: Threat Model. 37 threats and 14 blind spots
Already after all findings, I discovered that OpenClaw published an official threat model. 37 threats × 8 MITRE ATLAS tactics × 6 attack chains × 5 trust boundaries.
This is the first public threat model for an AI-agent platform. And it is honest — they acknowledge:
— “Pattern detection only, no blocking”
— “Skills execute with full agent privileges”
— “Tokens stored in plaintext, don't expire”
— “No rate limiting”
What is covered in their model
Their focus is on Skill Supply Chain (ClawHub) and Prompt Injection. These are the right priorities. They document 6 attack chains, including “Malicious Skill Full Kill Chain” and “Prompt Injection to RCE”.
What is NOT covered
Everything we found at the protocol level:
Our Findings | In their Threat Model? |
|---|---|
| ❌ |
| ❌ |
Credentials in cleartext WebSocket frame | ❌ |
Nonce without HMAC — replay attack | ❌ |
| ❌ |
| ❌ |
Timeout-as-approval | ❌ |
Loopback auth bypass | ❌ |
Memory flush autonomous writes | ❌ |
Plugin loader (jiti) arbitrary exec | ❌ |
SendClaw: broken OAuth + hash leak | ❌ |
ClawHub: unauthenticated API | ❌ |
CORS | ❌ |
CSP | ❌ |
14 out of 14 of our critical findings are not reflected in the official model.
The model covers ~70% of the actual attack surface. The remaining 30% consists of protocol-level and infrastructure-level vulnerabilities that make the attacks described in the model trivially exploitable.
How This Relates to Prompt Worms
Let's return to the first article. We outlined 4 conditions for Prompt Worm:
Condition | Status after audit |
|---|---|
✅ Data Access | Confirmed: |
✅ Untrusted Content | Confirmed: zero-sanitization pipeline, content from any channels |
✅ External Comms | Confirmed: shell commands via timeout-approval, email via skills |
✅ Persistent Memory | Confirmed: |
All 4 conditions are not just "met" — they have no protections whatsoever. In the first article, we suggested protection through Content Boundary Enforcement and Memory Sanitization. OpenClaw has implemented neither of these.
Prompt Worm Kill Chain — complete chain
In the first article, we described a theoretical chain through Moltbook. Here's how it works with real code:
function scrubAnthropicRefusalMagic(prompt) { ... }
This is the only line of defense between the DM from Telegram and the shell command on your server. In SENTINEL we have 187 detection engines. Because a single regex is not a security layer. It’s a comment in the code.
Responsible Disclosure
OpenClaw: Contact
[email protected]. Findings documented.SendClaw (5Ducks): Public security contact absent (which in itself is a red flag for a SaaS handling email credentials).
Write comment