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## 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
## 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## 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
## 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)

Lp3

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.

Vague Triggers

Medium
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.

Intent-Code Divergence

Medium
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.

Description-Behavior Mismatch

Medium
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.

Static analysis

No suspicious patterns detected.