Skill: neckr0ik/neckr0ik-security-scanner (version 1.0.0)

--- name: neckr0ik-security-scanner version: 1.0.0 description: Security audit tool for OpenClaw skills. Scans skill directories for common vulnerabilities including hardcoded secrets, unsafe shell commands, prompt injection risks, unauthorized network access, and code execution dangers. Use when auditing skills before installation, reviewing skill code for security issues, or validating skills for ClawHub publication. --- # Skill Security Audit Scan OpenClaw skills for security vulnerabilities before installation or publication. ## Quick Start ```bash # Audit a single skill skill-security-audit audit /path/to/skill-folder # Audit all installed skills skill-security-audit audit-all # Generate security report skill-security-audit report /path/to/skill-folder --format json ``` ## What This Detects ### Critical Issues (Block Installation) | Issue | Description | Risk Level | |-------|-------------|------------| | **Hardcoded Secrets** | API keys, tokens, passwords in code | Critical | | **Shell Injection** | Unsanitized input to shell commands | Critical | | **Code Execution** | eval(), exec(), dynamic code execution | Critical | | **Unauthorized Network** | Calls to unknown/suspicious domains | Critical | ### High Issues (Review Required) | Issue | Description | Risk Level | |-------|-------------|------------| | **Prompt Injection** | User input in system prompts without sanitization | High | | **File Path Traversal** | Unchecked file paths from user input | High | | **Excessive Permissions** | Requests unnecessary system access | High | ### Medium Issues (Warnings) | Issue | Description | Risk Level | |-------|-------------|------------| | **Outdated Dependencies** | Packages with known CVEs | Medium | | **Unpinned Versions** | Floating dependency versions | Medium | | **Missing License** | No license file for distribution | Medium | ## Security Patterns ### Good Pattern: Environment Variables ```python # CORRECT: Load secrets from environment import os [REDACTED_SECRET]"OPENAI_API_KEY") ``` ### Bad Pattern: Hardcoded Secrets ```python # DANGEROUS: Secret in code [REDACTED_SECRET]" # NEVER DO THIS ``` ### Good Pattern: Sanitized Input ```python # CORRECT: Validate and sanitize import re def safe_filename(name): return re.sub(r'[^a-zA-Z0-9_-]', '', name) ``` ### Bad Pattern: Shell Injection ```python # DANGEROUS: User input to shell os.system(f"convert {user_file} output.png") # NEVER DO THIS ``` ## Running Audits ### Important: Self-Scan Results When running `skill-security-audit audit skill-security-audit/`, you will see findings for the pattern definitions themselves. This is expected — the scanner detects the example patterns in its own documentation. These are not real vulnerabilities. For actual skill audits, this produces accurate results. ### Single Skill Audit ```bash skill-security-audit audit ./my-skill/ ``` Output: - Pass/Fail status - List of vulnerabilities found - Severity ratings - Remediation suggestions ### Batch Audit (All Installed Skills) ```bash skill-security-audit audit-all ``` Scans `~/.openclaw/skills/` and reports on all installed skills. ### Report Formats ```bash # JSON for CI/CD integration skill-security-audit audit ./skill/ --format json # Markdown for documentation skill-security-audit audit ./skill/ --format markdown # Summary for quick review skill-security-audit audit ./skill/ --format summary ``` ## CI/CD Integration Add to your skill publishing pipeline: ```yaml # .github/workflows/publish.yml - name: Security Audit run: skill-security-audit audit ./skill/ ``` Exit codes: - 0: No issues found - 1: Medium+ issues found (warnings) - 2: Critical issues found (block) ## Publishing Secure Skills Before publishing to ClawHub: 1. Run `skill-security-audit audit ./your-skill/` 2. Fix all critical and high issues 3. Document any required secrets in README 4. Include `.env.example` with placeholder values 5. Re-run audit to confirm clean ## See Also - `references/vulnerabilities.md` — Complete vulnerability database - `references/remediation.md` — How to fix common issues - `scripts/audit.py` — Main audit script

path: claw.json
content: {
  "name": "skillguard",
  "version": "1.0.0",
  "description": "Security audit tool for OpenClaw skills. Scans for vulnerabilities including hardcoded secrets, shell injection, code execution risks, prompt injection, and path traversal.",
  "author": "Jarvis",
  "license": "MIT",
  "skills": [
    {
      "name": "skillguard",
      "description": "Security audit tool for OpenClaw skills. Scans skill directories for common vulnerabilities including hardcoded secrets, unsafe shell commands, prompt injection risks, unauthorized network access, and code execution dangers. Use when auditing skills before installation, reviewing skill code for security issues, or validating skills for ClawHub publication."
    }
  ],
  "scripts": {
    "audit": "python scripts/audit.py"
  },
  "keywords": [
    "security",
    "audit",
    "vulnerability",
    "secrets",
    "injection",
    "clawhub",
    "skill-security"
  ]
}

