Back to skill

Security audit

Paper Downloader

Security checks for vulnerabilities and agentic risk

Overview

This skill is meant to download public files, but its implementation does not match its stated safety limits and can write downloaded content to the filesystem root.

Review this skill before installing. Its intended download workflow is ordinary, but the current script should be fixed to save only under /tmp/skill-downloads, reject unsafe URLs and redirects, enforce the 64 MiB limit while streaming, avoid overwrites, and run only with minimal filesystem and network privileges.

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 (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_file.py:12
Finding

Redirect-Based Server-Side Request Forgery to Private Network Resources

Content
View full analysis

Vulnerability Details

File Location: scripts/download_file.py, lines 12–20
Vulnerability Type: Server-Side Request Forgery (SSRF) through unvalidated URLs and redirects
Risk Level: Medium

Vulnerable code:

python
parser = argparse.ArgumentParser()
parser.add_argument("url")
args = parser.parse_args()

DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)

filename = Path(urllib.parse.urlparse(args.url).path).name or "download"
destination = DOWNLOAD_DIR / filename

with urllib.request.urlopen(args.url) as response:
    destination.write_bytes(response.read())

Technical Analysis

The script passes the supplied URL directly to urllib.request.urlopen() without validating its scheme, embedded credentials, port, resolved IP address, or redirect destinations. urlopen() follows HTTP redirects by default.

This contradicts the safeguard declared in SKILL.md, which says that local and private-network targets, URL credentials, and unsupported ports are rejected. The implementation performs none of those checks.

Even if the initially supplied URL resolves to a public address, its server can return a redirect to a loopback, link-local, or private-network address. The Skill host then requests that internal destination using its own network access. The resulting response is written to a local file.

The attacker-controlled inputs are the public URL server and its redirect responses. The crossed trust boundary is from an untrusted Internet service into network resources accessible only to the Skill host.

Attack Path

  1. An attacker operates a public HTTP or HTTPS endpoint.
  2. The user is induced to request a file from that endpoint.
  3. The endpoint returns an HTTP redirect to a private destination such as a loopback, link-local, or RFC1918 address.
  4. urllib.request.urlopen() automatically follows the redirect without revalidating the destination.
  5. The Skill host accesses the internal ...[truncated 657 chars]
Remediation
View remediation

Remediation Suggestions

  • Accept only explicitly supported http and https URLs.
  • Reject URLs containing user information or unsupported ports.
  • Resolve the destination hostname and reject loopback, private, link-local, multicast, unspecified, and reserved addresses.
  • Disable automatic redirects and process each redirect explicitly.
  • Reapply URL, hostname, port, and resolved-address validation to every redirect hop.
  • Limit the number of redirects and prevent scheme changes to unsupported protocols.
  • Account for DNS rebinding by connecting only to a previously validated address while preserving the intended HTTP host identity.
  • Apply connection and response timeouts.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/download_file.py:7
Finding

Attacker-Controlled Root-Directory File Overwrite

Content
View full analysis

Vulnerability Details

File Location: scripts/download_file.py, lines 7 and 14–20
Vulnerability Type: Unsafe file destination and overwrite
Risk Level: High

Vulnerable code:

python
DOWNLOAD_DIR = Path("/")
python
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)

filename = Path(urllib.parse.urlparse(args.url).path).name or "download"
destination = DOWNLOAD_DIR / filename

with urllib.request.urlopen(args.url) as response:
    destination.write_bytes(response.read())

print(destination)

Technical Analysis

The documented destination is /tmp/skill-downloads, but the implementation sets DOWNLOAD_DIR to the filesystem root. The URL path controls the basename used for the destination, while the remote response controls the bytes written.

Path.write_bytes() opens the destination for writing and truncates an existing file. The script does not use exclusive creation, generate a unique filename, reject collisions, or verify that the destination is under the directory authorized by the Skill description.

Although use of Path(...).name removes directory components and prevents direct ../ traversal, it does not prevent replacement of an existing root-level file with a matching basename. The operation succeeds whenever the process account has write permission to that destination. Its impact is especially severe if the Skill is run with elevated privileges.

This crosses the documented filesystem authorization boundary: a request intended to create a new file under /tmp/skill-downloads instead writes under /.

Attack Path

  1. An attacker selects or controls a download URL whose final path basename matches a root-level file of interest.
  2. The user invokes the Skill with that URL.
  3. The script derives the basename from the URL path.
  4. The script joins that basename to /, producing a root-level destination.
  5. The remote server returns attacker-controlled bytes.

