Back to skill

Security audit

Workspace Hygiene Publish

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does the advertised workspace hygiene audit, but unsafe file handling could let a crafted workspace redirect reads or writes outside the intended folder.

Install only if you trust the workspaces it will audit. Avoid running it with elevated privileges, avoid --fix on untrusted or shared workspaces, and review symlinks before use. Treat --report-only as 'no fixes' rather than a true dry run because it still writes a report file.

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

Error
Location
scripts/hygiene.py:440
Finding

Symlink-Following Writes Can Escape the Workspace Boundary

Content
View full analysis

Vulnerability Details

File Location: scripts/hygiene.py:440-451
Vulnerability Type: Symlink-following arbitrary file overwrite
Risk Level: High

Vulnerable Code

python
def write_report(result: AuditResult, report_date: dt.date, report_only: bool) -> Path:
    report_dir = result.workspace / "projects" / "system"
    if not report_dir.exists() and not report_only:
        report_dir.mkdir(parents=True, exist_ok=True)
        result.fixes.append("Created report directory: projects/system")
    elif not report_dir.exists():
        report_dir.mkdir(parents=True, exist_ok=True)

    report_path = report_dir / "hygiene-{0}.md".format(report_date.isoformat())
    report_path.write_text(render_report(result, report_date), encoding="utf-8")
    result.report_path = report_path
    return report_path

Technical Analysis

The report destination is predictable and is written without checking whether the destination or any parent component is a symbolic link. Python's Path.write_text() follows symbolic links and truncates an existing target before writing.

The workspace path is initially resolved, but paths constructed beneath it are not resolved and validated immediately before use. Therefore, resolving the top-level workspace does not prevent a malicious workspace entry such as projects/system/hygiene-YYYY-MM-DD.md, projects/system, or another parent component from redirecting the write outside the workspace.

This operation occurs during every normal invocation, including report-only operation, and does not require --fix.

Attack Path

  1. An attacker supplies a workspace or can modify a workspace that the victim will audit.
  2. The attacker creates projects/system/ and places a symbolic link named hygiene-YYYY-MM-DD.md, using the expected execution date.
  3. The symbolic link targets an arbitrary file outside the workspace that is writable by the victim.
  4. The victim runs ...[truncated 949 chars]
Remediation
View remediation

Remediation Suggestions

  • Reject a report destination if it already exists as a symbolic link or is not a regular file.
  • Resolve the intended destination and verify with Path.relative_to() or os.path.commonpath() that it remains beneath the resolved workspace.
  • Validate every path component because checking only the final filename does not prevent redirection through a symlinked parent directory.
  • Open the output with no-follow and exclusive-creation protections where available, such as os.open() with O_NOFOLLOW, and then write through the returned descriptor.
  • Use a safely created temporary file in a verified directory, flush and synchronize it as appropriate, and atomically replace the destination only after repeating boundary and file-type checks.
  • Document that untrusted workspaces must not be audited with elevated privileges.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/hygiene.py:191
Finding

Memory Consolidation Follows Symlinks for External Reads and Writes

Content
View full analysis

Vulnerability Details

File Location: scripts/hygiene.py:191-212
Vulnerability Type: Symlink-following arbitrary file read and modification
Risk Level: High

Vulnerable Code

python
def load_text(path: Path) -> str:
    return path.read_text(encoding="utf-8") if path.exists() else ""


def append_memory_content(target_path: Path, source_path: Path) -> bool:
    source_text = load_text(source_path)
    if not source_text.strip():
        return False

    target_text = load_text(target_path)
    if source_text.strip() in target_text:
        return False

    if target_text and not target_text.endswith("\n"):
        target_text += "\n"

    if target_text.strip():
        merged = target_text.rstrip() + "\n\n" + source_text.strip() + "\n"
    else:
        merged = source_text.rstrip() + "\n"

    target_path.parent.mkdir(parents=True, exist_ok=True)
    target_path.write_text(merged, encoding="utf-8")
    return True

The unsafe helper is reached by the following consolidation logic at scripts/hygiene.py:300-315:

python
if TIMESTAMP_MEMORY_RE.match(path.name):
    result.add("ERROR", "Timestamp-format memory file: {0}".format(path.name))
    if apply_fix:
        date_prefix = path.name[:10]
        daily_path = memory_dir / "{0}.md".format(date_prefix)
        changed = append_memory_content(daily_path, path)
        if changed:
            result.fixes.append(
                "Merged contents from {0} into {1} (source retained for manual cleanup)".format(
                    path.name, daily_path.name
                )
            )
        else:
            result.fixes.append(
                "Checked {0}; no merge needed because content was empty or already present".format(path.name)
            )

Technical Analysis

Both Path.read_text() and Path.write_text() follow symbolic links. The consolidation routine neither reject ...[truncated 2233 chars]

Remediation
View remediation

Remediation Suggestions

  • Refuse symbolic links for the memory/ directory, timestamped source files, and daily destination files.
  • Resolve every source and destination immediately before access and ensure each remains under result.workspace.resolve().
  • Use descriptor-based no-follow operations, including O_NOFOLLOW where supported, to reduce check-to-use races.
  • Require source and target objects to be regular files based on lstat/fstat, rather than file checks that follow symbolic links.
  • Create new destination files exclusively and perform updates through a verified temporary file followed by a guarded atomic replacement.
  • Revalidate parent directories and destination metadata immediately before replacement to mitigate time-of-check/time-of-use attacks.
  • Avoid running --fix on workspaces from untrusted sources and never run it with elevated privileges.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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 (4)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill documentation describes behavior that reads workspace files and writes a hygiene report, but the manifest declares no explicit tool scope or permissions boundary. In systems that rely on manifest-declared capabilities for policy enforcement or user review, this can lead to overbroad execution or unclear authorization for file access across the workspace.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The manifest activates on phrases such as "clean up workspace," "run hygiene," and "audit files," which are generic enough to match common everyday requests rather than a narrowly scoped skill invocation. The description also lacks exclusion conditions or negative examples clarifying when the skill should not run.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The help text says --report-only will audit and report without applying fixes, 'even if --fix is also passed,' but the implementation still mutates the workspace by creating a report directory and writing a report file. This mismatch is dangerous because operators, schedulers, or higher-level agents may rely on CLI semantics for safe dry-run behavior and unknowingly permit filesystem changes.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This is a real integrity/safety issue: when users select --report-only, they reasonably expect the workspace will not be modified, but write_report() still creates projects/system and writes a report file. In an automation context, that can unintentionally alter repositories, trigger downstream tooling, or violate dry-run expectations, especially because this skill is explicitly meant to audit and maintain workspace state.

Content

No source excerpt is available for this finding.