Back to skill

Security audit

Openclaw Egress

Security checks for vulnerabilities and agentic risk

Overview

This skill is a local egress scanner, but it can also automatically rename skill directories and rewrite source files, with path-scoping weaknesses that warrant Review before installation.

Install only if you are comfortable with a security tool that can modify other skills. Prefer using scan, status, domains, and scan --skills-only first; avoid protect and block unless you have backups and have reviewed exactly which files will change. Do not pass untrusted skill names or paths to mutation commands.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/egress.py:341
Finding
Path Traversal Allows Source Files Outside the Skills Directory to Be Modified<![CDATA[ ## Vulnerability Details **File Location**: `scripts/egress.py`, lines 341-369 **Vulnerability Type**: Insufficient path validation and workspace boundary bypass **Risk Level**: High ### Vulnerable Code ```python def cmd_block(ws, skill_name): sd = ws / "skills" skill_dir = sd / skill_name if not skill_dir.is_dir(): if (sd / (QUARANTINE_PREFIX + skill_name)).is_dir(): print(f"Skill '{skill_name}' is quarantined. Unquarantine first."); sys.exit(1) print(f"Skill not found: {skill_name}"); _print_skills(sd); sys.exit(1) if skill_name in SELF_SKILL_DIRS: print(f"Cannot block self: {skill_name}"); sys.exit(1) actionable = [f for f in scan_skill(ws, skill_name, load_allowlist(ws)) if f["risk"] in ("CRITICAL", "HIGH")] if not actionable: print(f"No CRITICAL or HIGH findings in '{skill_name}'. Nothing to block."); return 0 by_file = {} for f in actionable: by_file.setdefault(f["file"], []).append(f) total = files_mod = 0 print("=" * 60); print(f"BLOCKING NETWORK CALLS IN: {skill_name}"); print("=" * 60); print() for rel, ffindings in sorted(by_file.items()): ap = ws / rel if not ap.is_file(): continue if ap.suffix not in CODE_SUFFIXES: for ff in ffindings: if ff["url"]: print(f" [FLAGGED] {rel}:{ff['line']} — {ff['reason']} (non-code, manual review)") continue indices = {ff["line"] - 1 for ff in ffindings} cnt = _block_lines(ap, indices) if cnt: total += cnt; files_mod += 1 print(f" [BLOCKED] {rel}: {cnt} line(s) neutralized (backup: {ap.suffix}.bak)") print(f"\nTotal: {total} line(s) blocked across {files_mod} file(s)") if total: print("Backups created with .bak extension.\n") return 0 ``` The same unvalidated construction is also used when collecting files: ```python def collect_skill_files(ws, ...[truncated 2758 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict Skill names to a conservative identifier format, for example: ```python if not re.fullmatch(r"[A-Za-z0-9_.-]+", skill_name): raise ValueError("Invalid skill name") ``` 2. Explicitly reject absolute paths, `..`, path separators, and empty names. 3. Resolve and validate the canonical target before scanning: ```python skills_root = (ws / "skills").resolve(strict=True) skill_dir = (skills_root / skill_name).resolve(strict=True) if skill_dir.parent != skills_root: raise ValueError("Skill must be an immediate child of the skills directory") ``` 4. Reject symbolic-link Skill directories and symbolic-link files before reading or writing them. 5. Before every write, resolve the file again and verify that it remains beneath the validated Skill directory using `Path.is_relative_to()` or an equivalent compatibility helper. 6. Open files using link-resistant operating-system facilities where available to reduce time-of-check/time-of-use risks. 7. Apply equivalent validation to `block`, `quarantine`, `unquarantine`, and every other command accepting a Skill name. 8. Add regression tests for `../`, nested traversal, absolute paths, symlinked directories, and symlinked files. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/egress.py:321
Finding
Regex-Based Automated Protection Can Corrupt or Disable Skill Source Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/egress.py`, lines 321-336 and 452-464 **Vulnerability Type**: Unsafe automated source-code rewriting **Risk Level**: Medium ### Vulnerable Code ```python def _block_lines(abs_path, line_indices): """Comment out specific lines in a code file. Returns count blocked.""" try: content = abs_path.read_text(encoding="utf-8", errors="ignore") except (OSError, PermissionError): return 0 lines = content.split("\n") to_block = {i for i in line_indices if 0 <= i < len(lines) and BLOCK_COMMENT not in lines[i]} if not to_block: return 0 shutil.copy2(abs_path, abs_path.with_suffix(abs_path.suffix + ".bak")) cc = _comment_char(abs_path.suffix) for idx in to_block: orig = lines[idx]; stripped = orig.lstrip() indent = orig[:len(orig) - len(stripped)] if stripped.startswith("#") or stripped.startswith("//"): lines[idx] = f"{orig} {BLOCK_COMMENT}" else: lines[idx] = f"{indent}{cc}{stripped} {BLOCK_COMMENT}" abs_path.write_text("\n".join(lines), encoding="utf-8") return len(to_block) ``` The automated `protect` operation invokes this rewriting logic directly: ```python # HIGH only -> block by_file = {} for f in high: by_file.setdefault(f["file"], []).append(f) skill_cnt = 0 for rel, ffs in by_file.items(): ap = ws / rel if not ap.is_file() or ap.suffix not in CODE_SUFFIXES: continue cnt = _block_lines(ap, {ff["line"] - 1 for ff in ffs}) skill_cnt += cnt if skill_cnt: b_total += skill_cnt; b_list.append(skill) actions.append(f"BLOCKED: {skill} ({skill_cnt} line(s))") print(f" [BLOCK] {skill} — {skill_cnt} HIGH line(s) neutralized") ``` ### Technical Analysis Network findings are generated through line-oriented regular expressions rather than lan ...[truncated 2649 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make automated protection report-only by default. 2. Present an exact proposed diff and require explicit user confirmation before modifying each file. 3. Replace regex-selected whole-line commenting with language-aware transformations based on an abstract syntax tree or a parser appropriate to each supported language. 4. Do not automatically rewrite unsupported languages or ambiguous multiline constructs. 5. Write changes to a temporary file in the same directory, preserve permissions, validate the result, and atomically replace the original only after all checks succeed. 6. Run syntax validation for the relevant language before committing a modification. 7. Restore the original automatically if validation or replacement fails. 8. Create uniquely versioned backups rather than repeatedly overwriting a single `.bak` file. 9. Detect concurrent file changes by comparing file hashes or metadata between scanning and modification. 10. Separate detection from enforcement so a finding cannot directly trigger a destructive action without a distinct authorization step. 11. Add tests covering multiline expressions, decorators, chained calls, assignments, conditionals, strings, comments, fixtures, and multiple findings on the same source line. ]]>
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (6)

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The skill is presented as an egress/DLP scanner, but it also includes active countermeasures that rename skill directories and rewrite other skills' source files. That exceeds a read-only detection role and gives the tool the ability to disable or tamper with unrelated code in the workspace, which is dangerous if triggered on false positives or used abusively.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill is user-invocable and documents execution of a local Python script over an arbitrary workspace path, but it does not declare any explicit tool scope such as permissions or allowed-tools. That means the effective capabilities are broader than what is transparently disclosed, including file access and shell execution, which increases the risk of unintended data exposure or misuse if the host agent grants broad defaults.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
Workspace resolution and subsequent scanning logic enable enumeration of the full workspace, not just the target skills directory. For a skill whose stated purpose is agent-skill egress monitoring, this broad file access increases privacy and data-exposure risk by reading unrelated project files and metadata.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The `protect` flow automatically renames skill directories to quarantine them as soon as findings are classified as CRITICAL, with no confirmation step. In this context, false positives or overbroad pattern matches could disable legitimate skills and create an easy denial-of-service mechanism against the workspace.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The protection sweep automatically comments out lines in other skills and writes backup files without prior approval. Because the detection rules are heuristic and broad, this can silently alter legitimate code behavior, break skills, or be abused to tamper with the workspace under the guise of security scanning.

Intent-Code Divergence

Low
Confidence
84% confidence
Finding
The docstring says the function comments out specific lines and returns a count blocked, but the implementation also copies the original file to a .bak backup and rewrites the entire file in place. That is an active side effect beyond what the documentation describes.

Static analysis

No suspicious patterns detected.