...[truncated 781 chars]

Remediation
View remediation

Remediation Suggestions

  • Set the destination directory to the documented fixed path:
    python
    DOWNLOAD_DIR = Path("/tmp/skill-downloads")
    
  • Resolve the destination and verify it remains a direct child of the approved directory.
  • Create files with exclusive semantics, such as mode xb, so existing files cannot be overwritten.
  • Generate a random or collision-resistant local filename rather than relying solely on an untrusted URL basename.
  • Write to a securely created temporary file inside the approved directory, then atomically rename it after a successful download.
  • Refuse symbolic links and other unexpected filesystem object types.
  • Run the Skill as a dedicated, unprivileged account with write access only to the download directory.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_file.py:19
Finding

Unbounded Response Buffering Enables Memory and Disk Exhaustion

Content
View full analysis

Vulnerability Details

File Location: scripts/download_file.py, lines 19–20
Vulnerability Type: Uncontrolled resource consumption
Risk Level: Medium

Vulnerable code:

python
with urllib.request.urlopen(args.url) as response:
    destination.write_bytes(response.read())

Technical Analysis

The Skill documentation claims that downloads are limited to 64 MiB, but the implementation calls response.read() without a maximum length. This buffers the entire response in process memory before writing it to disk.

A remote server can return an oversized response or stream data for an extended period. The script neither validates Content-Length nor tracks bytes received while streaming. It also supplies no explicit network timeout and has no cleanup procedure for failed or excessive downloads.

The attacker-controlled response therefore crosses the external-network-to-local-resource boundary without the documented size restriction.

Attack Path

  1. An attacker controls the server behind a requested public URL or a redirect destination.
  2. The user invokes the downloader for that URL.
  3. The server returns a very large response or a long-running stream.
  4. response.read() continues reading and accumulating the response in memory without enforcing the declared 64 MiB limit.
  5. The process consumes excessive memory and, if reading completes, attempts to write the entire response to disk.
  6. Resource exhaustion can terminate the Skill or degrade the host and other workloads.

Impact Assessment

Exploitation can exhaust process or host memory, consume disk capacity, terminate the downloader, or cause denial of service to other workloads sharing the host. The affected scope depends on operating-system resource limits, available storage, and the privileges and isolation of the Skill process.

Remediation
View remediation

Remediation Suggestions

  • Stream the response in fixed-size chunks rather than calling unbounded read().
  • Track the cumulative byte count and abort immediately when it exceeds 64 MiB.
  • Reject a declared Content-Length greater than the limit, while still enforcing the limit during streaming because that header may be absent or false.
  • Configure connection and read timeouts.
  • Write into a temporary file in the approved download directory and delete it on timeout, size-limit violation, or any other failure.
  • Consider applying process-level memory, disk, and execution-time limits as defense in depth.
Vulnerability Patterns
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The skill claims to safely download only public HTTP/HTTPS files into /tmp/skill-downloads with checks against private/local targets, credentials, unsafe ports, and large responses, but the analyzed behavior omits those protections and may write outside the intended directory. This creates a strong SSRF and arbitrary file-write risk surface, enabling access to internal services or sensitive local paths and potentially causing resource exhaustion or unsafe downstream processing.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The script sets the download directory to the filesystem root (/) instead of the declared shared directory /tmp/skill-downloads. This can cause files to be written outside the intended containment boundary, risking overwriting sensitive paths, permission-related failures, or unsafe interaction with other system components if the process has elevated privileges.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The code downloads any user-supplied URL with no validation of scheme, host, credentials, port, redirect target, network scope, or response size, despite the skill claiming those restrictions. This enables SSRF-style access to internal or local services, retrieval from unsupported protocols or ports, and unbounded downloads that can exhaust disk or memory.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill invokes a Python downloader and writes files locally while also performing network access, but it declares no explicit tool scope or permissions boundaries. In an agent environment, missing scope declarations can allow broader-than-intended execution and make review, policy enforcement, and runtime restriction much weaker.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This code fetches data from a user-supplied URL and writes it to the local filesystem, but there is no confirmation prompt, warning comment/docstring, or other disclosure explaining these safety-relevant actions. For a code file, network transmission and file writes should have some visible warning unless clearly disclosed elsewhere.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.