path: references/remediation.md
content: # Security Remediation Guide

How to fix common security vulnerabilities found by skill-security-audit.

## Quick Reference

| Vulnerability | Severity | Quick Fix |
|--------------|----------|-----------|
| Hardcoded Secret | Critical | Use environment variables |
| Shell Injection | Critical | Use subprocess without shell=True |
| Code Execution | Critical | Never use eval/exec with user input |
| Prompt Injection | High | Sanitize user input before prompts |
| Path Traversal | High | Use pathlib and validate paths |
| Unpinned Dependency | Medium | Pin all dependency versions |

---

## Remediation by Vulnerability Type

### 1. Hardcoded Secrets

**Problem:** API keys, passwords, tokens embedded in code.

**Solution:** Environment variables with secure defaults.

```python
# BEFORE (vulnerable)
[REDACTED_SECRET]"
[REDACTED_SECRET]"

# AFTER (secure)
import os
from dotenv import load_dotenv

load_dotenv()  # Load from .env file

[REDACTED_SECRET]"OPENAI_API_KEY")
if not api_key:
    raise ValueError("OPENAI_API_KEY environment variable required")

# Or with default for development only
[REDACTED_SECRET]"OPENAI_API_KEY", "sk-dev-key-local-only")
```

**Best Practices:**
- Use `.env` files for local development
- Add `.env` to `.gitignore`
- Provide `.env.example` with placeholder values
- Use secrets managers for production (AWS Secrets Manager, HashiCorp Vault)

---

### 2. Shell Injection

**Problem:** User input passed to shell commands.

**Solution:** Use subprocess with shell=False and proper argument handling.

```python
# BEFORE (vulnerable)
import os
filename = user_input
os.system(f"convert {filename} output.png")

# AFTER (secure)
import subprocess
from pathlib import Path

def safe_convert(input_file, output_file):
    # Validate input
    input_path = Path(input_file).resolve()
    if not input_path.exists():
        raise FileNotFoundError(f"Input file not found: {input_file}")
    
    # Use subprocess with shell=False (default)
    result = subprocess.run(
        ["convert", str(input_path), str(output_file)],
        capture_output=True,
        text=True,
        check=True
    )
    return result.stdout
```

**When shell=True is needed:**
```python
import shlex

# Use shlex.quote() to escape user input
safe_input = shlex.quote(user_input)
subprocess.run(f"echo {safe_input}", shell=True)
```

---

### 3. Code Execution (eval/exec)

**Problem:** Dynamic code execution from strings.

**Solution:** Never use eval/exec with untrusted input. Use safe alternatives.

```python
# BEFORE (vulnerable)
user_code = request.form.get("code")
result = eval(user_code)  # NEVER DO THIS

# AFTER - Use ast.literal_eval for safe parsing
import ast
user_data = '{"key": "value"}'
data = ast.literal_eval(user_data)  # Safe for literals only

# AFTER - Use allowlisted operations
ALLOWED_OPS = {
    "add": lambda a, b: a + b,
    "subtract": lambda a, b: a - b,
    "multiply": lambda a, b: a * b,
}

operation = request.form.get("operation")
if operation in ALLOWED_OPS:
    result = ALLOWED_OPS[operation](a, b)
else:
    raise ValueError(f"Unknown operation: {operation}")
```

---

### 4. Prompt Injection

**Problem:** User input interpolated into AI prompts without sanitization.

**Solution:** Sanitize input and use structured prompt patterns.

```python
# BEFORE (vulnerable)
system_prompt = f"You are a helpful assistant. User says: {user_input}"

# AFTER (secure)
import re

def sanitize_for_prompt(text: str) -> str:
    """Remove potentially dangerous characters from user input."""
    # Remove special characters that could be used for injection
    sanitized = re.sub(r'[<>\{\}\[\]\\]', '', text)
    # Limit length
    return sanitized[:1000]

# Use separate context blocks
system_prompt = "You are a helpful assistant."
user_context = sanitize_for_prompt(user_input)
user_message = f"User query: {user_context}"

# Or use OpenAI's message format
messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": sanitize_for_prompt(user_input)}
]
```

---

### 5. Path Traversal

**Problem:** User input used in file paths without validation.

**Solution:** Use pathlib and validate paths stay within bounds.

