If you run OpenClaw on your own box or in ClawCloud, third-party skills are both the best feature and the biggest risk. Cisco Talos recently showed how a handful of Telegram bot skills quietly exfiltrated chat logs through side-channel HTTP calls. After reading their write-up I audited the 47 skills running on my production agent. Below is the playbook I wish I had last week — the practical steps to identify and avoid malicious skills before they siphon data or burn your credits.

Why malicious skills exist in the OpenClaw ecosystem

OpenClaw is basically an open plugin system. Anyone can publish a skill by dropping a folder with a skill.json, a SKILL.md, and some JavaScript in $HOME/.openclaw/skills or by pushing to @openclaw-skills on npm. That flexibility is a magnet for creativity and abuse. A rogue skill can:

  • Fire off HTTP requests to a C2 server every time the agent responds
  • Leak tokens via browser automation hooks
  • Escalate to shell access because OpenClaw provides exec() helpers
  • Abuse "memory" writes to smuggle PII into a persistence layer you never audit

The rest of this guide focuses on practical detection rather than theoretical hand-waving.

Reading SKILL.md like a threat analyst

Before touching code, start with the maintainer’s own words. The SKILL.md file is not just README fluff; it’s contract, changelog, and permission manifest rolled into one. Here are the red flags I look for:

  • Vague purpose – “enhances productivity” without a concrete use-case usually means the author doesn’t want scrutiny.
  • No maintainer contact – A throwaway GitHub alias or missing email suggests nobody will ship a security patch when things go south.
  • Opaque permissions – You should see an explicit list like:
    - requires: shell, browser, gmail.readonly
    Anything less specific (“needs full access”) is unacceptable.
  • Minified code reference – If the markdown links to a .min.js build instead of source, assume the worst.
  • Unpinned dependencies – A package.json with "^" or "*" majors effectively gives strangers write access to your runtime in the next npm install.

Habit: I run awk '/^## Changelog/{flag=1;next}/^##/{flag=0}flag' on SKILL.md to make sure the change log exists and is not empty. Missing changelog = auto reject.

Grepping for hidden HTTP calls and dynamic eval

Reading the markdown is cheap, but you still need to eyeball the code. Every malicious skill Cisco flagged used a tiny loader that fetched a second-stage script from Pastebin. You can catch 90% of these with two shell one-liners:

$ grep -R --line-number -E "(fetch|axios|http.request|https.get)" skills/<skill-name> $ grep -R --line-number "eval(.*fetch" skills/<skill-name>

Things I immediately distrust:

  • eval(Buffer.from(..., 'base64').toString()) – classic obfuscation
  • Any fetch() whose URL is not GitHub raw or a well-known CDN
  • Dynamic imports: import('http://' + domain + '/payload.js')

If you find something fishy but not obviously malicious, run it through node --trace-warnings --experimental-policy with a strict lockfile to see which modules get executed.

Static analysis shortcut with semgrep

For larger audits I drop semgrep rules into a .semgrep/ folder:

rules: - id: claw-suspicious-http patterns: - pattern: fetch($URL, ...) - pattern-not: fetch("https://api.openai.com/$REST", ...) message: "Suspicious remote fetch in skill" severity: ERROR

Run:

$ semgrep --config .semgrep skills/<skill-name>

Semgrep isn’t perfect but it automates the boring parts.

Auditing declared permissions versus actual code usage

OpenClaw 0.41.0 introduced a granular permission model in skill.json:

{ "name": "github-pr-reviewer", "permissions": ["github:write", "browser", "memory"] }

I feed that list into a tiny script that searches for APIs the skill shouldn’t touch:

// audit-perms.js import fs from 'node:fs/promises'; import path from 'node:path'; const allowed = new Set(process.argv.slice(3)); const dir = process.argv[2]; for await (const file of await fs.readdir(dir)) { if (!file.endsWith('.js')) continue; const text = await fs.readFile(path.join(dir, file), 'utf8'); if (text.includes('exec(') && !allowed.has('shell')) console.log('Shell call without shell perm in', file); if (text.match(/gmail\./) && !allowed.has('gmail:readonly')) console.log('Gmail access without perm in', file); }

