Skill: x-rayluan/clawlite-openclaw-security-guard (version 0.2.1)

--- name: openclaw-security-guard description: This skill should be used when the user asks to harden agent workflows, audit prompts/commands/URLs/paths, scan a third-party skill before install or publish, review a skill folder for secrets or unsafe scripts, or add a lightweight local security guard before OpenClaw publishing and automation. --- # OpenClaw Security Guard Use this skill to run **fast local security checks** before trusting or publishing automation. ## What this skill is for Run this skill when you need to: - scan suspicious text for prompt injection / secret leakage patterns - validate shell commands before automation or publishing - validate URLs for SSRF / localhost / metadata access risks - validate file paths for traversal / sensitive file access - audit a skill folder for dangerous scripts, hardcoded secrets, exfiltration patterns, or unsafe install/publish flows - add a lightweight self-defense layer before using external skills ## Workflow 1. Choose the narrowest check needed. 2. Run one of the bundled scripts. 3. Treat `BLOCK` as stop-work until reviewed. 4. Treat `WARN` as requiring human review or a narrower sandbox. 5. For skill audits, review the flagged file lines before install/publish. ## Bundled scripts ### 1) Quick text / command / URL / path checks ```bash node {baseDir}/scripts/security-check.mjs text "<content>" node {baseDir}/scripts/security-check.mjs command "<shell command>" node {baseDir}/scripts/security-check.mjs url "<url>" node {baseDir}/scripts/security-check.mjs path "<path>" ``` ### 2) Skill / folder audit ```bash node {baseDir}/scripts/audit-skill-dir.mjs /absolute/or/relative/path/to/skill ``` ### 3) Write audit into Obsidian vault ```bash node {baseDir}/scripts/write-obsidian-audit.mjs /tmp/audit.json "Skill Audit - my-skill" ``` This writes a markdown audit note into the ClawLite Obsidian vault under `Security Audits/`. ### 4) Install lightweight local hook wrapper ```bash bash {baseDir}/scripts/install-hooks.sh ``` This installs a reusable workspace script for prepublish checks. This audits for: - hardcoded secrets / tokens - curl|bash / wget|sh installers - destructive shell patterns - risky exfiltration / webhook / netcat usage - suspicious file targets like `~/.ssh`, `/etc/passwd`, `.env`, `id_rsa` ## Verdicts - `ALLOW` — no high-risk pattern found in this lightweight pass - `WARN` — review manually before proceeding - `BLOCK` — do not trust / run / publish until reviewed ## Important limits - This is a **lightweight guard**, not a full sandbox. - Regex-based detection catches common dangerous patterns, not all attacks. - A clean result does **not** prove safety. - For high-risk code, still prefer human review and runtime isolation. ## Publishing / install guard Before publishing or installing a skill from GitHub / ClawHub: 1. run `audit-skill-dir.mjs` 2. inspect every `WARN` / `BLOCK` 3. only proceed when the remaining risk is understood ## References If you need the audit categories / philosophy, read: - `{baseDir}/references/checklist.md`

path: package.json
content: {
  "name": "openclaw-security-guard",
  "version": "0.2.1",
  "description": "This skill should be used when the user asks to harden agent workflows, audit prompts, commands, URLs, or paths, scan a third-party skill before install or publish, review a skill folder for secrets or unsafe scripts, or add a lightweight OpenClaw security guard before publishing and automation.",
  "keywords": ["openclaw","security","prompt-injection","command-injection","ssrf","path-traversal","secret-detection","clawhub","skill-audit","ai-safety"],
  "private": true,
  "type": "module",
  "bin": {
    "openclaw-security-check": "./scripts/security-check.mjs",
    "openclaw-security-audit": "./scripts/audit-skill-dir.mjs"
  }
}


path: references/checklist.md
content: # Security Guard Checklist

Use this skill as a **preflight filter**, not as proof of safety.

## High-risk patterns worth blocking
- prompt injection / instruction override
- dangerous shell execution chains
- SSRF / localhost / metadata endpoints
- path traversal / sensitive files
- hardcoded secrets
- exfiltration utilities and outbound secret posting

## Typical publish-time red flags
- `curl ... | bash`
- `wget ... | sh`
- `rm -rf`, `mkfs`, `dd`, `chmod -R 777`
- posting to unknown webhooks / Discord webhooks / pastebins
- embedded tokens / API keys / JWTs / private keys
- scripts that auto-modify shell startup files without clear user intent

## Typical install-time red flags
- hidden postinstall scripts
- fetching remote code during install without pinning / checksum
- unbounded exec on user-supplied content
- scanning or uploading `~/.ssh`, `.env`, `/etc/*`, keychain dumps

## Decision rule
- `ALLOW`: proceed if context is low risk
- `WARN`: review lines manually before proceeding
- `BLOCK`: do not install/publish/run until resolved


