Back to skill

Security audit

Skill Audit Pro

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real local security scanner, but it goes to Review because it reads OpenClaw routing/session data and can expose recipient metadata while also scanning files too broadly.

Install only if you are comfortable with a scanner that can read installed/workspace skill files and OpenClaw session routing metadata. Prefer a version that removes the CHANNELS output, rejects symlinks, adds scan size/time limits, and makes scheduled/all-channel reporting explicit and opt-in.

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 (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/scan_and_report.py:45
Finding

Cross-Channel Disclosure of Recipient Metadata

Content
View full analysis

Vulnerability Details

File Location: scripts/scan_and_report.py:45-91, 233
Vulnerability Type: Sensitive metadata disclosure
Risk Level: Medium

Vulnerable Code

python
sessions_file = Path.home() / ".openclaw" / "agents" / "main" / "sessions" / "sessions.json"

if sessions_file.exists():
    try:
        with open(sessions_file) as f:
            sessions = json.load(f)

        for session_key, info in sessions.items():
            delivery = info.get('deliveryContext', {})
            channel = delivery.get('channel')
            to = delivery.get('to')

            if channel and to:
                entry = {'channel': channel, 'to': to}
                if entry not in channels:
                    channels.append(entry)
    except Exception as e:
        print(f"Warning: Could not read sessions: {e}", file=sys.stderr)

config_file = Path.home() / ".openclaw" / "openclaw.json"
if config_file.exists():
    try:
        with open(config_file) as f:
            config = json.load(f)

        channel_configs = config.get('channels', {})
        for channel_name, channel_cfg in channel_configs.items():
            if channel_cfg.get('enabled'):
                for session_key, info in sessions.items() if 'sessions' in dir() else []:
                    if info.get('lastChannel') == channel_name:
                        to = info.get('lastTo', '')
                        if to:
                            entry = {'channel': channel_name, 'to': to}
                            if entry not in channels:
                                channels.append(entry)
    except Exception as e:
        print(f"Warning: Could not read config: {e}", file=sys.stderr)
python
print(f"\n<!-- CHANNELS: {json.dumps(channels)} -->")

Technical Analysis

The script reads delivery destinations from the OpenClaw session database and configuration, including c ...[truncated 1644 chars]

Remediation
View remediation

Remediation Suggestions

  • Remove the CHANNELS comment from standard output.
  • Do not enumerate all configured recipients merely to generate a scan report.
  • Delegate recipient selection to the trusted OpenClaw delivery layer.
  • If routing data is operationally necessary, pass only the current destination over a private IPC or structured control channel rather than embedding it in report content.
  • Minimize access to sessions.json and openclaw.json, and avoid retaining recipient identifiers longer than required.
  • Add a regression test confirming that generated reports and logs contain no channel or recipient metadata.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/skill_audit.py:220
Finding

Scan-Root Escape Through Symbolic Links

Content
View full analysis

Vulnerability Details

File Location: scripts/skill_audit.py:220-225
Vulnerability Type: Unrestricted file access through symbolic-link traversal
Risk Level: High

Vulnerable Code

python
else:
    # Scan directory
    for filepath in self.skill_path.rglob('*'):
        if filepath.is_file() and not filepath.name.startswith('.'):
            try:
                content = filepath.read_text(encoding='utf-8', errors='ignore')
                self.scan_file(filepath, content)
            except Exception:
                pass

Technical Analysis

The directory scanner recursively enumerates paths and reads every non-hidden file without rejecting symbolic links. It also does not resolve each candidate and verify that the resolved path remains below self.skill_path.

A malicious or untrusted Skill can place a symbolic link inside its package that points to any file readable by the scanner process. Path.is_file() follows symbolic links, and read_text() consequently reads the target outside the intended scan root.

The scanner stores up to 100 characters of every regex match in issues[*]["match"]. The bundled console formatter does not currently print that field, but callers receive the complete issue dictionaries and can expose the captured fragments through logs, APIs, reports, or future integrations.

Attack Path

  1. An attacker prepares a Skill directory containing a symbolic link to a sensitive file outside the Skill root.
  2. A user or automated installation process scans that directory.
  3. rglob('*') discovers the symbolic-link path.
  4. filepath.is_file() follows the link and accepts its target as a file.
  5. read_text() reads the external file with the scanner process's privileges.
  6. Security-pattern matches and fragments are placed in the returned issue objects.
  7. An integrating caller may log or transmit those results, disclosing parts of the external file. ...[truncated 480 chars]
Remediation
View remediation

Remediation Suggestions

  • Reject symbolic links before reading:
    python
    if filepath.is_symlink():
        continue
    
  • Resolve the scan root and every candidate, then enforce containment:
    python
    root = self.skill_path.resolve()
    candidate = filepath.resolve()
    if not candidate.is_relative_to(root):
        continue
    
  • Open files using operating-system protections that prevent following symbolic links where supported, such as O_NOFOLLOW.
  • Scan only regular files and explicitly reject devices, sockets, FIFOs, and other special file types.
  • Remove or redact the match field before returning findings to external callers.
  • Run the scanner under a dedicated, minimally privileged account without access to unrelated secrets.
  • Add tests involving links to files both inside and outside the scan root.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/skill_audit.py:198
Finding

Unbounded Processing of Untrusted Files and Archives

Content
View full analysis

Vulnerability Details

File Location: scripts/skill_audit.py:198-225
Vulnerability Type: Resource exhaustion and denial of service
Risk Level: Medium

Vulnerable Code

python
if self.skill_path.suffix == '.skill':
    with zipfile.ZipFile(self.skill_path, 'r') as zf:
        for name in zf.namelist():
            if name.endswith(('.py', '.sh', '.js', '.ts', '.yaml', '.yml', '.json', '.md')):
                try:
                    content = zf.read(name).decode('utf-8', errors='ignore')
                    # Create a fake Path object for the zip file
                    fake_path = Path(name)
                    self.scan_file(fake_path, content)
                except Exception:
                    pass
elif self.skill_path.is_file():
    # Scan a single file
    try:
        content = self.skill_path.read_text(encoding='utf-8', errors='ignore')
        self.scan_file(self.skill_path, content)
    except Exception:
        pass
else:
    # Scan directory
    for filepath in self.skill_path.rglob('*'):
        if filepath.is_file() and not filepath.name.startswith('.'):
            try:
                content = filepath.read_text(encoding='utf-8', errors='ignore')
                self.scan_file(filepath, content)
            except Exception:
                pass

Technical Analysis

Archive members and ordinary files are loaded completely into memory. The scanner imposes no limit on individual file size, total expanded size, archive member count, compression ratio, directory file count, or scan duration.

A crafted .skill ZIP archive can contain highly compressed data that expands to a very large size. Similarly, a directory can contain oversized files or very large numbers of files. After allocation and decoding, the scanner applies many regular expressions to each complete string, further increasing CPU and memory consumption.

Exception suppression d ...[truncated 1123 chars]

Remediation
View remediation

Remediation Suggestions

  • Enforce strict per-file, total expanded-size, and file-count limits before scanning.
  • Inspect ZIP metadata through ZipInfo.file_size and ZipInfo.compress_size; reject excessive expanded sizes and suspicious compression ratios.
  • Read files incrementally with a hard byte limit rather than using zf.read() or read_text() without bounds.
  • Skip unsupported and binary content using explicit allowlists and content checks.
  • Set an aggregate scan timeout and terminate scans that exceed CPU or wall-clock budgets.
  • Run scans in an isolated subprocess or sandbox with memory, CPU, file-descriptor, and execution-time limits.
  • Report skipped or rejected files explicitly instead of silently suppressing exceptions.
  • Add regression tests using oversized files, excessive member counts, and high-compression archives.
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
Findings (14)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The documentation promises automated periodic scanning, installed-skill discovery, and channel reporting, but the analyzed behavior reportedly does not implement those features. This mismatch is dangerous because users may make trust and deployment decisions based on false security claims, assuming they are continuously protected when they are not.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The documentation promises automated periodic scanning, installed-skill discovery, and channel reporting, but the analyzed behavior reportedly does not implement those features. This mismatch is dangerous because users may make trust and deployment decisions based on false security claims, assuming they are continuously protected when they are not.

Content

No source excerpt is available for this finding.

YARA rule 'python_webshell': Python webshell — exec/eval/os.popen on request input [webshells]

High
Category
YARA Match
Confidence
75% confidence
Finding

YARA rule matched a known webshell pattern (PHP, Python, JSP, or ASPX webshell).

Content

Scanner excerpt · references/security-checks.md (reported line 15)May include surrounding context.

md
ew

This skill scans OpenClaw skills for security vulnerabilities before installation. It performs static analysis on skill files and provides a risk assessment.

## Security Check Categories

### 🔴 CRITICAL - Block Installation

These issues will cause automatic rejection of skill installation:

| Check | Pattern | Description |
|-------|---------|-------------|
| exec/eval with user input | `exec(request.args...)` | Dynamic code execution with unsanitized input |
| Hardcoded credentials | `password = "..."` | Credentials will be exposed to all users |
| Environment exfiltration | `os.environ[key]` → `requests.post()` | Sends secrets to external servers |
| Shell injection | `subprocess(user_input)` | Command injection vulnerabilities |
| File exfiltration | `open(os.environ[...])` | Reads files based on env vars |

### 🟠 HIGH - Require Confirmation

These issues will prompt a warning but allow manual override:

| Check | Pattern | Description |
|-------|---------|------------

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 · references/security-checks.md (reported line 30)May include surrounding context.

md
| Network without timeout | `curl ...` (no -m) | May hang indefinitely |
| os.system usage | `os.system(cmd)` | Shell command execution |
| Raw socket connections | `socket.connect()` | Unknown network destinations |
| Sensitive paths | `/etc/passwd`, `/proc/` | System file access |

### 🟡 MEDIUM - Caution

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
79% confidence
Finding

The skill declares behaviors implying file access, network transmission, and possibly shell-based scanning, but does not declare any explicit tool scope or permissions. This creates a transparency and least-privilege problem: users and the platform cannot clearly constrain what the skill may access or transmit, which is especially risky for a security-audit tool that inspects local skills and sends reports outward.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The description states that reports are automatically sent to all configured channels but does not clearly warn that scan contents may be transmitted to external messaging services. For a tool that may inspect code, paths, or possible secrets, this creates a significant risk of unintentional exfiltration to third-party platforms or unintended recipients.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest description is written entirely in Chinese, and the usage examples also include Chinese trigger phrases, but there is no indication that the skill is intentionally region-specific or that users can choose another language. This can violate language or locale policy when skills are expected to avoid forcing a specific language without user opt-in.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
82% confidence
Finding

Broad trigger phrases like 'scan skills' or '安全扫描' can cause accidental activation in normal conversation, leading the agent to inspect local content or produce reports without sufficiently clear user intent. Because this skill may access files and send findings externally, loose activation criteria raise the chance of unintended data processing or disclosure.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This Python file contains user-facing report strings primarily in Chinese, such as the subtitle, summary labels, issue details, and recommendations. The skill does not provide any visible opt-in, locale detection, or alternative language path, which is a natural-language locale policy concern under the rule for all file types.

Content

No source excerpt is available for this finding.

Unrestricted Tool Access

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill grants unrestricted tool access without appropriate constraints. An agent with unfettered tool access can perform arbitrary actions including file modification, network requests, and code execution.

Content

Scanner excerpt · scripts/skill_audit.py (reported line 126)May include surrounding context.

python
{
            "name": "Pickle deserialization",
            "pattern": r'pickle\.loads?|pickle\.load\s*\(',
            "description": "Pickle deserialization - can execute arbitrary code",
            "severity": MEDIUM
        },
        {

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
80% confidence
Finding

This code recursively reads all non-hidden files under the supplied path and also reads matching files from .skill zip archives, which can expose user data during scanning. Although the module docstrings describe security scanning, there is no user-facing warning, prompt, or log message explaining that arbitrary local files will be opened and analyzed.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest describes a tool that supports periodic scanning every 24 hours and automatically sends reports to all configured channels such as Telegram or Feishu. In this code, the implemented behavior is limited to scanning a user-supplied path from argv, printing a terminal report, and exiting with a status code; there is no scheduling, channel integration, or outbound report delivery.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The installation and post-install text describes ongoing background scanning and automatic report delivery without an explicit consent or warning model. This is risky because users may unknowingly enable recurring file inspection and outbound messaging after installation, even if the immediate technical impact is lower than direct exfiltration logic.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The top-level documentation frames the script as scanning skills before installation, implying installation-time enforcement. The implementation accepts any path from the command line, scans it ad hoc, and merely returns recommendations and exit codes; it is not tied to an installation workflow in this file.

Content

No source excerpt is available for this finding.