Back to skill

Security audit

cloudflare-drop

Security checks for vulnerabilities and agentic risk

Overview

This skill is a real Cloudflare deployment helper, but it needs review because it can publicly upload more local files than the user may expect and mutates local Wrangler auth state by default.

Install only if you are comfortable with public Cloudflare deployment from a clean, dedicated static-site folder. Do not run it from a directory containing credentials, private reports, backups, or unrelated source files. Prefer an explicit Cloudflare API token for permanent deploys, consider --no-pause-oauth if you have Wrangler OAuth login state, and periodically review or prune ~/.cloudflare-drop because it stores deployed HTML and claim links.

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

T09 · Insecure Skill Coding Practices

Error
Location
references/deploy.mjs:53
Finding
Overbroad Directory Publication Can Expose Unrelated Sensitive Files<![CDATA[ ## Vulnerability Details **File Location**: `references/deploy.mjs:53-69` **Vulnerability Type**: Unrestricted recursive staging of the source directory **Risk Level**: High ### Vulnerable Code ```js // Copy sibling assets (css/js/img) so a multi-file page renders — but only when // the source dir is a SEPARATE directory (not our staged root's parent), and // never the staged dir itself, so we can't recurse into our own output. const srcDir = dirname(htmlPath); if ( existsSync(srcDir) && statSync(srcDir).isDirectory() && srcDir !== stagedDir && srcDir !== root ) { cpSync(srcDir, stagedDir, { recursive: true, filter: (s) => !s.includes('node_modules') && !s.includes('.git') && !s.includes('__MACOSX') && !basename(s).startsWith('.') && s !== stagedDir, // guard against copying the staged dir into itself }); } ``` ### Technical Analysis Deploying a single HTML file recursively copies its entire containing directory into the deployment staging directory. The filter excludes only a small set of hidden or specially named paths. It does not restrict staging to assets referenced by the page or exclude visible sensitive files such as: - `credentials.json` - Configuration and environment backups - Database exports - Private reports - Source maps containing source code - Unrelated documents or archives Wrangler later publishes the staged directory. Consequently, the script can transmit and publicly host files that the operator did not intend to deploy. This exceeds the minimum filesystem and network scope necessary to publish the specified HTML file and its required assets. ### Attack Path 1. An HTML file is placed in, or selected from, a directory containing unrelated sensitive files. 2. The operator runs the documented `deploy.mjs` command for that HTML file. 3. `stageForDrop()` recursively copies the complete parent directory. 4. The limited filter permits visible sensitive files to enter the staging ...[truncated 832 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Stage only the explicitly requested HTML file by default. 2. Parse HTML and CSS references and copy only required local assets after canonical path validation. 3. Alternatively, require an explicit site-directory argument so directory publication is intentional and visible. 4. Generate and display a deployment manifest before upload, especially when files other than the requested page are included. 5. Reject symlinks and ensure every copied path remains inside the approved source root. 6. Add deny rules for common sensitive filenames and extensions as defense in depth, including credential files, environment files, private keys, database files, backups, and archives. 7. Add tests proving that unrelated visible files and nested sensitive files are not staged. ]]>

T08 · Insecure Dependencies

Error
Location
references/wrangler.mjs:87
Finding
Unpinned Remote Wrangler Package Is Installed and Executed with the Full Environment<![CDATA[ ## Vulnerability Details **File Location**: `references/wrangler.mjs:87-89, 109-115` **Vulnerability Type**: Unpinned executable dependency and excessive environment inheritance **Risk Level**: High ### Vulnerable Code ```js const args = ['exec', '--yes', 'wrangler@latest', '--', 'deploy', siteDir, '--name', name, '--compatibility-date', compatibilityDate]; if (mode === 'temporary') args.push('--temporary'); ``` ```js function defaultRun(args, { env }) { return execFileSync('npm', args, { encoding: 'utf8', env, timeout: 300_000, stdio: ['ignore', 'pipe', 'pipe'], }); } ``` ### Technical Analysis Each deployment uses `npm exec --yes wrangler@latest`. This retrieves and executes whichever Wrangler release is currently identified by the mutable `latest` tag. There is no pinned version, committed lockfile, or integrity-controlled dependency resolution in the project. The `--yes` option suppresses installation confirmation. The downloaded package executes with the complete process environment. That environment may include `CLOUDFLARE_API_TOKEN`, `CF_API_TOKEN`, and unrelated credentials inherited from the invoking agent or shell. Although using Wrangler is necessary for the declared deployment function, dynamically executing an unpinned latest release is not necessary. It creates a supply-chain execution channel whose effective code can change after this Skill has been reviewed. ### Attack Path 1. A Wrangler release, one of its transitive dependencies, or the associated package distribution channel is compromised. 2. The attacker causes malicious code to be published under a version selected by the `latest` tag. 3. The operator invokes the Skill through its normal documented command. 4. `npm exec --yes` downloads and executes the changed package without interactive approval. 5. Installation or runtime code reads the inherited environment and accesses local files with the invoking user's permissions. 6. The malicious dependen ...[truncated 782 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin Wrangler to a specific audited version instead of using `wrangler@latest`. 2. Declare the dependency in a package manifest and commit a lockfile with integrity metadata. 3. Install dependencies through a reproducible process such as `npm ci`. 4. Establish a controlled dependency-update process with review and automated security testing. 5. Pass an allowlisted environment to Wrangler rather than the complete `process.env`. 6. Include only variables required for deployment, such as the selected Cloudflare token and essential platform variables. 7. Avoid suppressing installation confirmation for dependencies that have not already been installed and integrity-verified. 8. Consider running the deployment process in a constrained subprocess or container with access only to the staging directory. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/drop-index.mjs:151
Finding
Deployed HTML and Claim Tokens Are Persisted in Plaintext Without Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `references/drop-index.mjs:151-171` **Vulnerability Type**: Plaintext storage of sensitive content and capability-bearing claim URLs **Risk Level**: Medium ### Vulnerable Code ```js const id = idFromUrl(url); const sha = sha256(html); // Content-addressed copy — write only if this content isn't already stored. const artifactPath = join(home, ARTIFACT_DIR, `${sha}.html`); if (!existsSync(artifactPath)) { writeFileSync(artifactPath, html); } const entry = { id, url, title, summary, claim_url: claimUrl, expires_at: expiryEpoch, deployed_at: now, sha256: sha, artifact: join(ARTIFACT_DIR, `${sha}.html`), ...(renewedFrom ? { renewed_from: renewedFrom } : {}), }; appendFileSync(join(home, INDEX_FILE), JSON.stringify(entry) + '\n'); ``` ### Technical Analysis Temporary deployment content is archived under `~/.cloudflare-drop/artifacts`, and the corresponding claim URL is appended to `index.jsonl`. The project does not set explicit restrictive directory or file modes, encrypt the data, enforce automatic deletion, or require separate consent for local persistence. The archived HTML may contain private reports, personal data, embedded application data, or credentials accidentally present in the page. A Cloudflare claim URL contains a capability-bearing token that may allow a holder to claim or manage the associated preview. The documentation mentions optional manual pruning, but the runtime does not enforce retention. Therefore, data can remain on disk after the temporary public deployment has expired. ### Attack Path 1. A user deploys a temporary preview containing sensitive or confidential material. 2. Following successful verification, `recordDeploy()` writes the complete deployed HTML to the local artifact directory. 3. The claim URL is stored in plaintext in `index.jsonl`. 4. A local process, malware, backup system, or another local user with filesystem access reads the stored fil ...[truncated 788 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the storage directory with mode `0700`. 2. Create HTML artifacts and the index file with mode `0600`, independent of the user's default umask. 3. Make archival opt-in and clearly disclose that page content will remain on disk after preview expiration. 4. Avoid persisting claim URLs unless they are strictly required for a supported workflow. 5. If claim URLs must be stored, use an operating-system credential store or authenticated encryption with protected key management. 6. Implement automatic expiry-based deletion for artifacts and metadata rather than relying solely on documented manual pruning. 7. Provide a command to securely remove an individual deployment record and associated unreferenced artifact. 8. Avoid placing secrets in static pages and perform a pre-deployment sensitive-file and content check. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/wrangler.mjs:92
Finding
Wrangler OAuth Credential File Is Renamed Through a Predictable Non-Recoverable Backup Path<![CDATA[ ## Vulnerability Details **File Location**: `references/wrangler.mjs:92-104` **Vulnerability Type**: Unsafe mutation of user authentication state **Risk Level**: Medium ### Vulnerable Code ```js // The OAuth pause is the only way to reach an anonymous temporary preview on a // machine that has a login it cannot use. It touches the USER'S credential file, // so it is guarded, opt-in, and restored in `finally` — a crash must never leave // the user logged out. const cfg = oauthConfigPath(env); const paused = mode === 'temporary' && allowPauseOAuth && existsSync(cfg); const parked = `${cfg}.paused-by-cloudflare-drop`; if (paused) renameSync(cfg, parked); let raw = ''; try { raw = run(args, { env }); } finally { if (paused && existsSync(parked)) renameSync(parked, cfg); } ``` ### Technical Analysis For temporary deployments, the code temporarily moves the user's Wrangler OAuth configuration to a fixed path ending in `.paused-by-cloudflare-drop`. Restoration in a `finally` block protects against ordinary JavaScript exceptions, but it does not protect against abrupt process termination, power loss, operating-system failure, or runtime crashes. The implementation also lacks: - A unique backup name - Collision detection for an existing parked file - An inter-process lock - Validation that the parked file is the original credential file - Startup recovery for interrupted operations - Explicit preservation checks for ownership and permissions Concurrent Skill executions or external processes using Wrangler can observe inconsistent authentication state. Mutating the user's credential file is also broader than necessary when an isolated configuration directory can be supplied to the subprocess. ### Attack Path 1. The user has an existing Wrangler OAuth configuration and no API token. 2. The Skill selects temporary mode and renames the OAuth file to the predictable parked path. 3. Before the `finally` block restores it, the process is forcefully term ...[truncated 1001 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not rename or otherwise mutate the user's existing OAuth configuration. 2. Run anonymous temporary deployments with a newly created isolated `WRANGLER_HOME` or equivalent configuration directory. 3. Pass the isolated location only to the Wrangler subprocess and delete it after completion. 4. If credential mutation remains unavoidable, require explicit user consent for each operation. 5. Use a securely created unique backup path, reject existing-file collisions, and acquire an inter-process lock. 6. Record sufficient recovery metadata and restore interrupted operations on the next startup. 7. Verify file ownership, type, and permissions before moving or restoring credentials. 8. Add tests for concurrent invocations, pre-existing parked files, forced termination recovery, and permission preservation. ]]>
Vulnerability Patterns
  • 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
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The code deliberately renames the user's local Wrangler OAuth config file to bypass authenticated mode and force an anonymous temporary deployment path. Even though it attempts to restore the file in a finally block, modifying credential state on disk exceeds the expected scope of 'deploy a static site' and creates risk of credential disruption, race conditions with other Wrangler processes, or leaving auth state altered if the process is terminated abruptly outside normal exception handling.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
references/wrangler.mjs:110