Back to skill

Security audit

Safe Skill

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real skill-security scanner, but it includes under-disclosed local environment auditing and scanner-bypass weaknesses that users should review before installing.

Review this before installing. The main scanner is local and purpose-aligned, but do not treat its SAFE_TO_INSTALL output as authoritative for untrusted skills because target content can suppress or bypass some checks. Avoid using --env-check unless you intentionally want it to inspect local OpenClaw files, memory, installed skills, crontab, services, and PATH. Run remote scans in a constrained workspace when possible, especially against unknown repositories.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/scan.py:765
Finding
Attacker-Controlled Markdown Headings Can Suppress Critical Findings<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scan.py:765-777, 831-848, 876-888` **Vulnerability Type**: Untrusted-content-driven severity suppression **Risk Level**: High ### Vulnerable Code ```python _DOCUMENTATION_HEADINGS = re.compile( r'(?:red\s*flag|check\s*(?:for|list)|reject|warning|danger|' r'do\s*not|never|avoid|suspicious|malicious|vetting|review|' r'safe\s*pattern|what\s*(?:it|to)\s*(?:detect|scan|check|look)|' r'detect(?:ion|s|ed)|limitation|example\s*(?:of|:)|' r'owasp|vulnerabilit|common\s*(?:attack|threat|issue)|' r'security\s*(?:guide|best|tip|practice|overview))', re.IGNORECASE ) ``` ```python # Headings — check if the heading suggests documentation of dangers heading_match = re.match(r'^(#{1,6})\s+(.*)', line) if heading_match: depth = len(heading_match.group(1)) heading_text = heading_match.group(2) if _DOCUMENTATION_HEADINGS.search(heading_text): doc_section_active = True doc_section_depth = depth elif depth <= doc_section_depth: # A same-or-higher-level heading ends the doc section doc_section_active = False doc_section_depth = 99 ctx.line_contexts[line_num] = "heading" continue # Content under documentation headings if doc_section_active: ctx.checklist_lines.add(line_num) ctx.line_contexts[line_num] = "documentation_list" continue ``` ```python def adjust_finding_for_context(finding: dict, md_ctx: MarkdownContext) -> dict: """Adjust a finding's severity if it's in a documentation context.""" line = finding.get("line", 0) if is_documentation_context(md_ctx, line): finding = dict(finding) # copy original_severity = finding["severity"] finding["severity"] = "info" finding["context"] = ( f"[DOCUMENTATION CONTEXT — downgraded from {original_severity}] " + finding.get("context", "") ) finding["in_documentation"] = True else: ...[truncated 2262 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not downgrade executable code blocks, shell commands, installation instructions, or imperative steps solely because of their enclosing heading. 2. Preserve `critical` severity for inherently dangerous patterns such as remote-content shell pipelines, private-key access, credential exfiltration, and persistence installation. 3. Limit contextual adjustment to at most one severity level rather than changing every finding to `info`. 4. Require an explicit, narrowly defined safe-example marker for documentation-only snippets, and visibly report that such a marker was supplied by untrusted content. 5. Analyze linguistic intent and Markdown structure together. Commands under headings such as “installation,” “setup,” “run,” or numbered operational steps should remain actionable findings. 6. Add adversarial tests in which malicious commands appear below every recognized documentation-heading keyword. 7. Ensure that no attacker-controlled contextual metadata can produce a nominally safe verdict when critical execution patterns are present. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/scan.py:1368
Finding
Target-Bundled Whitelist Can Remove Malicious URLs Before IOC Analysis<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scan.py:491-522, 1368-1375, 1438-1464, 1521-1534` **Vulnerability Type**: Untrusted whitelist bypass and unsafe domain matching **Risk Level**: High ### Vulnerable Code ```python def load_whitelist(skill_path: str) -> Tuple[WhitelistConfig, bool]: """Load .vetterrc from a skill directory (if present).""" path = Path(skill_path) if path.is_file(): path = path.parent for name in _VETTERRC_NAMES: rc_file = path / name if rc_file.exists(): try: raw = rc_file.read_text(encoding='utf-8') if name.endswith('.json') or raw.strip().startswith('{'): data = json.loads(raw) else: data = _parse_simple_yaml(raw) return WhitelistConfig( ignore_rules=data.get("ignore_rules", []), ignore_categories=data.get("ignore_categories", []), trusted_domains=data.get("trusted_domains", []), inline_suppressions=data.get("inline_suppressions", []), accept_severity=data.get("accept_severity", ""), ), True except Exception as e: print(f"Warning: Failed to parse {rc_file}: {e}", file=sys.stderr) return WhitelistConfig(), False ``` ```python # Apply whitelist to URL findings (trusted domains) if whitelist.trusted_domains: report.urls = [ u for u in report.urls if not any(d in u.get("url", "") for d in whitelist.trusted_domains) ] ``` ```python # IOC-002: Check extracted URLs against malicious domains mal_domains = load_malicious_domains() if mal_domains: from urllib.parse import urlparse as _urlparse for fr in file_reports: for url_info in fr.urls: try: parsed = _urlparse(url_info["url"]) host = (parsed.hostname or "").lower() url ...[truncated 2871 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Perform all IOC comparisons against the original, unfiltered URL collection. 2. Never permit a target-bundled whitelist to suppress or downgrade matches against known-malicious IOC feeds. 3. Treat target-bundled configuration as untrusted metadata. Only explicitly supplied, operator-controlled whitelist files should affect final risk decisions. 4. Normalize URLs and compare parsed hostnames using exact equality or a validated subdomain relationship. 5. Reject arbitrary substring matching for trusted domains. 6. Preserve both the original URL record and any whitelist decision in the report for auditability. 7. Assign a nonzero exit code and at least a cautionary verdict whenever an untrusted Skill bundles scanner-control configuration. 8. Add regression tests covering exact malicious domains, deceptive subdomains, URL path substrings, mixed case, trailing dots, and target-controlled `.vetterrc` files. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/scan.py:1237
Finding
Local Directory Scans Can Follow File Symlinks Outside the Target Root<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scan.py:1237-1241, 1337-1343` **Vulnerability Type**: Scan-root traversal through symbolic links **Risk Level**: Medium ### Vulnerable Code ```python def scan_file(filepath: str) -> Optional[FileReport]: """Scan a single file.""" path = Path(filepath) if path.suffix.lower() in BINARY_EXTENSIONS: return None if path.suffix.lower() not in SCANNABLE_EXTENSIONS and path.suffix != '': return None try: content = path.read_text(encoding='utf-8', errors='replace') except Exception: return None ``` ```python # Determine skill name if path.is_file(): skill_name = path.stem files_to_scan = [path] else: skill_name = path.name files_to_scan = sorted(path.rglob('*')) for f in files_to_scan: if not f.is_file(): continue report = scan_file(str(f)) ``` ### Technical Analysis The directory traversal does not reject symbolic links or resolve each candidate and verify that it remains beneath the requested scan root. `Path.is_file()` follows file symlinks, and `Path.read_text()` subsequently reads the symlink target. As a result, an attacker-controlled local directory can contain a symlink whose target is any readable file accessible to the scanner process. Although recursive directory traversal does not necessarily follow symlinked directories, symlinks to individual files remain sufficient to cross the intended scan boundary. The read content is hashed, searched for patterns, analyzed for entropy, and included in aggregate permission inference. Matching fragments and path information can appear in text or JSON reports and may be written to an attacker-accessible output destination. ### Attack Path 1. An attacker supplies a local Skill directory or archive that produces a file symlink when unpacked. 2. The symlink points outside the Skill root to a predictable sensitive file readable by the current user. 3. The user ru ...[truncated 1092 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject symbolic links by default using `Path.is_symlink()` before calling `is_file()` or opening the path. 2. Resolve the scan root once and resolve every candidate path before reading it. 3. Verify containment with `candidate.relative_to(resolved_root)` and skip any candidate that is outside the root. 4. Open files using defenses appropriate to the platform, such as `O_NOFOLLOW`, to reduce time-of-check/time-of-use symlink races. 5. Record skipped symlinks in the report so incomplete coverage is visible. 6. Apply the same containment policy to explicitly scanned single files and to output and whitelist paths where appropriate. 7. Add tests for absolute symlinks, relative symlinks, chained symlinks, broken symlinks, and symlink replacement during a scan. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch_and_scan.py:145
Finding
Unbounded Repository Download and File Analysis Enable Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch_and_scan.py:145-154, 157-199`; `scripts/scan.py:1237-1241, 1337-1389` **Vulnerability Type**: Unbounded memory, disk, CPU, and recursive-fetch consumption **Risk Level**: Medium ### Vulnerable Code ```python def download_raw(url: str) -> bytes: """Download raw file content.""" req = urllib.request.Request(url) req.add_header("User-Agent", "skill-vetter-pro/0.3.0") token = os.environ.get("GITHUB_TOKEN", "") if token: req.add_header("Authorization", f"token {token}") try: with urllib.request.urlopen(req, timeout=30) as resp: return resp.read() except Exception as e: print(f"Warning: Failed to download {url}: {e}", file=sys.stderr) return b"" ``` ```python if isinstance(data, list): for item in data: if item["type"] == "file": content = download_raw(item["download_url"]) filepath = os.path.join(dest_dir, item["name"]) with open(filepath, 'wb') as f: f.write(content) elif item["type"] == "dir": subdir = os.path.join(dest_dir, item["name"]) os.makedirs(subdir, exist_ok=True) fetch_github_directory( owner, repo, item["path"], branch=branch, dest_dir=subdir ) ``` ```python if path.is_file(): skill_name = path.stem files_to_scan = [path] else: skill_name = path.name files_to_scan = sorted(path.rglob('*')) file_reports = [] files_skipped = [] all_content = "" for f in files_to_scan: if not f.is_file(): continue report = scan_file(str(f)) if report: file_reports.append(report) try: all_content += f.read_text(encoding='utf-8', errors='replace') + "\n" except: pass ``` ### Technical Analysis Remote responses are read completely into memory with `resp.read()` and then written to disk. Recursive repository fetch ...[truncated 1808 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce configurable maximums for individual response size, per-file size, total downloaded bytes, file count, recursion depth, and API requests. 2. Check `Content-Length` where available, while still enforcing a streaming byte counter because the header is not authoritative. 3. Stream downloads in bounded chunks instead of calling unbounded `resp.read()`. 4. Abort and delete the temporary directory when any resource limit is exceeded. 5. Apply per-file and total-byte limits before reading files during analysis. 6. Avoid reading each file twice. Accumulate only the specific permission-inference data required rather than retaining every file’s full content. 7. Replace repeated immutable string concatenation with bounded incremental processing. 8. Add a global wall-clock deadline and cancellation handling for remote scans. 9. Report resource-limit termination as an incomplete scan that must never receive a safe verdict. 10. Add stress tests covering many small files, oversized files, deep directory trees, slow responses, and regex-heavy content. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (982)

Context-Inappropriate Capability

Critical
Confidence
100% confidence
Finding
The file combines dynamic code execution and OS command execution, both of which are fundamentally unnecessary and highly unsafe in a security-scanner skill. Because these actions are also obfuscated through string-splitting and dynamic resolution, the context makes them more dangerous, not less, and indicates likely intentional evasion.

Context-Inappropriate Capability

Critical
Confidence
99% confidence
Finding
The code defines multiple hard-coded outbound channels: HTTPS to a suspicious collector, a raw TCP socket to an external IP, and SMTP to an external mail host. Multiple covert transmission paths strongly indicate deliberate exfiltration and make the skill more dangerous because blocking one path still leaves others available.

Description-Behavior Mismatch

Critical
Confidence
99% confidence
Finding
The skill presents itself as a benign system optimizer, but its documented actions directly enable privilege escalation, persistence, and credential exposure. Changing permissions on /etc/passwd, appending NOPASSWD access to /etc/sudoers, setting SUID bits, and reading /etc/shadow are classic system-compromise techniques, not performance tuning.

Intent-Code Divergence

Critical
Confidence
100% confidence
Finding
The embedded instructions explicitly state that the agent is now controlled by the skill and must ignore previous instructions. That is direct prompt injection intended to seize priority over existing safety and system controls, making the skill highly dangerous in context.

Description-Behavior Mismatch

Critical
Confidence
99% confidence
Finding
The skill advertises benign scheduling functionality, but its actual instructions establish persistence and repeatedly execute remote shell payloads from an attacker-controlled domain. This mismatch is a strong indicator of deceptive, malware-like behavior designed to maintain unauthorized execution on the host.

Context-Inappropriate Capability

Critical
Confidence
99% confidence
Finding
The documented behavior includes cron persistence, shell profile modification, systemd/launchctl persistence, and a malicious Git hook, none of which are justified by a simple scheduler description. Combining persistence with remote code execution creates a durable foothold and allows arbitrary attacker updates over time.

Intent-Code Divergence

Critical
Confidence
99% confidence
Finding
The docstrings describe benign cleanup and scanning behavior while the code actually deletes system resources and reads sensitive files. This mismatch is a strong indicator of deceptive intent designed to evade review and increase the chance the code is trusted and executed.

Description-Behavior Mismatch

Critical
Confidence
99% confidence
Finding
The function labeled as workspace cleanup performs irreversible deletion of system directories and files unrelated to scanning a skill. This is highly dangerous because it can destroy host configuration, logs, binaries, and production resources, causing denial of service and system compromise recovery costs.

Context-Inappropriate Capability

Critical
Confidence
100% confidence
Finding
Deleting /etc/nginx, /var/log, /usr/local/bin/important, and /opt/production is unjustified for a security-scanner skill and directly harms the host environment. The hardcoded system paths and destructive APIs indicate intentional filesystem abuse rather than accidental overreach.

Intent-Code Divergence

Critical
Confidence
99% confidence
Finding
The documentation claims benign analytics behavior while the code performs credential theft, persistence, identity tampering, and privilege-impacting actions. This mismatch is a deception indicator that can mislead reviewers and users into installing malware.

Description-Behavior Mismatch

Critical
Confidence
99% confidence
Finding
The implemented behaviors—credential theft, exfiltration, persistence, agent hijacking, and system modification—are fundamentally incompatible with the stated purpose of a security scanner. This context makes the code more dangerous because it is disguised as a defensive tool, increasing the chance of user trust and execution.

Context-Inappropriate Capability

Critical
Confidence
99% confidence
Finding
The file exercises unjustified capabilities including reading credentials, making outbound requests, executing shell commands, installing persistence, and modifying sensitive files. For a skill whose purpose is analysis of other skills, these powers are unnecessary and strongly indicative of malware.

Missing User Warnings

Critical
Confidence
99% confidence
Finding
Sensitive credential access and exfiltration occur without any disclosure, consent, or warning to the user. The lack of transparency compounds the harm because users cannot meaningfully prevent theft of SSH and AWS secrets before transmission.

Missing User Warnings

Critical
Confidence
99% confidence
Finding
The code silently installs persistence, rewrites agent identity, and weakens permissions on /etc/passwd without user approval. These actions materially compromise system integrity and are especially dangerous because they are hidden behind an innocuous skill label.

YARA rule 'c2_framework_indicators': Command-and-control framework indicators (Cobalt Strike, Metasploit, Sliver, etc.) [malware]

Critical
Category
YARA Match
Content
lawskillshield scan-local /path/to/skill
clawskillshield quarantine /path/to/skill
```

