Skill: xunxingyuan/openclaw-security-suite (version 0.2.3)

--- name: openclaw-security-suite description: Comprehensive security suite for OpenClaw skills. Includes static scanning (AST + keywords) and AI-powered semantic behavior review to detect malicious code. version: 0.2.3 tags: - security - scanner - code-review - static-analysis - ai-review requirements: binaries: - node environment: [] input_schema: type: object properties: action: type: string enum: ["scan", "review"] description: Action to perform. "scan" for static analysis, "review" for AI semantic review. path: type: string description: Absolute path to the skill directory (for scan) or file (for review) required: - action - path --- # OpenClaw Security Suite A comprehensive security protection layer for OpenClaw extensions, providing both static analysis and AI-assisted behavioral review. ## Features This suite bundles two core security capabilities: ### 1. Static Security Scan (`action: "scan"`) Analyzes a full skill directory for deterministic threats: - **Blocked Imports**: e.g., `child_process`, `cluster` - **Dangerous Functions**: e.g., `exec()`, `spawn()` - **Known Bad Keywords**: e.g., `eval(`, `__proto__`, `rm -rf` - **Sensitive File Access**: e.g., `/etc/passwd`, `/.env` - **Suspicious Regex Patterns**: e.g., `curl ... | bash` ### 2. AI Code Review (`action: "review"`) Uses the active LLM context (`ctx.llm`) to semantically analyze a specific file for hidden threats: - **Data exfiltration** - **Credential leaks** - **Obfuscated shell execution** - **System modification** ## Usage You must specify an `action` and a `path`. **Example 1: Static Scan** ```json { "action": "scan", "path": "/path/to/skill/directory" } ``` **Example 2: AI Review** ```json { "action": "review", "path": "/path/to/skill/index.ts" } ``` ## Output **Scan Output:** ```json { "safe": false, "results": [ { "file": "index.ts", "issues": [{ "type": "blocked_module", "module": "child_process" }] } ] } ``` **Review Output:** ```json { "risk_level": "high", "reason": "Code reads AWS credentials from environment and posts them to an external IP." } ```

path: CHANGELOG.md
content: # Changelog

## 0.2.0 (2026-03-11)

### Added
- `SKILL.md` for `scan_skill` and `review_skill` (ClawHub compatible)
- `malicious_patterns.json` with 4 detection categories:
  - `dangerous_keywords` (22 patterns)
  - `sensitive_files` (4 paths)
  - `suspicious_urls` (2 URLs)
  - `suspicious_patterns` (4 regex rules)
- `tsconfig.json` (ESNext + NodeNext)
- Test fixtures and verification script (`test/verify.ts`)
- `.gitignore` and `README.md`

### Changed
- Replaced deprecated `vm2` with Node built-in `node:vm` module
- Enhanced `keyword_scanner.ts` to support all 4 pattern categories
- Fixed `ast_scanner.ts` ESM compatibility with `@babel/traverse`
- Improved `callee.name` access with proper type narrowing

### Security
- Removed `vm2` dependency (CVE-2023-37466 and other sandbox escape vulnerabilities)

## 0.1.0

### Added
- Initial implementation
- AST scanner with Babel
- Keyword scanner
- VM sandbox runner (vm2)
- Runtime guard
- AI review via LLM
- Signature verification


path: README.md
content: # OpenClaw Security

> Security protection layer for OpenClaw skills — scan, review, and guard against malicious code.

## Architecture

```
security_skill_scanner/
├── index.ts                 # Plugin entry — exports scanSkill & reviewSkill
├── config/
│   ├── policy.json          # Blocked modules, functions & permissions
│   └── malicious_patterns.json  # Dangerous keyword patterns
├── scanner/
│   ├── skill_scanner.ts     # Orchestrator — scans all .ts/.js in a directory
│   ├── ast_scanner.ts       # AST-based detection (blocked imports & calls)
│   ├── keyword_scanner.ts   # Text-based dangerous keyword matching
│   └── vm_runner.ts         # Sandboxed code execution (Node vm)
├── guard/
│   └── runtime_guard.ts     # Runtime protection (block metadata & shell access)
├── llm/
│   └── ai_review.ts         # LLM-powered code review
├── signature/
│   └── verify_signature.ts  # SHA256 file signature verification
└── skills/
    ├── scan_skill/index.ts  # Skill: static security scan
    └── review_skill/index.ts # Skill: AI-assisted code review
```

### `openclaw-security-suite`

Unified security tool providing both static analysis and AI-assisted code review.

**Input:**
```json
{ 
  "action": "scan", // or "review"
  "path": "/path/to/skill/directory" 
}
```