Usage:

$ node audit-perms.js skills/github-pr-reviewer shell github:write browser memory

If the script yells, either patch the skill or delete it.

Leveraging the ClawCloud × VirusTotal scan pipeline

Since ClawCloud 2024-05-12 you get automatic VirusTotal coverage. Every time you upload or enable a skill through the web UI, the gateway zips the folder and posts it to VirusTotal’s private API. What you get back:

  • SHA-256
  • Malicious / Suspicious / Harmless verdict split across 72 engines
  • Any network IOCs (domains, IPs)
  • YARA matches

You can fetch the report from the CLI:

$ claw skill vt-report skills/github-pr-reviewer ┌─────────────────────┬────────┐ │ Engine │ Verdict│ ├─────────────────────┼────────┤ │ CrowdStrike │ clean │ │ DrWeb │ clean │ │ Kaspersky │ suspicious │ └─────────────────────┴────────┘

One false positive is fine. Three suspicious engines? I yank the skill and file an issue.

Local hashing for on-prem users

If you run bare-metal OpenClaw, the same helper exists but is opt-in:

$ claw skill hash skills/telegram-photo-sync SHA256: 4c2e...34a1 $ curl -s --request GET 'https://www.virustotal.com/api/v3/files/4c2e...34a1' \ --header 'x-apikey: $VT_TOKEN' | jq '.data.attributes.last_analysis_stats'

Yes, this leaks the hash to VirusTotal. If you’re dealing with TLP:RED data, skip and rely on local scanning instead.

Community reporting and quarantine workflow

The ecosystem only survives if we yell loudly when we find bad code. ClawCloud’s #security-alerts Discord, the GitHub SECURITY.md advisories, and the in-product “Report skill” button all end up in the same triage queue.

  1. Click “Report skill” in the UI or run claw skill report <skillName> --reason 'phoning home'.
  2. The gateway bundles: SHA-256, maintainer, last commit hash, your log snippet.
  3. Skill moves to quarantine state for 24 hours — new installs blocked, existing agents warned.
  4. Core team or a trusted responder (rotates weekly) reproduces.
  5. If confirmed, the skill is yanked from @openclaw-skills org and marks as deprecated on ClawCloud.

The process is imperfect but better than silence. Last month voice-note-transcriber was flagged within 3 hours, well before widespread installs.

Zero-day bounty

If your finding is a true zero-day (e.g., RCE in the gateway) email security@clawcloud.com. Payouts are public on the @clawcloud-security repo.

Hardening your deployment with allow-lists and namespaces

You can’t audit every line when the backlog piles up. Two defensive configs help:

Skill allow-list

Add this to ~/.claw/config.yaml:

security: allowedSkills: - github-pr-reviewer - notion-quick-note - weather-cli

Any other skill will refuse to load, even if a rogue maintainer adds it as a dependency.

Namespace mapping

From OpenClaw 0.42, skills can be forced into a sandboxed Node vm.Module with a restricted global:

skills: defaults: namespace: "vm:readonly" overrides: github-pr-reviewer: namespace: "vm:browser"

Inside vm:readonly the skill has no require('node:child_process') and no network. Fine for local math helpers, lethal for spyware.

Practical takeaway

Malicious OpenClaw skills are a supply-chain reality, not an edge case. Treat every new skill as untrusted code:

  • Scan SKILL.md for vague claims and missing changelogs
  • Grep the source for hidden HTTP calls and dynamic eval
  • Cross-check declared permissions with actual API usage
  • Pull the VirusTotal report before enabling in prod
  • Use allow-lists and VM namespaces to contain fallout

Do that and you’ll sleep pretty well — until tomorrow’s npm advisory at least.