path: scripts/audit-skill-dir.mjs
content: #!/usr/bin/env node
import fs from 'fs';
import path from 'path';

const target = process.argv[2];
if (!target) {
  console.error('Usage: node audit-skill-dir.mjs <skill-dir>');
  process.exit(2);
}

const root = path.resolve(target);
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
  console.error(`Directory not found: ${root}`);
  process.exit(2);
}

const includeExt = new Set(['.md', '.js', '.mjs', '.ts', '.tsx', '.json', '.yaml', '.yml', '.sh']);
const findings = [];

const rules = [
  { level: 'BLOCK', label: 'hardcoded-secret', re: /(sk-[a-z0-9]{16,}|ghp_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|-----BEGIN (RSA|OPENSSH|EC) PRIVATE KEY-----|re_[A-Za-z0-9_]{10,})/i },
  { level: 'BLOCK', label: 'pipe-to-shell', re: /(curl .*\| *(bash|sh)|wget .*\| *(bash|sh))/i },
  { level: 'BLOCK', label: 'destructive-shell', re: /(rm -rf|mkfs|dd if=|chmod -R 777|chown -R root|:\(\)\{:\|:&\};:)/i },
  { level: 'WARN', label: 'exfiltration', re: /(discord\.com\/api\/webhooks|pastebin|transfer\.sh|api\.telegram\.org|nc |ncat |scp |rsync .*@)/i },
  { level: 'WARN', label: 'sensitive-path', re: /(\/etc\/passwd|id_rsa|authorized_keys|\.env|\.ssh|\.gnupg|keychain)/i },
  { level: 'WARN', label: 'postinstall-autoexec', re: /("postinstall"\s*:\s*"[^"]+")/i }
];

function walk(dir) {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    if (entry.name === '.git' || entry.name === 'node_modules' || entry.name === '.next' || entry.name === 'dist') continue;
    const p = path.join(dir, entry.name);
    if (entry.isDirectory()) walk(p);
    else if (includeExt.has(path.extname(entry.name).toLowerCase()) || entry.name === 'SKILL.md' || entry.name === 'package.json') scanFile(p);
  }
}

function scanFile(file) {
  const text = fs.readFileSync(file, 'utf8');
  const lines = text.split(/\r?\n/);
  lines.forEach((line, i) => {
    for (const rule of rules) {
      if (rule.re.test(line)) {
        findings.push({ level: rule.level, label: rule.label, file, line: i + 1, excerpt: line.trim().slice(0, 200) });
      }
    }
  });
}

walk(root);
const verdict = findings.some(f => f.level === 'BLOCK') ? 'BLOCK' : findings.some(f => f.level === 'WARN') ? 'WARN' : 'ALLOW';
console.log(JSON.stringify({ root, verdict, findings }, null, 2));
process.exit(verdict === 'BLOCK' ? 1 : 0);


path: scripts/install-hooks.sh
content: #!/usr/bin/env bash
set -euo pipefail
BASE_DIR="$(cd "$(dirname "$0")/.." && pwd)"
TARGET="$HOME/.openclaw/workspace/scripts/security-prepublish-guard.sh"
cat > "$TARGET" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
SKILL_DIR="${1:-}"
if [ -z "$SKILL_DIR" ]; then
  echo "Usage: security-prepublish-guard.sh <skill-dir>" >&2
  exit 2
fi
node "$HOME/.openclaw/workspace/skills/openclaw-security-guard/scripts/prepublish-guard.mjs" "$SKILL_DIR"
EOF
chmod +x "$TARGET"
echo "Installed: $TARGET"


path: scripts/prepublish-guard.mjs
content: #!/usr/bin/env node
import { spawnSync } from 'child_process';
import fs from 'fs';
import path from 'path';

const skillDir = process.argv[2];
if (!skillDir) {
  console.error('Usage: node prepublish-guard.mjs <skill-dir>');
  process.exit(2);
}
const abs = path.resolve(skillDir);
const auditScript = path.resolve(path.dirname(new URL(import.meta.url).pathname), 'audit-skill-dir.mjs');
const result = spawnSync(process.execPath, [auditScript, abs], { encoding: 'utf8' });
if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);
let parsed;
try { parsed = JSON.parse(result.stdout || '{}'); } catch { parsed = null; }
if (!parsed) process.exit(1);
const actionableBlocks = (parsed.findings || []).filter(f => f.level === 'BLOCK' && !/references\/checklist\.md|scripts\/audit-skill-dir\.mjs|scripts\/security-check\.mjs/.test(f.file));
if (actionableBlocks.length > 0) {
  console.error('\nPrepublish guard: BLOCK due to actionable findings.');
  process.exit(1);
}
console.log('\nPrepublish guard: PASS (no actionable BLOCK findings).');


