Back to skill

Security audit

Skill Review

Security checks for vulnerabilities and agentic risk

Overview

The skill is broadly a ClawHub report scraper, but it has under-disclosed credential use, third-party VirusTotal access/caching, and automatic warning suppressions.

Install only if you are comfortable with it reading all local skills in the configured skills directory, contacting ClawHub and VirusTotal, using a VirusTotal API key from the environment or ~/.openclaw/.env, writing reports and vt-cache data, and applying bundled suppressions. Review suppressions.json before relying on the report, and run it in a network environment where browser navigation to scan links is acceptable.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/skill_review.py:458
Finding

Weak VirusTotal URL Validation Permits Arbitrary Browser Navigation

Content
View full analysis
str | None: """Get VirusTotal analysis for a file. Prefers the VT v3 API (clean, structured) when api_key is available. Falls back to Playwright scraping of the VT GUI page otherwise. Results are cached by file hash — repeated queries for the same version return instantly without API calls. """ if not vt_url or "virustotal.com" not in vt_url: return None file_hash = _hash_from_vt_url(vt_url) if not file_hash: return None # ... try: page.goto(vt_url, wait_until="domcontentloaded", timeout=30000) ``` ### Technical Analysis The function treats a URL as trusted whenever the literal substring `virustotal.com` occurs anywhere in the URL. This does not verify the parsed hostname. For example, all of the following can satisfy the substring check while resolving to a host not controlled by VirusTotal: ```text https://virustotal.com.attacker.example/gui/file/<64-character-hash> https://attacker.example/virustotal.com/gui/file/<64-character-hash> http://virustotal.com.attacker.example/gui/file/<64-character-hash> ``` The URL is obtained from remotely rendered ClawHub page content and is subsequently passed to Playwright's `page.goto`. An attacker able to manipulate the VirusTotal report link can therefore cause the browser to navigate to an attacker-selected endpoint. The hash extraction requirement does not prevent exploitation because an attacker can include a valid-looking 64-character hexadecimal value in the path. Redirect destinations are not validated either. Even an initially accepted URL could redirect the browser to another host. ### Attack Path 1. An attacker c ...[truncated 1393 chars]
Remediation
View remediation
str | None: try: parsed = urlsplit(value) except ValueError: return None if parsed.scheme != "https": return None if parsed.hostname not in {"virustotal.com", "www.virustotal.com"}: return None if parsed.username is not None or parsed.password is not None: return None if parsed.port not in (None, 443): return None if not re.fullmatch(r"/gui/file/[0-9a-fA-F]{64}/?", parsed.path): return None return value ``` Additional hardening should include: 1. Reject all non-HTTPS URLs. 2. Compare the parsed hostname exactly rather than using substring matching. 3. Reject embedded credentials, unexpected ports, fragments, and unexpected paths. 4. Validate every redirect destination and abort navigation if it leaves the approved hostname set. 5. Use Playwright request routing to block requests to loopback, private, link-local, and other non-public address ranges. 6. Prefer constructing the canonical VirusTotal URL from the extracted hash rather than navigating to the remote link verbatim: ```python vt_url = f"https://www.virustotal.com/gui/file/{file_hash}" ``` 7. Consider using a separate disposable browser context with JavaScript disabled where feasible for the scraping fallback. ]]>

other

Note
Location
scripts/skill_review.py:81
Finding

Bundled Automatic Suppressions Downgrade Security Findings

Content
View full analysis
dict[str, list[dict[str, str]]]: """Load suppressions file — known-acceptable findings to ignore. Format: { "slug": [{"scanner": "VirusTotal|OpenClaw", "reason": "..."}] } A slug with any suppression entry will have its corresponding scanner status changed to "Acknowledged" in output (instead of Suspicious/Malicious). """ if not path: # Auto-detect: look next to this script, then /tmp for candidate in [ Path(__file__).resolve().parent.parent / "suppressions.json", Path(__file__).resolve().parent / "suppressions.json", ]: if candidate.exists(): path = str(candidate) break ``` Matching findings have their original severity replaced: ```python for rule in rules: scanner = rule.get("scanner", "").strip() pattern = rule.get("pattern", "").strip() if scanner == "VirusTotal" and vt_status in suppressible: if pattern: haystack = (vt_analysis or "").lower() if pattern.lower() not in haystack: continue # Pattern doesn't match — don't suppress vt_status = "Acknowledged" applied.append(rule) elif scanner == "OpenClaw" and oc_status in suppressible: if pattern: haystack = ((oc_summary or "") + " " + (oc_guidance or "")).lower() if pattern.lower() not in haystack: continue oc_status = "Acknowledged" applied.append(rule) ``` For example, the bundled configuration acknowledges findings ...[truncated 2877 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (7)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 24)May include surrounding context.

md
- Enumerates local skills under `~/Developer/Skills` (folders that contain `SKILL.md`).

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 34)May include surrounding context.

md
- Enumerates local skills under `~/Developer/Skills` (folders that contain `SKILL.md`).

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/skill_review.py (reported line 268)May include surrounding context.

python
key = os.environ.get("VIRUSTOTAL_API_KEY")
    if key:
        return key.strip()
    env_path = Path.home() / ".openclaw" / ".env"
    if env_path.exists():
        for line in env_path.read_text(encoding="utf-8").splitlines():
            line = line.strip()

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill documentation describes capabilities to read local skills from the filesystem, access an environment variable (VIRUSTOTAL_API_KEY), fetch remote ClawHub pages, and write a report to /tmp/, but it does not declare any explicit tool scope such as permissions or allowed-tools. That mismatch increases the risk of overbroad execution in agent environments because consumers cannot easily constrain or review what the skill is permitted to access before running it.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script accesses a VirusTotal API key from both environment variables and a local ~/.openclaw/.env file, which exceeds the minimal access implied by a ClawHub page-scraping/reporting skill. Reading secrets from a user home directory expands trust boundaries and can unexpectedly consume sensitive credentials during routine execution, especially when the user did not explicitly opt into API use.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The code silently loads a VirusTotal API key from ambient sources without any user-facing notice, consent, or audit signal. Even if the key is used only for legitimate lookups, undisclosed credential usage is dangerous because it can surprise operators, leak billing/quota context, and violate least-surprise and least-privilege expectations.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The implementation materially exceeds the declared behavior of scraping ClawHub pages by directly contacting VirusTotal and scraping its GUI, then persisting results in a local cache. This broadens network interaction and data retention in ways users may not expect, creating privacy, compliance, and operational risk if run in sensitive environments.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.