openclaw-shield
Security audit engine for OpenClaw configurations. Detects vulnerabilities, misconfigurations, secret leaks, and over-privileged agents. Use when the user asks about security, hardening, config review, or audit of their OpenClaw setup.
Install
npx skills add https://github.com/aAAaqwq/AGI-Super-Team/tree/main/skills/ai-shield-audit
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install aaaaqwq-agi-super-team@llmmart
git clone https://github.com/aAAaqwq/AGI-Super-Team.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole aaaaqwq/agi-super-team collection as a plugin from our marketplace. Git is the plain clone.
README
🛡️ OpenClaw Shield
Security audit engine for OpenClaw configurations. Find vulnerabilities, misconfigurations, and secret leaks before they become breaches.
Built by Autonomous Intelligence — we run 9 agents in production. We know what breaks.
Features
- 11 security check categories covering auth, networking, channels, agents, secrets, and more
- Structured JSON reports with risk scores, severity levels, and remediation steps
- Config sanitizer to strip secrets before sharing
- CLI tool for quick audits
- Zero dependencies — pure Node.js, no npm install needed
Quick Start
# Audit your live config
node bin/shield.js audit ~/.openclaw/openclaw.json --summary
# Get JSON report
node bin/shield.js audit ~/.openclaw/openclaw.json
# Strip secrets before sharing
node bin/shield.js sanitize ~/.openclaw/openclaw.json > safe-config.json
Sample Output
╔══════════════════════════════════════════════════╗
║ 🛡️ OpenClaw Shield Audit Report ║
╚══════════════════════════════════════════════════╝
Risk Level: HIGH
Score: 42/100
Safe to Deploy: ❌ No
Action: REVIEW_AND_REMEDIATE
Vulnerabilities: 12
🔴 Critical: 2
🟠 High: 3
🟡 Medium: 4
🔵 Low: 3
Security Checks
| # | Category | What It Catches |
|---|---|---|
| 1 | Gateway Auth | Missing auth token, insecure UI, weak tokens |
| 2 | Network Exposure | Non-loopback bind, Tailscale funnel, wildcard proxies |
| 3 | Channel Security | Wildcard allowFrom, missing allowlists |
| 4 | DM Policy | Open DM policy without pairing |
| 5 | Subagent Permissions | Wildcard allowAgents, circular chains, self-spawn |
| 6 | Tool Permissions | Over-privileged agents (full tool access) |
| 7 | Secret Leakage | API keys, tokens, private keys in plaintext |
| 8 | Sandbox/Execution | Missing workspace isolation, no exec policies |
| 9 | Plugin Config | Orphaned plugins, missing channel configs |
| 10 | Heartbeat Exposure | Sensitive data in heartbeat prompts |
| 11 | Remote Config | Unencrypted WebSocket, exposed remote tokens |
JSON Report Schema
{
"risk_level": "CRITICAL|HIGH|MEDIUM|LOW",
"overall_score": 0-100,
"vulnerabilities": [{
"category": "string",
"severity": "critical|high|medium|low",
"issue": "Description of the problem",
"recommendation": "How to fix it",
"auto_fix": { "path": "config.path", "value": "suggested_value" }
}],
"vulnerability_count": { "critical": 0, "high": 0, "medium": 0, "low": 0, "total": 0 },
"best_practices_compliance": 0.0-1.0,
"action_recommended": "BLOCK|REVIEW_AND_REMEDIATE|APPROVE",
"safe_to_deploy": true|false,
"audit_timestamp": "ISO8601",
"engine_version": "1.0.0"
}
Programmatic Usage
const { auditConfig } = require('./src/audit');
const { sanitizeConfig } = require('./src/sanitize');
// Audit
const config = require('./my-openclaw.json');
const report = auditConfig(config);
if (!report.safe_to_deploy) {
console.log(`${report.vulnerability_count.critical} critical issues found!`);
}
// Sanitize before sharing
const clean = sanitizeConfig(config);
Pricing
- Local audit: Free (install the skill, run locally)
- Premium remote audit: $0.10/audit via ACP (deeper analysis + auto-fix patches)
License
MIT — Autonomous Intelligence 2026
Skill manifest
OpenClaw Shield — Security Audit
Audit any OpenClaw config for security vulnerabilities, misconfigurations, and best-practice violations. Produces a structured JSON report with risk scores, findings, and remediation steps.
When to Use
- User asks to check/audit/review their OpenClaw security
- User wants to harden their config before deploying
- User is setting up a new OpenClaw instance
- User asks about secret leakage or API key exposure in their config
- Before publishing or sharing any config file
Quick Audit (live config)
node scripts/shield-audit.sh
Or directly:
node SKILL_DIR/bin/shield.js audit ~/.openclaw/openclaw.json --summary
What It Checks (11 categories)
- Gateway Auth — missing/weak auth, insecure UI settings
- Network Exposure — bind address, Tailscale funnel, wildcard proxies
- Channel Security — wildcard allowFrom, missing allowlists
- DM Policy — open DM policy without pairing
- Subagent Permissions — wildcard allowAgents, circular delegation chains, self-delegation
- Tool Permissions — over-privileged agents with tools.profile: "full"
- Secret Leakage — API keys, tokens, private keys in plaintext config
- Sandbox/Execution — missing workspace isolation, no execution policies
- Plugin Config — enabled plugins without channel config
- Heartbeat Exposure — sensitive data in heartbeat prompts
- Remote Config — unencrypted WebSocket, exposed remote URLs/tokens
Usage
Audit a config file
node SKILL_DIR/bin/shield.js audit <config.json>
node SKILL_DIR/bin/shield.js audit <config.json> --summary # human-readable
Audit from stdin
cat config.json | node SKILL_DIR/bin/shield.js audit --stdin
Sanitize a config (strip secrets)
node SKILL_DIR/bin/shield.js sanitize <config.json>
Programmatic use
const { auditConfig } = require('SKILL_DIR/src/audit');
const config = require('./openclaw.json');
const report = auditConfig(config);
console.log(report.risk_level); // "CRITICAL" | "HIGH" | "MEDIUM" | "LOW"
console.log(report.overall_score); // 0-100
console.log(report.vulnerabilities); // detailed findings
Output
Returns JSON with: risk_level, overall_score (0-100), vulnerabilities[], vulnerability_count, best_practices_compliance, action_recommended, safe_to_deploy, audit_timestamp.
Workflow for Agent
- Load the user's config:
cat ~/.openclaw/openclaw.json - Run:
node SKILL_DIR/bin/shield.js audit ~/.openclaw/openclaw.json --summary - Present findings to user with prioritized recommendations
- Offer to sanitize before sharing:
node SKILL_DIR/bin/shield.js sanitize <file>
Files (agi-super-team)
-
.clawhub
-
origin.json 147 B
{ "version": 1, "registry": "https://clawhub.ai", "slug": "ai-shield-audit", "installedVersion": "1.0.0", "installedAt": 1777570231123 }
-
-
bin
-
shield.js 5.2 KB
#!/usr/bin/env node /** * OpenClaw Shield CLI * Usage: * shield audit <config.json> — run security audit * shield audit --stdin — read config from stdin * shield sanitize <config.json> — strip secrets from config * shield sanitize --stdin — read from stdin * shield audit --live — audit the running OpenClaw instance */ 'use strict'; const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const { auditConfig } = require('../src/audit'); const { sanitizeAndStringify } = require('../src/sanitize'); const args = process.argv.slice(2); const command = args[0]; function usage() { console.log(`OpenClaw Shield v1.0.0 — Security Audit for OpenClaw Configs Usage: shield audit <config.json> Audit a config file shield audit --stdin Audit config from stdin shield audit --live Audit the running OpenClaw config shield sanitize <config.json> Strip secrets from config shield sanitize --stdin Strip secrets from stdin shield --help Show this help Options: --json Output raw JSON (default for audit) --pretty Pretty-print JSON output --summary Human-readable summary instead of JSON Examples: node bin/shield.js audit ~/.openclaw/openclaw.json openclaw config.get | node bin/shield.js audit --stdin node bin/shield.js audit --live --summary `); } function loadConfig() { if (args.includes('--live')) { try { const raw = execSync('openclaw config.get', { encoding: 'utf-8', timeout: 10000 }); return JSON.parse(raw); } catch (e) { // config.get may not be available, try reading the file directly const configPath = path.join(process.env.HOME || '/root', '.openclaw', 'openclaw.json'); if (fs.existsSync(configPath)) { return JSON.parse(fs.readFileSync(configPath, 'utf-8')); } console.error('Error: Could not load live config. Is OpenClaw running?'); process.exit(1); } } if (args.includes('--stdin')) { const input = fs.readFileSync('/dev/stdin', 'utf-8'); return JSON.parse(input); } const filePath = args.find(a => !a.startsWith('-')); if (filePath && filePath !== command) { return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } // Try second positional arg const fileArg = args[1]; if (fileArg && !fileArg.startsWith('-')) { return JSON.parse(fs.readFileSync(fileArg, 'utf-8')); } console.error('Error: No config source specified. Use a file path, --stdin, or --live.'); process.exit(1); } function formatSummary(report) { const lines = []; const icons = { critical: '🔴', high: '🟠', medium: '🟡', low: '🔵' }; lines.push(''); lines.push('╔══════════════════════════════════════════════════╗'); lines.push('║ 🛡️ OpenClaw Shield Audit Report ║'); lines.push('╚══════════════════════════════════════════════════╝'); lines.push(''); lines.push(` Risk Level: ${report.risk_level}`); lines.push(` Score: ${report.overall_score}/100`); lines.push(` Safe to Deploy: ${report.safe_to_deploy ? '✅ Yes' : '❌ No'}`); lines.push(` Action: ${report.action_recommended}`); lines.push(''); lines.push(` Vulnerabilities: ${report.vulnerability_count.total}`); lines.push(` 🔴 Critical: ${report.vulnerability_count.critical}`); lines.push(` 🟠 High: ${report.vulnerability_count.high}`); lines.push(` 🟡 Medium: ${report.vulnerability_count.medium}`); lines.push(` 🔵 Low: ${report.vulnerability_count.low}`); lines.push(''); lines.push('─'.repeat(52)); for (const v of report.vulnerabilities) { lines.push(''); lines.push(` ${icons[v.severity]} [${v.severity.toUpperCase()}] ${v.category}`); lines.push(` Issue: ${v.issue}`); lines.push(` Fix: ${v.recommendation}`); } lines.push(''); lines.push('─'.repeat(52)); lines.push(` Compliance: ${(report.best_practices_compliance * 100).toFixed(0)}%`); lines.push(` Audit time: ${report.audit_timestamp}`); lines.push(` Engine: v${report.engine_version}`); lines.push(''); return lines.join('\n'); } // ── Main ── if (!command || args.includes('--help') || args.includes('-h')) { usage(); process.exit(0); } try { if (command === 'audit') { const config = loadConfig(); const report = auditConfig(config); if (args.includes('--summary')) { console.log(formatSummary(report)); } else { console.log(JSON.stringify(report, null, 2)); } // Exit code based on risk process.exit(report.risk_level === 'CRITICAL' ? 2 : report.risk_level === 'HIGH' ? 1 : 0); } else if (command === 'sanitize') { const config = loadConfig(); console.log(sanitizeAndStringify(config)); } else { console.error(`Unknown command: ${command}. Use --help for usage.`); process.exit(1); } } catch (e) { console.error(`Error: ${e.message}`); process.exit(1); }
-
-
scripts
-
shield-audit.sh 323 B
#!/bin/bash # Quick audit wrapper for OpenClaw Shield # Usage: ./scripts/shield-audit.sh [config-path] [--summary|--json] SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SHIELD="$SCRIPT_DIR/../bin/shield.js" CONFIG="${1:-$HOME/.openclaw/openclaw.json}" FORMAT="${2:---summary}" exec node "$SHIELD" audit "$CONFIG" "$FORMAT"
-
-
src
-
audit.js 23 KB
#!/usr/bin/env node /** * OpenClaw Shield — Core Security Audit Engine * Analyzes an OpenClaw config JSON and produces a structured security report. * * Usage: const { auditConfig } = require('./audit'); const report = auditConfig(configObj); */ 'use strict'; const SEVERITY = { critical: 4, high: 3, medium: 2, low: 1 }; function auditConfig(config, options = {}) { const vulns = []; const push = (v) => vulns.push(v); checkGatewayAuth(config, push); checkNetworkExposure(config, push); checkChannelSecurity(config, push); checkDmPolicy(config, push); checkSubagentPermissions(config, push); checkToolPermissions(config, push); checkSecretLeakage(config, push, options); checkSandboxExecution(config, push); checkPluginConfig(config, push); checkHeartbeatExposure(config, push); checkRemoteConfig(config, push); // Sort by severity (critical first) vulns.sort((a, b) => SEVERITY[b.severity] - SEVERITY[a.severity]); const score = computeScore(vulns); const riskLevel = score >= 80 ? 'LOW' : score >= 60 ? 'MEDIUM' : score >= 40 ? 'HIGH' : 'CRITICAL'; const bpc = Math.max(0, Math.min(1, score / 100)); return { risk_level: riskLevel, overall_score: score, vulnerabilities: vulns, vulnerability_count: { critical: vulns.filter(v => v.severity === 'critical').length, high: vulns.filter(v => v.severity === 'high').length, medium: vulns.filter(v => v.severity === 'medium').length, low: vulns.filter(v => v.severity === 'low').length, total: vulns.length, }, best_practices_compliance: Math.round(bpc * 100) / 100, action_recommended: score >= 70 ? 'APPROVE' : score >= 40 ? 'REVIEW_AND_REMEDIATE' : 'BLOCK', safe_to_deploy: score >= 60, audit_timestamp: new Date().toISOString(), engine_version: '1.0.0', }; } // ────────────────────────────────────────────── // 1. Gateway Auth // ────────────────────────────────────────────── function checkGatewayAuth(config, push) { const gw = config.gateway || {}; const auth = gw.auth || {}; const controlUi = gw.controlUi || {}; if (!auth.mode && !auth.token) { push({ category: 'gateway_auth', severity: 'critical', issue: 'Gateway has no authentication configured — anyone with network access can control the agent.', recommendation: 'Set gateway.auth.mode to "token" and provide a strong gateway.auth.token.', auto_fix: { path: 'gateway.auth', value: { mode: 'token' } }, }); } if (auth.token && auth.token.length < 24) { push({ category: 'gateway_auth', severity: 'high', issue: `Gateway auth token is only ${auth.token.length} characters — too short for production use.`, recommendation: 'Use a token of at least 32 characters generated with a CSPRNG.', }); } if (controlUi.allowInsecureAuth === true) { push({ category: 'gateway_auth', severity: 'high', issue: 'Control UI has allowInsecureAuth enabled — authentication can be bypassed or downgraded.', recommendation: 'Set gateway.controlUi.allowInsecureAuth to false.', auto_fix: { path: 'gateway.controlUi.allowInsecureAuth', value: false }, }); } if (controlUi.enabled === true && !auth.token) { push({ category: 'gateway_auth', severity: 'high', issue: 'Control UI is enabled but no gateway auth token is set — the UI is accessible without credentials.', recommendation: 'Set a strong gateway.auth.token or disable the control UI.', }); } } // ────────────────────────────────────────────── // 2. Network Exposure // ────────────────────────────────────────────── function checkNetworkExposure(config, push) { const gw = config.gateway || {}; const bind = (gw.bind || '').toLowerCase(); if (bind && bind !== 'loopback' && bind !== 'localhost' && bind !== '127.0.0.1' && bind !== '::1') { const sev = (bind === '0.0.0.0' || bind === '::' || bind === 'all') ? 'critical' : 'high'; push({ category: 'network_exposure', severity: sev, issue: `Gateway binds to "${gw.bind}" — the API is accessible from the network.`, recommendation: 'Set gateway.bind to "loopback" unless you explicitly need remote access.', auto_fix: { path: 'gateway.bind', value: 'loopback' }, }); } const ts = gw.tailscale || {}; if (ts.mode === 'on' || ts.mode === 'funnel') { if (ts.mode === 'funnel') { push({ category: 'network_exposure', severity: 'critical', issue: 'Tailscale Funnel is enabled — gateway is publicly reachable on the internet.', recommendation: 'Use tailscale.mode "on" (private tailnet only) instead of "funnel" unless public access is intended.', }); } } const proxies = gw.trustedProxies || []; const dangerousProxies = proxies.filter(p => p === '*' || p === '0.0.0.0/0' || p === '::/0'); if (dangerousProxies.length > 0) { push({ category: 'network_exposure', severity: 'high', issue: `trustedProxies contains wildcard entries: ${dangerousProxies.join(', ')} — any source IP can spoof headers.`, recommendation: 'Restrict trustedProxies to specific proxy IPs (e.g. 127.0.0.1, ::1).', }); } } // ────────────────────────────────────────────── // 3. Channel Security // ────────────────────────────────────────────── function checkChannelSecurity(config, push) { const channels = config.channels || {}; for (const [name, ch] of Object.entries(channels)) { const allowFrom = ch.allowFrom || []; if (allowFrom.includes('*')) { push({ category: 'channel_security', severity: 'high', issue: `Channel "${name}" uses wildcard allowFrom: ["*"] — any user can interact with the agent.`, recommendation: `Restrict ${name}.allowFrom to specific user IDs or remove the wildcard.`, auto_fix: { path: `channels.${name}.allowFrom`, value: '[]' }, }); } if (allowFrom.length === 0 && ch.enabled !== false) { push({ category: 'channel_security', severity: 'medium', issue: `Channel "${name}" has an empty allowFrom list — depending on defaults, this may allow all or deny all.`, recommendation: `Explicitly configure ${name}.allowFrom with authorized user IDs.`, }); } } } // ────────────────────────────────────────────── // 4. DM Policy // ────────────────────────────────────────────── function checkDmPolicy(config, push) { const channels = config.channels || {}; for (const [name, ch] of Object.entries(channels)) { if (ch.dmPolicy === 'open') { push({ category: 'dm_policy', severity: 'medium', issue: `Channel "${name}" has dmPolicy set to "open" — any user can DM the agent without pairing.`, recommendation: 'Set dmPolicy to "pairing" to require explicit user approval.', auto_fix: { path: `channels.${name}.dmPolicy`, value: 'pairing' }, }); } if (!ch.dmPolicy && ch.enabled !== false) { push({ category: 'dm_policy', severity: 'low', issue: `Channel "${name}" has no explicit dmPolicy — relying on default behavior.`, recommendation: 'Explicitly set dmPolicy to "pairing" for clarity and security.', }); } } } // ────────────────────────────────────────────── // 5. Subagent Permissions // ────────────────────────────────────────────── function checkSubagentPermissions(config, push) { const agents = (config.agents && config.agents.list) || []; for (const agent of agents) { const sa = agent.subagents || {}; const allowed = sa.allowAgents || []; if (allowed.includes('*')) { push({ category: 'subagent_perms', severity: 'high', issue: `Agent "${agent.id}" has wildcard subagent permission (allowAgents: ["*"]) — can spawn any agent.`, recommendation: `Restrict ${agent.id}.subagents.allowAgents to specific agent IDs.`, auto_fix: { path: `agents.list[id=${agent.id}].subagents.allowAgents`, value: 'remove_wildcard' }, }); } // Check for self-delegation (circular risk) if (allowed.includes(agent.id)) { push({ category: 'subagent_perms', severity: 'low', issue: `Agent "${agent.id}" can spawn itself — potential infinite recursion risk.`, recommendation: `Remove "${agent.id}" from its own allowAgents list unless self-delegation is intentional.`, }); } } // Check for circular delegation chains const graph = {}; for (const agent of agents) { graph[agent.id] = (agent.subagents || {}).allowAgents || []; } const cycles = findCycles(graph); if (cycles.length > 0) { push({ category: 'subagent_perms', severity: 'medium', issue: `Circular subagent delegation detected: ${cycles.map(c => c.join(' → ')).join('; ')}`, recommendation: 'Break circular delegation chains to prevent infinite agent loops.', }); } // Check for no maxConcurrent limits const defaults = (config.agents && config.agents.defaults) || {}; const defSub = defaults.subagents || {}; if (!defSub.maxConcurrent && defSub.maxConcurrent !== 0) { push({ category: 'subagent_perms', severity: 'low', issue: 'No global subagent maxConcurrent limit set — agents could spawn unlimited subagents.', recommendation: 'Set agents.defaults.subagents.maxConcurrent to a reasonable limit (e.g., 4-8).', }); } } function findCycles(graph) { const cycles = []; const visited = new Set(); const stack = new Set(); function dfs(node, path) { if (stack.has(node)) { const cycleStart = path.indexOf(node); if (cycleStart >= 0) { cycles.push(path.slice(cycleStart).concat(node)); } return; } if (visited.has(node)) return; visited.add(node); stack.add(node); path.push(node); for (const neighbor of (graph[node] || [])) { if (graph[neighbor]) { // only check known agents dfs(neighbor, [...path]); } } stack.delete(node); } for (const node of Object.keys(graph)) { dfs(node, []); } return cycles; } // ────────────────────────────────────────────── // 6. Tool Permissions // ────────────────────────────────────────────── function checkToolPermissions(config, push) { const agents = (config.agents && config.agents.list) || []; const fullProfileAgents = agents.filter(a => a.tools && a.tools.profile === 'full'); if (fullProfileAgents.length > 0) { // More than half the agents having full tools is a smell const ratio = fullProfileAgents.length / agents.length; if (ratio > 0.5) { push({ category: 'tool_permissions', severity: 'medium', issue: `${fullProfileAgents.length}/${agents.length} agents have tools.profile: "full" — most agents are over-privileged.`, recommendation: 'Apply principle of least privilege: only admin/main agents should have full tool access. Others should use restrictive profiles.', affected_agents: fullProfileAgents.map(a => a.id), }); } for (const agent of fullProfileAgents) { if (agent.id !== 'main') { push({ category: 'tool_permissions', severity: 'low', issue: `Non-main agent "${agent.id}" has tools.profile: "full" — can access all tools including destructive ones.`, recommendation: `Consider restricting ${agent.id} to only the tools it needs.`, }); } } } } // ────────────────────────────────────────────── // 7. Secret Leakage // ────────────────────────────────────────────── function checkSecretLeakage(config, push, options = {}) { const secretPatterns = [ { pattern: /sk-or-v1-[a-f0-9]{64}/i, type: 'OpenRouter API key' }, { pattern: /sk-[a-zA-Z0-9]{20,}/i, type: 'API key (sk-* pattern)' }, { pattern: /\b[0-9]+:[A-Za-z0-9_-]{35,}\b/, type: 'Telegram bot token' }, { pattern: /0x[a-fA-F0-9]{64}/i, type: 'Private key (hex 256-bit)' }, { pattern: /xai-[a-zA-Z0-9]{20,}/i, type: 'xAI API key' }, { pattern: /ghp_[a-zA-Z0-9]{36,}/i, type: 'GitHub personal access token' }, { pattern: /gho_[a-zA-Z0-9]{36,}/i, type: 'GitHub OAuth token' }, { pattern: /glpat-[a-zA-Z0-9_-]{20,}/i, type: 'GitLab personal access token' }, { pattern: /AKIA[0-9A-Z]{16}/i, type: 'AWS access key' }, { pattern: /-----BEGIN (RSA |EC |DSA |OPENSSH |)PRIVATE KEY-----/, type: 'PEM private key' }, ]; const configStr = JSON.stringify(config); const foundSecrets = new Set(); // Check top-level env scanObj('env', config.env || {}, secretPatterns, push, foundSecrets); // Check skill entries env const skills = (config.skills && config.skills.entries) || {}; for (const [skillName, skill] of Object.entries(skills)) { scanObj(`skills.entries.${skillName}.env`, skill.env || {}, secretPatterns, push, foundSecrets); } // Check channel configs for tokens const channels = config.channels || {}; for (const [chName, ch] of Object.entries(channels)) { if (ch.botToken) { for (const sp of secretPatterns) { if (sp.pattern.test(ch.botToken)) { const key = `channels.${chName}.botToken`; if (!foundSecrets.has(key)) { foundSecrets.add(key); push({ category: 'secret_leakage', severity: 'critical', issue: `Channel "${chName}" has a ${sp.type} in botToken — this is expected for channel config but MUST NOT be shared.`, recommendation: 'Ensure this config file is never shared, committed to git, or sent to external services without sanitization.', }); } } } } } // Check gateway auth token exposure const gwToken = (config.gateway && config.gateway.auth && config.gateway.auth.token) || ''; if (gwToken) { push({ category: 'secret_leakage', severity: 'medium', issue: 'Gateway auth token is stored in plaintext in the config file.', recommendation: 'Consider using environment variable references or a secrets manager for the gateway token.', }); } // Check remote token const remote = (config.gateway && config.gateway.remote) || {}; if (remote.token && remote.token !== '' && remote.token !== 'blabla') { push({ category: 'secret_leakage', severity: 'medium', issue: 'Gateway remote.token is stored in plaintext — this token grants remote gateway access.', recommendation: 'Use environment variable references for remote tokens.', }); } else if (remote.token === 'blabla' || remote.token === 'changeme' || remote.token === 'test') { push({ category: 'secret_leakage', severity: 'high', issue: `Gateway remote.token is set to a placeholder value ("${remote.token}") — this is trivially guessable.`, recommendation: 'Generate a strong random token for gateway.remote.token.', }); } } function scanObj(path, obj, patterns, push, found) { for (const [key, val] of Object.entries(obj)) { if (typeof val !== 'string') continue; for (const sp of patterns) { if (sp.pattern.test(val)) { const loc = `${path}.${key}`; if (!found.has(loc)) { found.add(loc); push({ category: 'secret_leakage', severity: 'critical', issue: `${sp.type} found in plaintext at ${loc}.`, recommendation: `Move ${loc} to an environment variable or secrets manager. Never share this config without sanitizing it first.`, }); } } } } } // ────────────────────────────────────────────── // 8. Sandbox/Execution // ────────────────────────────────────────────── function checkSandboxExecution(config, push) { // Check for agents without workspace isolation const agents = (config.agents && config.agents.list) || []; const noWorkspace = agents.filter(a => !a.workspace && !a.default); if (noWorkspace.length > 0) { push({ category: 'sandbox_execution', severity: 'medium', issue: `${noWorkspace.length} non-default agent(s) have no explicit workspace — they share the default workspace. Agents: ${noWorkspace.map(a => a.id).join(', ')}`, recommendation: 'Assign separate workspaces to each agent for isolation.', }); } // Check if command execution controls exist const defaults = (config.agents && config.agents.defaults) || {}; if (!defaults.sandbox && !defaults.execution) { push({ category: 'sandbox_execution', severity: 'low', issue: 'No explicit sandbox or execution policy defined in agent defaults.', recommendation: 'Consider setting execution restrictions for non-admin agents.', }); } } // ────────────────────────────────────────────── // 9. Plugin Config // ────────────────────────────────────────────── function checkPluginConfig(config, push) { const plugins = (config.plugins && config.plugins.entries) || {}; const channels = config.channels || {}; for (const [pluginName, plugin] of Object.entries(plugins)) { if (plugin.enabled === true && !channels[pluginName]) { push({ category: 'plugin_config', severity: 'medium', issue: `Plugin "${pluginName}" is enabled but has no corresponding channel configuration.`, recommendation: `Add a channels.${pluginName} configuration or disable the plugin.`, }); } } // Check for channels without corresponding plugins for (const [chName, ch] of Object.entries(channels)) { if (ch.enabled !== false && !plugins[chName]) { push({ category: 'plugin_config', severity: 'low', issue: `Channel "${chName}" is configured but has no corresponding plugin entry.`, recommendation: `Add plugins.entries.${chName} or verify the channel is loaded by default.`, }); } } } // ────────────────────────────────────────────── // 10. Heartbeat Exposure // ────────────────────────────────────────────── function checkHeartbeatExposure(config, push) { const agents = (config.agents && config.agents.list) || []; for (const agent of agents) { const hb = agent.heartbeat || {}; if (hb.prompt && typeof hb.prompt === 'string') { // Check if heartbeat prompt contains sensitive paths or secrets if (/password|secret|key|token|credential/i.test(hb.prompt)) { push({ category: 'heartbeat_exposure', severity: 'medium', issue: `Agent "${agent.id}" heartbeat prompt may reference sensitive information.`, recommendation: 'Avoid embedding secrets or sensitive paths directly in heartbeat prompts.', }); } } // Very frequent heartbeats increase attack surface via prompt injection if (hb.every) { const minutes = parseInterval(hb.every); if (minutes > 0 && minutes < 5) { push({ category: 'heartbeat_exposure', severity: 'low', issue: `Agent "${agent.id}" heartbeat interval is very frequent (${hb.every}) — increases prompt injection surface.`, recommendation: 'Use heartbeat intervals of at least 10-15 minutes unless high-frequency is required.', }); } } } } function parseInterval(str) { const m = /^(\d+)\s*(m|min|minutes?)$/i.exec(str); if (m) return parseInt(m[1], 10); const h = /^(\d+)\s*(h|hr|hours?)$/i.exec(str); if (h) return parseInt(h[1], 10) * 60; return 0; } // ────────────────────────────────────────────── // 11. Remote Config // ────────────────────────────────────────────── function checkRemoteConfig(config, push) { const remote = (config.gateway && config.gateway.remote) || {}; if (remote.url) { push({ category: 'remote_config', severity: 'medium', issue: `Remote gateway URL is configured: ${remote.url}`, recommendation: 'Ensure the remote URL is only accessible on trusted networks (e.g., Tailscale, VPN).', }); if (remote.url.startsWith('ws://') && !remote.url.includes('localhost') && !remote.url.includes('127.0.0.1')) { push({ category: 'remote_config', severity: 'high', issue: 'Remote gateway uses unencrypted WebSocket (ws://) — traffic can be intercepted.', recommendation: 'Use wss:// (encrypted WebSocket) for remote connections.', auto_fix: { path: 'gateway.remote.url', value: remote.url.replace('ws://', 'wss://') }, }); } } // Check trustedProxies breadth const proxies = (config.gateway && config.gateway.trustedProxies) || []; if (proxies.length > 5) { push({ category: 'remote_config', severity: 'low', issue: `${proxies.length} trusted proxies configured — wide proxy trust increases header-spoofing risk.`, recommendation: 'Minimize trusted proxies to only those actually in the request path.', }); } } // ────────────────────────────────────────────── // Scoring // ────────────────────────────────────────────── function computeScore(vulns) { // Start at 100, deduct per vulnerability let score = 100; const deductions = { critical: 15, high: 8, medium: 4, low: 1 }; for (const v of vulns) { score -= deductions[v.severity] || 2; } return Math.max(0, Math.min(100, score)); } module.exports = { auditConfig }; -
sanitize.js 1.9 KB
#!/usr/bin/env node /** * OpenClaw Shield — Config Sanitizer * Strips secrets from an OpenClaw config before external submission. */ 'use strict'; const REDACTED = '[REDACTED]'; const SECRET_KEY_PATTERNS = [ /key$/i, /token$/i, /secret$/i, /password$/i, /credential$/i, /private/i, ]; const SECRET_VALUE_PATTERNS = [ /^sk-or-v1-[a-f0-9]+$/i, // OpenRouter /^sk-[a-zA-Z0-9]+$/i, // Generic API key /^\d+:[A-Za-z0-9_-]{35,}$/, // Telegram bot token /^0x[a-fA-F0-9]{64}$/i, // Private key /^xai-[a-zA-Z0-9]+$/i, // xAI /^ghp_[a-zA-Z0-9]+$/i, // GitHub /^gho_[a-zA-Z0-9]+$/i, // GitHub OAuth /^glpat-[a-zA-Z0-9_-]+$/i, // GitLab /^AKIA[0-9A-Z]{16}$/i, // AWS ]; function sanitizeConfig(config) { return deepSanitize(JSON.parse(JSON.stringify(config)), []); } function deepSanitize(obj, path) { if (obj === null || obj === undefined) return obj; if (Array.isArray(obj)) { return obj.map((item, i) => deepSanitize(item, [...path, String(i)])); } if (typeof obj === 'object') { const result = {}; for (const [key, val] of Object.entries(obj)) { result[key] = deepSanitize(val, [...path, key]); } return result; } if (typeof obj === 'string') { const currentKey = path[path.length - 1] || ''; // Check if key name suggests a secret if (SECRET_KEY_PATTERNS.some(p => p.test(currentKey))) { return REDACTED; } // Check if value looks like a secret if (SECRET_VALUE_PATTERNS.some(p => p.test(obj))) { return REDACTED; } // Redact env sections aggressively if (path.includes('env') && obj.length > 20) { return REDACTED; } } return obj; } function sanitizeAndStringify(config, pretty = true) { const clean = sanitizeConfig(config); return pretty ? JSON.stringify(clean, null, 2) : JSON.stringify(clean); } module.exports = { sanitizeConfig, sanitizeAndStringify };
-
-
package.json 622 B
{ "name": "openclaw-shield", "version": "1.0.0", "description": "Security audit engine for OpenClaw configurations — find vulnerabilities, misconfigs, and secret leaks before they become breaches.", "main": "src/audit.js", "bin": { "shield": "bin/shield.js" }, "scripts": { "audit": "node bin/shield.js audit --live --summary", "audit:json": "node bin/shield.js audit --live", "test": "node bin/shield.js audit --live --summary" }, "keywords": [ "openclaw", "security", "audit", "config", "hardening" ], "author": "Autonomous Intelligence", "license": "MIT" } -
README.md 3.6 KB
# 🛡️ OpenClaw Shield **Security audit engine for OpenClaw configurations.** Find vulnerabilities, misconfigurations, and secret leaks before they become breaches. Built by [Autonomous Intelligence](https://autonomousintelligence.ai) — we run 9 agents in production. We know what breaks. ## Features - **11 security check categories** covering auth, networking, channels, agents, secrets, and more - **Structured JSON reports** with risk scores, severity levels, and remediation steps - **Config sanitizer** to strip secrets before sharing - **CLI tool** for quick audits - **Zero dependencies** — pure Node.js, no npm install needed ## Quick Start ```bash # Audit your live config node bin/shield.js audit ~/.openclaw/openclaw.json --summary # Get JSON report node bin/shield.js audit ~/.openclaw/openclaw.json # Strip secrets before sharing node bin/shield.js sanitize ~/.openclaw/openclaw.json > safe-config.json ``` ## Sample Output ``` ╔══════════════════════════════════════════════════╗ ║ 🛡️ OpenClaw Shield Audit Report ║ ╚══════════════════════════════════════════════════╝ Risk Level: HIGH Score: 42/100 Safe to Deploy: ❌ No Action: REVIEW_AND_REMEDIATE Vulnerabilities: 12 🔴 Critical: 2 🟠 High: 3 🟡 Medium: 4 🔵 Low: 3 ``` ## Security Checks | # | Category | What It Catches | |---|----------|----------------| | 1 | Gateway Auth | Missing auth token, insecure UI, weak tokens | | 2 | Network Exposure | Non-loopback bind, Tailscale funnel, wildcard proxies | | 3 | Channel Security | Wildcard allowFrom, missing allowlists | | 4 | DM Policy | Open DM policy without pairing | | 5 | Subagent Permissions | Wildcard allowAgents, circular chains, self-spawn | | 6 | Tool Permissions | Over-privileged agents (full tool access) | | 7 | Secret Leakage | API keys, tokens, private keys in plaintext | | 8 | Sandbox/Execution | Missing workspace isolation, no exec policies | | 9 | Plugin Config | Orphaned plugins, missing channel configs | | 10 | Heartbeat Exposure | Sensitive data in heartbeat prompts | | 11 | Remote Config | Unencrypted WebSocket, exposed remote tokens | ## JSON Report Schema ```json { "risk_level": "CRITICAL|HIGH|MEDIUM|LOW", "overall_score": 0-100, "vulnerabilities": [{ "category": "string", "severity": "critical|high|medium|low", "issue": "Description of the problem", "recommendation": "How to fix it", "auto_fix": { "path": "config.path", "value": "suggested_value" } }], "vulnerability_count": { "critical": 0, "high": 0, "medium": 0, "low": 0, "total": 0 }, "best_practices_compliance": 0.0-1.0, "action_recommended": "BLOCK|REVIEW_AND_REMEDIATE|APPROVE", "safe_to_deploy": true|false, "audit_timestamp": "ISO8601", "engine_version": "1.0.0" } ``` ## Programmatic Usage ```javascript const { auditConfig } = require('./src/audit'); const { sanitizeConfig } = require('./src/sanitize'); // Audit const config = require('./my-openclaw.json'); const report = auditConfig(config); if (!report.safe_to_deploy) { console.log(`${report.vulnerability_count.critical} critical issues found!`); } // Sanitize before sharing const clean = sanitizeConfig(config); ``` ## Pricing - **Local audit:** Free (install the skill, run locally) - **Premium remote audit:** $0.10/audit via ACP (deeper analysis + auto-fix patches) ## License MIT — Autonomous Intelligence 2026 -
SKILL.md 3.1 KB
--- name: openclaw-shield description: Security audit engine for OpenClaw configurations. Detects vulnerabilities, misconfigurations, secret leaks, and over-privileged agents. Use when the user asks about security, hardening, config review, or audit of their OpenClaw setup. metadata: {"openclaw":{"emoji":"🛡️","homepage":"https://github.com/autonomous-intelligence/openclaw-shield"}} --- # OpenClaw Shield — Security Audit Audit any OpenClaw config for security vulnerabilities, misconfigurations, and best-practice violations. Produces a structured JSON report with risk scores, findings, and remediation steps. ## When to Use - User asks to check/audit/review their OpenClaw security - User wants to harden their config before deploying - User is setting up a new OpenClaw instance - User asks about secret leakage or API key exposure in their config - Before publishing or sharing any config file ## Quick Audit (live config) ```bash node scripts/shield-audit.sh ``` Or directly: ```bash node SKILL_DIR/bin/shield.js audit ~/.openclaw/openclaw.json --summary ``` ## What It Checks (11 categories) 1. **Gateway Auth** — missing/weak auth, insecure UI settings 2. **Network Exposure** — bind address, Tailscale funnel, wildcard proxies 3. **Channel Security** — wildcard allowFrom, missing allowlists 4. **DM Policy** — open DM policy without pairing 5. **Subagent Permissions** — wildcard allowAgents, circular delegation chains, self-delegation 6. **Tool Permissions** — over-privileged agents with tools.profile: "full" 7. **Secret Leakage** — API keys, tokens, private keys in plaintext config 8. **Sandbox/Execution** — missing workspace isolation, no execution policies 9. **Plugin Config** — enabled plugins without channel config 10. **Heartbeat Exposure** — sensitive data in heartbeat prompts 11. **Remote Config** — unencrypted WebSocket, exposed remote URLs/tokens ## Usage ### Audit a config file ```bash node SKILL_DIR/bin/shield.js audit <config.json> node SKILL_DIR/bin/shield.js audit <config.json> --summary # human-readable ``` ### Audit from stdin ```bash cat config.json | node SKILL_DIR/bin/shield.js audit --stdin ``` ### Sanitize a config (strip secrets) ```bash node SKILL_DIR/bin/shield.js sanitize <config.json> ``` ### Programmatic use ```javascript const { auditConfig } = require('SKILL_DIR/src/audit'); const config = require('./openclaw.json'); const report = auditConfig(config); console.log(report.risk_level); // "CRITICAL" | "HIGH" | "MEDIUM" | "LOW" console.log(report.overall_score); // 0-100 console.log(report.vulnerabilities); // detailed findings ``` ## Output Returns JSON with: `risk_level`, `overall_score` (0-100), `vulnerabilities[]`, `vulnerability_count`, `best_practices_compliance`, `action_recommended`, `safe_to_deploy`, `audit_timestamp`. ## Workflow for Agent 1. Load the user's config: `cat ~/.openclaw/openclaw.json` 2. Run: `node SKILL_DIR/bin/shield.js audit ~/.openclaw/openclaw.json --summary` 3. Present findings to user with prioritized recommendations 4. Offer to sanitize before sharing: `node SKILL_DIR/bin/shield.js sanitize <file>` -
_meta.json 134 B
{ "ownerId": "kn75drek4c08k4c6p9bk9hwzsn81q4r5", "slug": "ai-shield-audit", "version": "1.0.0", "publishedAt": 1771884479257 }
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.