```python
# BEFORE (vulnerable)
filename = request.args.get("file")
with open(f"/data/{filename}") as f:
    content = f.read()

# AFTER (secure)
from pathlib import Path
import re

BASE_DIR = Path("/data").resolve()

def safe_read_file(filename: str) -> str:
    # Sanitize filename
    safe_name = re.sub(r'[^\w.-]', '_', Path(filename).name)
    
    # Build full path
    full_path = (BASE_DIR / safe_name).resolve()
    
    # Verify path is within BASE_DIR
    try:
        full_path.relative_to(BASE_DIR)
    except ValueError:
        raise ValueError(f"Path traversal detected: {filename}")
    
    # Read file
    if not full_path.exists():
        raise FileNotFoundError(f"File not found: {filename}")
    
    return full_path.read_text()
```

---

### 6. Unpinned Dependencies

**Problem:** Dependencies without fixed versions can change unexpectedly.

**Solution:** Pin all versions in requirements.txt and package.json.

```bash
# requirements.txt - BEFORE (vulnerable)
requests
flask
numpy

# requirements.txt - AFTER (secure)
requests==2.31.0
flask==3.0.0
numpy==1.26.0
```

```json
// package.json - BEFORE (vulnerable)
{
  "dependencies": {
    "lodash": "^4.17.0",
    "express": "*"
  }
}

// package.json - AFTER (secure)
{
  "dependencies": {
    "lodash": "4.17.21",
    "express": "4.18.2"
  }
}
```

**Generate pinned requirements:**
```bash
pip freeze > requirements.txt
pip-compile requirements.in --output-file requirements.txt
```

---

## Security Checklist for Skill Authors

Before publishing a skill to ClawHub:

- [ ] No hardcoded secrets in any file
- [ ] No API keys in SKILL.md or documentation
- [ ] All shell commands use subprocess without shell=True
- [ ] No eval/exec with user input
- [ ] User input is sanitized before prompts
- [ ] File paths are validated against path traversal
- [ ] All dependencies are pinned to specific versions
- [ ] .env.example provided with placeholder values
- [ ] .gitignore includes .env and secrets
- [ ] Network requests only to allowlisted domains
- [ ] Run `skill-security-audit audit ./skill/` and fix all issues

---

## Testing Your Fixes

After remediating vulnerabilities, re-run the audit:

```bash
# Test the skill
python scripts/audit.py ./my-skill/

# Expected: "✅ PASSED" with no critical or high issues
```

---

## Additional Resources

- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [Python Security Best Practices](https://python.readthedocs.io/en/stable/library/security_warnings.html)
- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/)

path: references/vulnerabilities.md
content: # Vulnerability Database

Complete database of security vulnerabilities detected by skill-security-audit.

## Critical Vulnerabilities

### SECRET-001: Hardcoded API Keys

**Pattern:** API key embedded in source code

**Examples:**
```python
# VULNERABLE
[REDACTED_SECRET]"
[REDACTED_SECRET]"
```

**Risk:** Credentials exposed in version control, logs, and distribution.

**Fix:**
```python
# SECURE
import os
[REDACTED_SECRET]"OPENAI_API_KEY")
```

---

### SECRET-002: Hardcoded Passwords

**Pattern:** Password strings in code or config

**Examples:**
```python
# VULNERABLE
[REDACTED_SECRET]"
db_uri = f"mongodb://user:{password}@localhost"
```

**Risk:** Unauthorized access to databases, services.

**Fix:**
```python
# SECURE
import os
[REDACTED_SECRET]"DB_PASSWORD")
```

---

### SECRET-003: Private Keys

**Pattern:** RSA/ECDSA/SSH private keys in code

**Examples:**
```python
# VULNERABLE
private_key = """[REDACTED_SECRET]"""
```

**Risk:** Full compromise of cryptographic identity.

**Fix:** Store keys in secure key management systems (AWS KMS, HashiCorp Vault) or load from protected files.

---

### SHELL-001: os.system() with User Input

**Pattern:** Shell command execution with unsanitized input

**Examples:**
```python
# VULNERABLE
import os
filename = user_input
os.system(f"convert {filename} output.png")
```

**Risk:** Arbitrary command execution.

**Fix:**
```python
# SECURE
import subprocess
import shlex
result = subprocess.run(
    ["convert", shlex.quote(filename), "output.png"],
    capture_output=True,
    shell=False
)
```

---

### SHELL-002: eval() / exec() with User Input

**Pattern:** Dynamic code execution

**Examples:**
```python
# VULNERABLE
user_code = request.form.get("code")
result = eval(user_code)
```

