Back to skill

Security audit

Publish Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a coherent publishing helper, but its bundled scanner can follow file symlinks outside the selected skill folder and read unintended local files.

Install only if you are comfortable with a publishing assistant that can inspect skill folders and guide git, GitHub, and ClawHub publish actions. Until the scanner is fixed to skip symlinks and use bounded reads, do not run it on untrusted skill directories or archives without first checking for symlinks.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/review_skill.py:100
Finding

Symbolic Link Traversal Allows Reads Outside the Reviewed Skill Directory

Content
View full analysis

Vulnerability Details

File Location: scripts/review_skill.py:100-104, 115-116, 158-184
Vulnerability Type: Symbolic link traversal and unbounded file read
Risk Level: Medium

Vulnerable code:

python
def iter_files(root: Path) -> Iterable[Path]:
    for current, dirs, files in os.walk(root):
        dirs[:] = [name for name in dirs if name not in EXCLUDED_DIRS]
        for file_name in files:
            yield Path(current) / file_name
python
def read_text(path: Path, max_bytes: int) -> tuple[str | None, str | None]:
    data = path.read_bytes()
    if b"\x00" in data:
        return None, "binary content"
    if len(data) > max_bytes:
        return None, f"text file larger than {max_bytes} bytes"
python
for path in iter_files(root):
    rel_path = relative(path, root)
    lower_rel = rel_path.lower()

    for pattern in SECRET_FILE_PATTERNS:
        if pattern.search(lower_rel):
            findings.append(Finding("ERROR", rel_path, None, "sensitive filename", "Remove secret-like files before publishing."))

    size = path.stat().st_size
    if size > 5_000_000:
        findings.append(Finding("INFO", rel_path, None, "large file", f"File is {size} bytes; confirm it belongs in the skill."))

    if not is_text_candidate(path):
        text, note = read_text(path, max_text_bytes)
        if text is None:
            findings.append(Finding("INFO", rel_path, None, "skipped non-text file", note or "Not a text file."))
            continue
    else:
        text, note = read_text(path, max_text_bytes)
        if note:
            findings.append(Finding("INFO", rel_path, None, "text scan note", note))
        if text is None:
            continue

Technical Analysis

The scanner recursively enumerates names beneath the user-supplied Skill directory, but it does not reject symbolic links or resolve each candidate ...[truncated 2523 chars]

Remediation
View remediation

Remediation Suggestions

  • Reject symbolic links before inspecting metadata or content:

    python
    if path.is_symlink():
        findings.append(
            Finding("WARN", rel_path, None, "symbolic link", "Skipped symbolic link.")
        )
        continue
    
  • Resolve the root once and verify every resolved candidate remains within it:

    python
    resolved_root = root.resolve()
    resolved_path = path.resolve(strict=True)
    if not resolved_path.is_relative_to(resolved_root):
        findings.append(
            Finding("ERROR", rel_path, None, "path escapes root",
                    "Resolved file is outside the reviewed skill directory.")
        )
        continue
    
  • Perform the containment check immediately before opening the file to reduce time-of-check/time-of-use exposure.

  • Open files without following symlinks where supported, such as by using os.open() with O_NOFOLLOW, and then read from the returned descriptor.

  • Check file size before allocating content, using metadata obtained from the safely opened descriptor.

  • Read at most max_bytes + 1 bytes through a bounded stream rather than calling read_bytes():

    python
    with safe_opened_file as handle:
        data = handle.read(max_bytes + 1)
    if len(data) > max_bytes:
        return None, f"text file larger than {max_bytes} bytes"
    
  • Treat filesystem errors, broken links, special files, and files changed during scanning as explicit findings rather than allowing them to terminate or bypass the review.

  • Add regression tests covering file symlinks to external files, directory symlinks, broken symlinks, oversized targets, and symlink replacement races.

Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (8)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description presents a broad automation skill for preparing, versioning, committing, and publishing Codex skills through GitHub and ClawHub. The actual code chunk only implements the pre-publish safety review portion: a local static scanner for common risks in a skill folder. While this behavior is consistent with the 'pre-publish security checks' part of the description, it does not implement the primary publishing/release capabilities that the description emphasizes. Therefore the code chunk materially underdelivers relative to the declared purpose, making the description inaccurate for this supplied code.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- Read the target `SKILL.md`, `agents/openai.yaml` if present, and a concise file listing before making decisions.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- Read the target `SKILL.md`, `agents/openai.yaml` if present, and a concise file listing before making decisions.

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/review_skill.py (reported line 32)May include surrounding context.

python
".bash",
    ".cjs",
    ".css",
    ".env",
    ".html",
    ".js",
    ".json",

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

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

Content

Scanner excerpt · scripts/review_skill.py (reported line 73)May include surrounding context.

python
SENSITIVE_PATH_PATTERNS = [
    ("SSH directory", re.compile(r"(?<![A-Za-z0-9_~/-])~?/\.ssh\b|/Users/[^/\s]+/\.ssh|/home/[^/\s]+/\.ssh")),
    ("cloud credentials", re.compile(r"(?<![A-Za-z0-9_~/-])~?/\.aws/credentials\b|~?/\.config/gcloud\b|~?/\.azure\b")),
    ("shell history", re.compile(r"(?<![A-Za-z0-9_~/-])~?/\.(zsh_history|bash_history)\b")),
    ("OS keychain", re.compile(r"\bkeychain\b|\bsecurity\s+find-(?:generic|internet)-password\b", re.IGNORECASE)),
]

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/review_skill.py (reported line 75)May include surrounding context.

python
("SSH directory", re.compile(r"(?<![A-Za-z0-9_~/-])~?/\.ssh\b|/Users/[^/\s]+/\.ssh|/home/[^/\s]+/\.ssh")),
    ("cloud credentials", re.compile(r"(?<![A-Za-z0-9_~/-])~?/\.aws/credentials\b|~?/\.config/gcloud\b|~?/\.azure\b")),
    ("shell history", re.compile(r"(?<![A-Za-z0-9_~/-])~?/\.(zsh_history|bash_history)\b")),
    ("OS keychain", re.compile(r"\bkeychain\b|\bsecurity\s+find-(?:generic|internet)-password\b", re.IGNORECASE)),
]

NETWORK_PATTERNS = [

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill instructs the agent to read local files, invoke shell commands, and potentially verify online documentation, but it declares no explicit tool scope or permissions boundary. In a publishing workflow that includes repository inspection, validation scripts, auth-related commands, and possible network use, missing scope increases the chance the agent will overreach or run unintended state-changing operations without clear user-visible constraints.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
85% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

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

md
2. List files under the target skill and explain why each resource directory exists.
3. Read every executable script in the skill before publishing.
4. Confirm scripts use local paths supplied by the user and avoid hard-coded personal directories.
5. Confirm references do not instruct agents to exfiltrate secrets, disable safeguards, or bypass user approval.
6. Confirm generated GitHub Actions never echo secrets and never commit generated credentials.

## Scanner Interpretation

Static analysis

No suspicious patterns detected.