**Output (Scan):**
```json
{
  "safe": false,
  "results": [
    {
      "file": "index.ts",
      "issues": [
        { "type": "blocked_module", "module": "child_process" },
        { "type": "keyword", "keyword": "eval(" }
      ]
    }
  ]
}
```

## Security Layers

| Layer | Method | What it catches |
|-------|--------|-----------------|
| **AST Scanner** | Babel AST traversal | Blocked module imports (`child_process`, `cluster`), dangerous function calls (`exec`, `spawn`) |
| **Keyword Scanner** | Text matching | `eval(`, `__proto__`, `process.env`, `fs.writeFileSync`, etc. |
| **VM Runner** | Node.js `vm` sandbox | Runtime behavior analysis with memory isolation and 1s timeout |
| **Runtime Guard** | Argument inspection | Cloud metadata access (`169.254.169.254`), shell command execution |
| **AI Review** | LLM analysis | Data exfiltration, credential leaks, system modification |
| **Signature Verify** | SHA256 + public key | File integrity and authenticity |

## Setup

```bash
npm install
```

## Configuration

### `config/policy.json`

```json
{
  "blocked_modules": ["child_process", "cluster"],
  "blocked_functions": ["exec", "spawn", "execSync"],
  "allowed_permissions": ["network", "memory"],
  "blocked_permissions": ["shell", "process"]
}
```

### `config/malicious_patterns.json`

Contains a list of `dangerous_keywords` matched via text search (e.g. `eval(`, `Function(`, `__proto__`).

## License

MIT


path: config/malicious_patterns.json
content: {
    "dangerous_keywords": [
        "eval(",
        "Function(",
        "setTimeout(String",
        "setInterval(String",
        "__proto__",
        "constructor.constructor",
        "process.env",
        "process.exit",
        "require('child_process')",
        "require(\"child_process\")",
        "import('child_process')",
        "fs.writeFileSync",
        "fs.unlinkSync",
        "fs.rmdirSync",
        "net.createServer",
        "http.createServer",
        "globalThis",
        "Reflect.construct",
        "child_process",
        "exec(",
        "spawn(",
        "rm -rf",
        "fs.rm",
        "fs.rmdir",
        "\"nc ",
        "\"netcat ",
        "\"ssh ",
        "chmod 777"
    ],
    "sensitive_files": [
        "/etc/passwd",
        "/etc/shadow",
        "/.ssh",
        "/.env"
    ],
    "suspicious_urls": [
        "metadata.google.internal",
        "169.254.169.254"
    ],
    "suspicious_patterns": [
        "curl .+\\|.+bash",
        "wget .+\\|.+sh\\b",
        "curl .+169\\.254\\.169\\.254",
        "curl .+metadata\\.google\\.internal"
    ]
}

path: config/policy.json
content: {
    "blocked_modules": [
        "child_process",
        "cluster"
    ],
    "blocked_functions": [
        "exec",
        "spawn",
        "execSync"
    ],
    "allowed_permissions": [
        "network",
        "memory"
    ],
    "blocked_permissions": [
        "shell",
        "process"
    ]
}

path: guard/runtime_guard.ts
content: export function runtimeGuard(args: any) {

    if (!args) return

    if (args.url && args.url.includes("169.254.169.254")) {

        throw new Error("Blocked cloud metadata access")

    }

    if (args.command) {

        throw new Error("Shell execution blocked")

    }

}

path: index.ts
content: import fs from "fs"
import path from "path"
import url from "url"
import { scanSkill } from "./scanner/skill_scanner.js"
import { aiReview } from "./llm/ai_review.js"

// ESM alternative to __dirname
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))

export default {

    id: "security_suite",

    description: "Unified openclaw security suite for static scanning and semantic review",

    async run({ action, path: targetPath }: any, ctx: any) {

        if (action === "scan") {

            const policy = JSON.parse(
                fs.readFileSync(path.join(__dirname, "config/policy.json"), "utf8")
            )

            const patterns = JSON.parse(
                fs.readFileSync(path.join(__dirname, "config/malicious_patterns.json"), "utf8")
            )

            return scanSkill(targetPath, policy, patterns)

        } else if (action === "review") {

            if (!fs.existsSync(targetPath)) {
                throw new Error(`File not found: ${targetPath}`)
            }

            const code = fs.readFileSync(targetPath, "utf8")

            if (!ctx || !ctx.llm) {
                throw new Error("LLM context (ctx.llm) is required for semantic review.")
            }

            return aiReview(code, ctx.llm)

        } else {
            throw new Error(`Unknown action: ${action}. Expected 'scan' or 'review'.`)
        }

    }

}