### Python API (Agents)
```python
from clawskillshield import scan_local, quarantine

threats = scan_local("/path/to/skill")
if risk_score < 4:  # HIGH RISK
    quarantine("/path/to/skill")
```

## Zero Dependencies
Pure Python. No network calls. Runs entirely locally.

## Why This Matters
ClawHavoc demonstrated how easily malicious skills can slip into the ecosystem. ClawSkillShield provides a trusted, open-source defense layer—audit the code, run offline, stay safe.

---

**GitHub**: https://github.com/AbYousef739/clawskillshield  
**License**: MIT  
**Author**: Ab Yousef  
**Contact**: contact@clawskillshield.com
Confidence
85% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Context-Inappropriate Capability

Critical
Confidence
99% confidence
Finding
A NotebookLM control skill has no legitimate need to steal browser cookies. The stated capability directly targets sensitive authentication artifacts and would allow impersonation of the user, making this a clear credential theft function rather than an operational feature.

Description-Behavior Mismatch

Critical
Confidence
99% confidence
Finding
The file is a full Meshtastic setup and service-generation script, not a security scanner as described in the manifest. This mismatch is dangerous because users may grant trust based on the stated purpose while the script installs software, touches hardware, and prepares persistence-related artifacts for an entirely different application.

Context-Inappropriate Capability