path: scripts/security-check.mjs
content: #!/usr/bin/env node

const [, , mode, ...rest] = process.argv;
const input = rest.join(' ').trim();

if (!mode || !input) {
  console.error('Usage: node security-check.mjs <text|command|url|path> <value>');
  process.exit(2);
}

const RULES = {
  text: [
    { level: 'BLOCK', label: 'prompt-injection', re: /(ignore (all|previous) instructions|developer mode|system override|reveal (the )?system prompt|bypass safety|jailbreak)/i },
    { level: 'BLOCK', label: 'secret', re: /(sk-[a-z0-9]{16,}|ghp_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|-----BEGIN (RSA|OPENSSH|EC) PRIVATE KEY-----|xox[baprs]-[A-Za-z0-9-]+)/i },
    { level: 'WARN', label: 'exfiltration', re: /(send (me|this|that).*(token|secret|key|cookie)|upload .*?(\.env|id_rsa|passwd)|pastebin|webhook)/i },
    { level: 'WARN', label: 'obfuscation', re: /(```json|@context|mainEntity|acceptedAnswer|\| .* \| .* \|)/i }
  ],
  command: [
    { level: 'BLOCK', label: 'destructive-shell', re: /(rm -rf|mkfs|dd if=|shutdown|reboot|:\(\)\{:\|:&\};:)/i },
    { level: 'BLOCK', label: 'shell-pipe-fetch', re: /(curl .*\| *(bash|sh)|wget .*\| *(bash|sh))/i },
    { level: 'WARN', label: 'chaining', re: /(&&|\|\||;|\$\(|`)/ },
    { level: 'WARN', label: 'network-exfil', re: /(nc |ncat |scp |rsync .*@|curl .*https?:\/\/)/i }
  ],
  url: [
    { level: 'BLOCK', label: 'localhost', re: /https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])/i },
    { level: 'BLOCK', label: 'metadata-ssrf', re: /(169\.254\.169\.254|metadata\.google|100\.100\.100\.200|latest\/meta-data)/i },
    { level: 'BLOCK', label: 'file-uri', re: /^file:\/\//i },
    { level: 'WARN', label: 'private-ip', re: /https?:\/\/(10\.|192\.168\.|172\.(1[6-9]|2\d|3[0-1])\.)/i }
  ],
  path: [
    { level: 'BLOCK', label: 'traversal', re: /(\.\.\/|\.\.\\)/ },
    { level: 'BLOCK', label: 'sensitive-path', re: /(\/etc\/passwd|\/proc\/|id_rsa|\.env|keychain|\/var\/run\/docker\.sock|authorized_keys)/i },
    { level: 'WARN', label: 'home-secret-path', re: /(\.ssh|\.gnupg|\.aws|\.npmrc|\.pypirc)/i }
  ]
};

const rules = RULES[mode];
if (!rules) {
  console.error(`Unknown mode: ${mode}`);
  process.exit(2);
}

const findings = rules.filter(r => r.re.test(input));
const verdict = findings.some(f => f.level === 'BLOCK') ? 'BLOCK' : findings.some(f => f.level === 'WARN') ? 'WARN' : 'ALLOW';

console.log(JSON.stringify({ mode, verdict, findings: findings.map(f => ({ level: f.level, label: f.label })) }, null, 2));
process.exit(verdict === 'BLOCK' ? 1 : 0);


path: scripts/write-obsidian-audit.mjs
content: #!/usr/bin/env node
import fs from 'fs';
import path from 'path';

const [,, jsonPath, noteTitle] = process.argv;
if (!jsonPath) {
  console.error('Usage: node write-obsidian-audit.mjs <audit-json-path> [note-title]');
  process.exit(2);
}
const vaultDir = [REDACTED_SECRET];
const outDir = path.join(vaultDir, 'Security Audits');
fs.mkdirSync(outDir, { recursive: true });
const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
const ts = new Date().toISOString().replace(/[:.]/g, '-');
const title = noteTitle || `Security Audit ${ts}`;
const outPath = path.join(outDir, `${title}.md`);
const lines = [];
lines.push(`# ${title}`);
lines.push('');
lines.push(`- Root: ${data.root}`);
lines.push(`- Verdict: ${data.verdict}`);
lines.push(`- Findings: ${(data.findings || []).length}`);
lines.push('');
for (const f of (data.findings || [])) {
  lines.push(`- [${f.level}] ${f.label} — ${f.file}:${f.line}`);
  lines.push(`  - ${f.excerpt}`);
}
fs.writeFileSync(outPath, lines.join('\n'));
console.log(outPath);


Security scan verdicts — ClawScan: clean | static analysis: suspicious | VirusTotal: clean | SkillSpector: clean (LOW)