path: llm/ai_review.ts
content: export async function aiReview(code: string, llm: any) {

    const prompt = `
Analyze the following code for malicious behavior.

Check for:
- data exfiltration
- shell execution
- credential leak
- system modification

Code:
${code}

Return JSON:
risk_level
reason
`

    const result = await llm.generate(prompt)

    return result

}

path: package.json
content: {
    "name": "openclaw-security",
    "version": "0.2.3",
    "description": "Security scanner for OpenClaw skills",
    "author": "xxy",
    "license": "MIT",
    "type": "module",
    "keywords": [
        "openclaw",
        "security",
        "scanner",
        "skill",
        "clawhub",
        "static-analysis",
        "code-review"
    ],
    "scripts": {
        "publish:skill": "./scripts/publish.sh"
    },
    "dependencies": {
        "@babel/parser": "^7.26.0",
        "@babel/traverse": "^7.26.0"
    },
    "devDependencies": {
        "@types/babel__traverse": "^7.28.0",
        "@types/node": "^22.0.0",
        "typescript": "^5.6.0"
    }
}

path: scanner/ast_scanner.ts
content: import { parse } from "@babel/parser"
import _traverse from "@babel/traverse"
import type { NodePath } from "@babel/traverse"
import type { ImportDeclaration, CallExpression } from "@babel/types"

const traverse = (_traverse as any).default || _traverse

export function astScan(code: string, policy: any) {

    const issues: any[] = []

    const ast = parse(code, {
        sourceType: "module",
        plugins: ["typescript"]
    })

    traverse(ast, {

        ImportDeclaration(path: NodePath<ImportDeclaration>) {

            const moduleName = path.node.source.value

            if (policy.blocked_modules.includes(moduleName)) {

                issues.push({
                    type: "blocked_module",
                    module: moduleName
                })

            }

        },

        CallExpression(path: NodePath<CallExpression>) {

            const callee = path.node.callee

            const name = callee.type === "Identifier" ? callee.name : undefined

            if (name && policy.blocked_functions.includes(name)) {

                issues.push({
                    type: "blocked_function",
                    function: name
                })

            }

        }

    })

    return issues

}

path: scanner/keyword_scanner.ts
content: export function keywordScan(code: string, patterns: any) {

    const issues: any[] = []

    for (const keyword of patterns.dangerous_keywords ?? []) {

        if (code.includes(keyword)) {

            issues.push({
                type: "keyword",
                keyword
            })

        }

    }

    for (const file of patterns.sensitive_files ?? []) {

        if (code.includes(file)) {

            issues.push({
                type: "sensitive_file",
                file
            })

        }

    }

    for (const url of patterns.suspicious_urls ?? []) {

        if (code.includes(url)) {

            issues.push({
                type: "suspicious_url",
                url
            })

        }

    }

    for (const pattern of patterns.suspicious_patterns ?? []) {

        const regex = new RegExp(pattern)

        if (regex.test(code)) {

            issues.push({
                type: "suspicious_pattern",
                pattern
            })

        }

    }

    return issues

}

path: scanner/skill_scanner.ts
content: import fs from "fs"
import path from "path"

import { astScan } from "./ast_scanner.js"
import { keywordScan } from "./keyword_scanner.js"

export function scanSkill(dir: string, policy: any, patterns: any) {

    const files = fs.readdirSync(dir)

    let results: any[] = []

    for (const file of files) {

        if (!file.endsWith(".ts") && !file.endsWith(".js")) continue

        const full = path.join(dir, file)

        const code = fs.readFileSync(full, "utf8")

        const astIssues = astScan(code, policy)

        const keywordIssues = keywordScan(code, patterns)

        const issues = [...astIssues, ...keywordIssues]

        if (issues.length) {

            results.push({
                file,
                issues
            })

        }

    }

    return {
        safe: results.length === 0,
        results
    }

}

path: scanner/vm_runner.ts
content: import vm from "node:vm"

export function runSandbox(code: string) {

    const context = vm.createContext(Object.create(null))

    const result = vm.runInContext(code, context, {
        timeout: 1000
    })

    return result

}

path: signature/verify_signature.ts
content: import crypto from "crypto"
import fs from "fs"

export function verifySignature(file: string, signature: string, publicKey: string) {

    const data = fs.readFileSync(file)

    const verify = crypto.createVerify("SHA256")

    verify.update(data)

    return verify.verify(publicKey, signature, "hex")

}

path: tsconfig.json
content: {
    "compilerOptions": {
        "target": "ESNext",
        "module": "NodeNext",
        "moduleResolution": "NodeNext",
        "esModuleInterop": true,
        "strict": true,
        "outDir": "./dist",
        "rootDir": ".",
        "declaration": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
        "resolveJsonModule": true
    },
    "include": [
        "./**/*.ts"
    ],
    "exclude": [
        "node_modules",
        "dist"
    ]
}


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