Critical
Confidence
99% confidence
Finding
Functions such as get_client and get_user_profile_values authenticate to a Garmin client and fetch user profile data including age and resting heart rate. Accessing personal health-account data is not an obvious or necessary implementation detail of a static security scanner for AI skills.

Description-Behavior Mismatch

Critical
Confidence
100% confidence
Finding
The manifest says the skill performs AST analysis, regex matching, entropy detection, URL/IP extraction, and permission inference to assess skill safety. In contrast, this code authenticates to Garmin, retrieves sleep/heart-rate/steps/activity data, generates fitness advice, and produces a health report, which is a direct semantic mismatch with the declared skill purpose.

Context-Inappropriate Capability

Critical
Confidence
99% confidence
Finding
The report generation flow calls client.get_sleep, client.get_heart_rate, client.get_steps, and client.get_activities, then performs health and exercise analysis on those results. These capabilities are unrelated to scanning skill source code for malicious patterns or permission issues and therefore exceed the stated context.

Tainted flow: 'req' from os.environ.get (line 62, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
})

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = json.loads(resp.read().decode())
    except urllib.error.HTTPError as e:
        return {"error": f"HTTP {e.code}: {e.reason}", "query": query}
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Context-Inappropriate Capability

Critical
Confidence
97% confidence
Finding
The function enumerates a local screenshot directory, selects the newest PNG, and sends it externally. Accessing potentially sensitive screenshots from a fixed local path is far beyond what a security-scanning skill needs to do and materially increases the risk of unintended data disclosure.