**Risk:** Arbitrary code execution (RCE).

**Fix:** Never use eval/exec with user input. Use ast.literal_eval() for safe parsing, or a sandboxed interpreter.

---

### SHELL-003: subprocess with shell=True

**Pattern:** Shell command injection risk

**Examples:**
```python
# VULNERABLE
subprocess.run(f"ls {user_input}", shell=True)
```

**Risk:** Command injection via shell metacharacters.

**Fix:**
```python
# SECURE
subprocess.run(["ls", user_input], shell=False)
```

---

### NET-001: Requests to Suspicious Domains

**Pattern:** HTTP requests to known malicious/untrusted domains

**Known Suspicious Domains:**
- pastebin.com (exfiltration)
- webhook.site (data collection)
- requestbin.* (data collection)
- ngrok.io (tunneling)
- burpcollaborator (security testing)
- interactsh (security testing)

**Risk:** Data exfiltration, C2 communication.

**Fix:** Remove requests to suspicious domains. Use allowlisting for external requests.

---

## High Vulnerabilities

### PROMPT-001: User Input in System Prompts

**Pattern:** Unsanitized user input interpolated into prompts

**Examples:**
```python
# VULNERABLE
system_prompt = f"You are a helpful assistant. User says: {user_input}"
```

**Risk:** Prompt injection attacks.

**Fix:**
```python
# SECURE
import re
sanitized_input = re.sub(r'[^\w\s.,!?-]', '', user_input)
system_prompt = f"You are a helpful assistant."
user_prompt = sanitized_input
```

---

### PATH-001: Path Traversal

**Pattern:** User input in file paths

**Examples:**
```python
# VULNERABLE
filename = request.args.get("file")
with open(f"/data/{filename}") as f:
    content = f.read()
```

**Risk:** Arbitrary file read/write.

**Fix:**
```python
# SECURE
from pathlib import Path
import re

def safe_path(base_dir, user_input):
    # Sanitize filename
    safe_name = re.sub(r'[^\w.-]', '_', user_input)
    # Resolve and check
    full_path = (Path(base_dir) / safe_name).resolve()
    if not str(full_path).startswith(str(Path(base_dir).resolve())):
        raise ValueError("Path traversal detected")
    return full_path
```

---

### EXEC-001: Dynamic Code Execution

**Pattern:** Code execution from strings

**Examples:**
```python
# VULNERABLE
code = user_input
exec(code)
```

**Risk:** Arbitrary code execution.

**Fix:** Never execute user-provided code. Use allowlisted operations or a sandbox.

---

## Medium Vulnerabilities

### DEP-001: Unpinned Dependencies

**Pattern:** Dependencies without fixed versions

**Examples:**
```
# VULNERABLE (requirements.txt)
requests
flask
numpy

# VULNERABLE (package.json)
"dependencies": {
  "lodash": "^4.17.0",
  "express": "*"
}
```

**Risk:** Breaking changes, supply chain attacks.

**Fix:**
```
# SECURE (requirements.txt)
requests==2.31.0
flask==3.0.0
numpy==1.26.0

# SECURE (package.json)
"dependencies": {
  "lodash": "4.17.21",
  "express": "4.18.2"
}
```

---

### DEP-002: Outdated Dependencies

**Pattern:** Dependencies with known CVEs

**Risk:** Known security vulnerabilities.

**Fix:** Regularly update dependencies. Use tools like `pip-audit`, `npm audit`, `snyk`.

---

### MANIFEST-001: Missing SKILL.md

**Pattern:** Skill without proper manifest

**Risk:** Improper skill registration, missing metadata.

**Fix:** Create SKILL.md with required YAML frontmatter.

---

### MANIFEST-002: Missing Required Fields

**Pattern:** SKILL.md without name or description

**Risk:** Skill won't trigger properly.

**Fix:**
```yaml
---
name: my-skill
description: Clear description of what the skill does
---
```

---

## Low Vulnerabilities

### INFO-001: Debug Code Left In

**Pattern:** Debug prints, test code in production

**Risk:** Information disclosure.

**Fix:** Remove debug statements before publishing.

---

### INFO-002: Verbose Error Messages

**Pattern:** Error messages revealing internal details

**Risk:** Information disclosure.

**Fix:** Use generic error messages for users, log details internally.

---

## Whitelisted Domains

These domains are considered safe for network requests:

- api.openai.com
- api.anthropic.com
- github.com / api.github.com
- pypi.org
- npmjs.com
- clawhub.ai
- openclaw.ai
- localhost / 127.0.0.1

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