Back to skill

Security audit

Downloads Folder Organizer

Security checks for vulnerabilities and agentic risk

Overview

This Downloads organizer is review-worthy because it can move files using unvalidated plan paths and stores a persistent index of the target directory tree.

Review carefully before installing. Use only on a narrow Downloads and target directory, avoid enabling watch mode unless you want automatic moves, and do not pass edited or untrusted plan JSON to --execute. The publisher should add path containment checks, safer plan handling, and make indexing explicitly opt-in or narrower.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T09 · Insecure Skill Coding Practices

Error
Location
organizer.py:210
Finding
Unvalidated Execution Plan Allows Arbitrary Filesystem Moves<![CDATA[ ## Vulnerability Details **File Location**: `organizer.py:210-219`, `organizer.py:339-342`; execution workflow documented at `SKILL.md:39-43` **Vulnerability Type**: Unvalidated caller-controlled source and destination paths **Risk Level**: High ### Vulnerable Code ```python def execute_plan(plan: List[Dict]) -> Dict: results = {"moved": [], "errors": []} log_lines = [f"# Organize log {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", ""] for item in plan: source = Path(item["source"]) target = Path(item["final_target"]) try: target.parent.mkdir(parents=True, exist_ok=True) shutil.move(str(source), str(target)) results["moved"].append({"from": str(source), "to": str(target)}) ``` The command-line handler passes decoded, caller-controlled JSON directly to this function: ```python if args.execute: plan = json.loads(args.execute) results = execute_plan(plan) update_index(config) print(json.dumps(results, ensure_ascii=False, indent=2)) return ``` The documented workflow explicitly permits modification of destination-related plan fields before execution: ```markdown - If the user requests **changes**: adjust the plan (modify `target_subdir` and `final_target` fields accordingly), show the updated table, and ask again Pass the (possibly modified) plan JSON back to the script: ```bash python3 ~/.claude/skills/organize/organizer.py --execute '<JSON>' ``` ``` ### Technical Analysis `execute_plan()` treats the `source` and `final_target` fields in supplied JSON as trusted filesystem paths. It does not verify that: - The source is a regular file located directly inside the configured `downloads_dir`. - The destination is contained within the configured `target_root`. - The source and destination are free of symbolic-link redirection. - The destination does not already exist at execution time. - The plan was produced by the current invocation of `--scan`. - The d ...[truncated 2226 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve and validate every path immediately before the move: ```python downloads_root = Path(config["downloads_dir"]).resolve() target_root = Path(config["target_root"]).resolve() source = Path(item["source"]).resolve(strict=True) target = Path(item["final_target"]).resolve(strict=False) if source.parent != downloads_root: raise ValueError("Source must be a direct child of downloads_dir") if not target.is_relative_to(target_root): raise ValueError("Destination must remain under target_root") if not source.is_file() or source.is_symlink(): raise ValueError("Source must be a non-symlink regular file") ``` 2. Pass the loaded configuration into `execute_plan()` so it can enforce source and destination boundaries rather than trusting the caller. 3. Do not accept complete source and destination paths from the caller. Return opaque identifiers from `--scan`, store the associated plan internally, and recompute paths during execution. 4. Permit user adjustments only through a validated relative target subdirectory. Reject absolute paths, `..` components, empty components, and paths escaping `target_root`. 5. Re-run conflict resolution immediately before each move to mitigate scan-to-execution races. Use move semantics that explicitly refuse to overwrite an existing destination. 6. Reject unknown or malformed plan fields and enforce a strict JSON schema. 7. Consider binding each plan to the current scan using a short-lived nonce or authenticated digest so arbitrary plans cannot be submitted directly. 8. Perform symlink-aware checks and ensure every relevant parent directory remains under the validated root at the moment of use. ]]>

T08 · Insecure Dependencies

Note
Location
SKILL.md:69
Finding
Watch Mode Uses an Unpinned Third-Party Dependency<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:69-70`; dependency import at `organizer.py:270-275` **Vulnerability Type**: Unpinned third-party package installation **Risk Level**: Low ### Vulnerable Code The documentation recommends installing an unconstrained package: ```markdown ## Notes - Script location: `~/.claude/skills/organize/organizer.py` - Config: `~/.claude/skills/organize/config.json` - Logs: `~/.claude/skills/organize/logs/YYYY-MM-DD.log` - Index: `~/.claude/skills/organize/index.md` - The script requires `watchdog` for watch mode: `pip install watchdog` ``` The package is imported when watch mode is selected: ```python def watch_mode(config: Dict): try: from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler except ImportError: print("Please install watchdog first: pip install watchdog", file=sys.stderr) sys.exit(1) ``` ### Technical Analysis The installation instruction does not pin a reviewed version, require integrity hashes, identify a trusted package index, or provide a lock file. Running `pip install watchdog` installs whichever release the configured package resolver considers current at installation time. The package name is not visibly misspelled, and the project does not specify a suspicious source. The risk arises from non-reproducible dependency resolution and from trusting future package versions or a misconfigured package index without integrity constraints. ### Attack Path 1. A user enables watch mode and encounters the missing-dependency message. 2. The user follows the documented `pip install watchdog` instruction. 3. Pip resolves the package using the environment's currently configured indexes and selects an unconstrained version. 4. If the selected distribution or configured index is compromised, malicious installation or runtime code executes with the privileges of the user running pip or the organizer. 5. The organizer late ...[truncated 726 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `watchdog` to a specifically reviewed version instead of installing the latest available release. 2. Maintain dependencies in a lock file and include cryptographic hashes, for example by using a hash-locked requirements file and installing with: ```bash python3 -m pip install --require-hashes -r requirements.txt ``` 3. Document the intended package index and avoid untrusted or unintended extra indexes. 4. Install the dependency in an isolated virtual environment rather than the user's global Python environment. 5. Periodically review and update the pinned version after checking release notes, provenance, and known vulnerability reports. 6. Ensure the runtime environment installs only from the reviewed lock file rather than repeating the unconstrained installation command in error messages. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • 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 (6)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared purpose is simple file organization, but the documented behavior expands into persistent directory monitoring, logging, full target-root indexing, and content inspection via file reads and external utilities. This mismatch is dangerous because users may consent to a narrow housekeeping task without realizing the skill also performs broader surveillance-like collection and persistence that exposes file metadata and possibly content.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes a local Python script with shell commands and can read from Downloads, move files into other directories, and update logs/indexes, but it declares no explicit tool scope or permission boundaries. That omission increases the chance the agent executes broad file-system and shell actions without adequate user awareness or enforcement, which is a real security issue for a skill that performs writes and command execution.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
For a local file organizer, spawning an external command is not an obvious or necessary capability, especially since the manifest only describes organizing files into directories. This adds execution capability beyond straightforward file inspection and increases the skill's operational scope.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Use `strings` for binary/PDF files
        import subprocess
        result = subprocess.run(
            ['strings', str(filepath)],
            capture_output=True, text=True, timeout=5
        )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The skill exceeds its stated purpose by recursively indexing the entire configured target directory tree and writing that inventory into the skill directory. This creates a persistent catalog of filenames and folder structure that may expose sensitive local information unrelated to simply organizing Downloads files.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
Watch mode automatically moves newly created files from Downloads after a fixed delay without explicit confirmation or a startup safety acknowledgement. This can cause unintended relocation of files, interfere with user workflows or other software, and may move sensitive files immediately upon arrival before the user can review them.

Static analysis

No suspicious patterns detected.