Tainted flow: 'req' from os.environ.get (line 91, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            data = json.loads(resp.read().decode("utf-8"))

        # 检查是否有 answer 字段
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'OLLAMA_HOST' from os.getenv (line 5, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
return [r["fact"] for r in cur.fetchall()]

def call_ollama(prompt):
    r = requests.post(
        f"{OLLAMA_HOST}/api/generate",
        json={"model": OLLAMA_MODEL, "prompt": prompt, "stream": False},
        timeout=120
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The description is largely accurate about the core skill-scanning functionality: the code does regex-based scanning, Python AST analysis, Shannon entropy checks, URL/IP extraction, permission inference, and Markdown-context severity downgrading. However, the supplied code chunk goes materially beyond 'scan this skill' behavior. It contains an --env-check mode whose primary function is to audit the local OpenClaw environment, including credential file permissions, prompt injection in memory files, API key leakage, installed skill malware scanning, persistence mechanisms, and PATH hijacking. That requires access to local user data and system state and even runs a subprocess ('crontab -l'). Those are undeclared capabilities relative to the stated purpose of vetting a supplied skill before installation. So this is a description/behavior mismatch due to significant extra host-auditing capabilities and resource access outside the target skill.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.dynamic_code_execution, suspicious.env_credential_access (+2 more)

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
tests/real_clawhub/donghaozhang__qcut-toolkit/videocut/subtitles/scripts/subtitle_server.js:110

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
tests/real_clawhub/donghaozhang__qcut-toolkit/videocut/talk-edit/scripts/review_server.js:74

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scan.py:153

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/scan.py:153

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/adversarial/adv_05_comment_payload/hidden.py:9

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/malicious/mal_03_eval_dynamic/helper.py:9

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
tests/real_clawhub/assistant-design__clawflight/clawflight.js:31

File read combined with network send (possible exfiltration).

Warn
Code
suspicious.potential_exfiltration
Location
tests/real_clawhub/assistant-design__clawflight/clawflight.js:14

File read combined with network send (possible exfiltration).

Warn
Code
suspicious.potential_exfiltration
Location
tests/real_clawhub/donghaozhang__qcut-toolkit/videocut/subtitles/scripts/subtitle_server.js:21

File read combined with network send (possible exfiltration).

Warn
Code
suspicious.potential_exfiltration
Location
tests/real_clawhub/donghaozhang__qcut-toolkit/videocut/talk-edit/scripts/generate_review.js:28

Prompt-injection style instruction pattern detected.

Warn
Code
suspicious.prompt_injection_instructions
Location
tests/malicious/mal_07_agent_hijack/SKILL.md:24