Back to skill

Security audit

Publish Guard

Security checks for vulnerabilities and agentic risk

Overview

Publish Guard is a coherent local pre-release audit tool, but its scanners can follow repository symlinks and read files outside the repo the user meant to audit.

Review before installing or running on untrusted repositories. The skill appears intended for local release auditing, but until symlink containment is fixed, a crafted repo could cause it to read user-readable files outside the target repo and include limited findings or snippets in local reports.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/scan_leaks.py:111
Finding

Repository Symlink Traversal Allows Reads Outside the Audit Root

Content
View full analysis

Vulnerability Details

File Locations:

  • scripts/scan_leaks.py:111-158
  • scripts/scan_public_surface.py:112-185

Vulnerability Type: Improper symbolic-link handling and filesystem boundary violation
Risk Level: Medium

Vulnerable Code

From scripts/scan_leaks.py:

python
def iter_files(root: Path) -> list[Path]:
    files: list[Path] = []
    for path in root.rglob("*"):
        if not path.is_file():
            continue
        if any(part in IGNORE_DIRS or part.startswith(".venv") for part in path.relative_to(root).parts[:-1]):
            continue
        if not is_scannable_text_file(path):
            continue
        if not under_size_limit(path):
            continue
        files.append(path)
    return sorted(files)


def sanitize_snippet(line: str) -> str:
    snippet = line.strip()[:220]
    for pattern, replacement in SNIPPET_REDACTIONS:
        snippet = pattern.sub(replacement, snippet)
    snippet = SECRET_ENV_VALUE.sub(r"\g<name>\g<sep><redacted>", snippet)
    return snippet


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--root", required=True, help="Repo root to scan.")
    parser.add_argument("--out", required=True, help="Output JSON file.")
    args = parser.parse_args()

    root = Path(args.root).expanduser().resolve()
    findings: list[dict[str, object]] = []

    for path in iter_files(root):
        try:
            lines = path.read_text(encoding="utf-8").splitlines()
        except (OSError, UnicodeDecodeError):
            continue
        for line_number, line in enumerate(lines, start=1):
            for code, severity, pattern in PATTERNS:
                if pattern.search(line):
                    findings.append(
                        {
                            "code": code,
                            "severity": severity,
       
...[truncated 6467 chars]
Remediation
View remediation

Remediation Suggestions

  1. Reject symbolic links during repository traversal:

    python
    if path.is_symlink():
        continue
    
  2. Resolve every candidate and enforce containment before any stat(), exists(), or read_text() operation:

    python
    root = root.resolve()
    
    def safe_repo_file(path: Path, root: Path) -> Path | None:
        if path.is_symlink():
            return None
        try:
            resolved = path.resolve(strict=True)
            resolved.relative_to(root)
        except (OSError, ValueError):
            return None
        return resolved if resolved.is_file() else None
    
  3. Perform all reads through the validated resolved path rather than the original repository path.

  4. Apply the same validation to directly constructed paths, including README.md, SKILL.md, openai.yaml, SECURITY.md, and other documentation paths.

  5. Where supported, open files using operating-system options that prohibit following symbolic links, such as O_NOFOLLOW, to reduce time-of-check/time-of-use risks.

  6. Do not place source-line snippets in reports unless necessary. If snippets are retained, use conservative redaction or provide only the finding type and location.

  7. Add regression tests for:

    • A file symlink targeting a file outside the repository.
    • A directory symlink targeting an external directory.
    • Directly named symlinks such as README.md and SKILL.md.
    • Broken and cyclic symbolic links.
    • A symlink changed between validation and reading.
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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 (9)

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The declared description promises a broader pre-release audit across repo contents, README, SKILL.md, and public metadata. The supplied code chunk implements a narrowly scoped README scoring script only. While README review is consistent with part of the description, the primary behavior here is much more limited than the declared purpose, with no evidence of repository-wide checks, SKILL.md inspection, or metadata auditing.

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 20)May include surrounding context.

md
- `SKILL.md`

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
- `SKILL.md`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `SKILL.md`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `SKILL.md`

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `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/scan_leaks.py (reported line 12)May include surrounding context.

python
from pathlib import Path
TEXT_EXTENSIONS = {
    ".cer",
    ".env",
    ".ini",
    ".js",
    ".json",

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

python
from pathlib import Path
TEXT_EXTENSIONS = {
    ".cer",
    ".env",
    ".ini",
    ".js",
    ".json",

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.