Back to skill

Security audit

Zeplin to Prompt

Security checks for vulnerabilities and agentic risk

Overview

This skill appears to export Zeplin designs as claimed, but it handles a personal access token and includes unsafe local command-launching paths that require review before use.

Install only if you are comfortable giving the skill access to the relevant Zeplin project. Prefer providing ZEPLIN_TOKEN through an environment variable or other local secret mechanism instead of pasting it into chat, review and delete ~/.zeplin-skill-config.json when finished, and use --no-open when running the scripts directly. Avoid attacker-controlled --workdir paths until the shell-based open calls are fixed.

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

Error
Location
export_screen.mjs:87
Finding

Command Injection Through the Single-Screen Export Output Path

Content
View full analysis
{ try { execSync(`open "${targetPath.replace(/"/g, '\\"')}"`); } catch (err) { log(`Warning: failed to open ${targetPath}: ${err?.message || err}`); } }; ``` ### Technical Analysis The `targetPath` value is interpolated into a command string passed to `execSync`, which executes the command through a shell. The only attempted protection escapes double quotation marks. This does not prevent shell expansions that remain active inside double-quoted strings, including command substitution with `$()` and backticks. A caller can control the output path through the `--workdir ` argument. That path is subsequently supplied to `openInFinder` unless the caller explicitly uses `--no-open`. Consequently, a crafted directory name can inject a shell expression even though the path is enclosed in double quotes. For example, a path containing `$(touch /tmp/export-pwned)` would cause the shell to execute `touch /tmp/export-pwned` while evaluating the command string. ### Attack Path 1. An attacker persuades a user or automation process to run the exporter with a malicious `--workdir` value. 2. The value is accepted without restricting shell metacharacters and is resolved into the export path. 3. The export completes without the `--no-open` option. 4. `openInFinder` interpolates the attacker-controlled path into an `execSync` command string. 5. The shell evaluates command substitution embedded in the path. 6. The injected command executes with the same operating-system privileges as the Node.js exporter. Example: ```bash ZEPLIN_TOKEN="..." node export_screen.mjs PROJECT_ID SCREEN_ID \ --workdir '/tmp/export-$(touch /tmp/export-pwned)' ``` ### Impact Assessment Successful exploitation provides arbitrary command ...[truncated 421 chars]
Remediation
View remediation
{ try { execFileSync("open", [targetPath], { shell: false, stdio: "ignore" }); } catch (err) { log(`Warning: failed to open the generated output: ${err?.message || err}`); } }; ``` Additional hardening should include: - Keep `--no-open` as the default behavior for automated or agent-driven execution. - Validate that a supplied work directory resolves beneath an explicitly permitted export root when arbitrary destinations are unnecessary. - Never attempt to secure shell commands solely by escaping selected metacharacters. - Avoid including attacker-controlled paths in error messages when logs may be consumed by other parsers. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
export_project.mjs:193
Finding

Command Injection Through the Project Export Output Path

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
lib/assets.mjs:29
Finding

Unrestricted Asset Downloads Permit HTTPS SSRF and Resource Exhaustion

Content
View full analysis
{ const { statusCode, headers } = res; if (statusCode && statusCode >= 300 && statusCode < 400 && headers.location) { if (redirectCount >= MAX_DOWNLOAD_REDIRECTS) { reject(new Error(`Too many redirects for ${url}`)); return; } res.destroy(); fs.unlink(tempDestination, () => { downloadToFile(headers.location, destination, redirectCount + 1).then(resolve).catch(reject); }); return; } if (statusCode && statusCode >= 400) { res.resume(); fs.unlink(tempDestination, () => reject(new Error(`Request failed with status ${statusCode} for ${url}`))); return; } res.pipe(fileStream); fileStream.on("finish", () => { fileStream.close(() => { fs.rename(tempDestination, destination, (err) => { if (err) { reject(err); return; } resolve(destination); }); }); }); }; fileStream.on("error", (err) => { fs.unlink(tempDestination, () => reject(err)); }); try { https.get(url, handleResponse).on("error", (err) => { fs.unlink(tempDestination, () => reject(err)); }); } catch (err) { fs.unlink(tempDestination, () => reject(err)); } ``` The remote URL is taken directly from asset metadata: ```js if (!content?.url) continue; const formatRaw = content.format || content.type; const format = formatRaw ? String(formatRaw).toLowerCase() : "png"; if (format !== "png") continue; try { await downloadToFile(content.url, destination); if (verbose) log(` -> Downloaded asset density=${density} -> ${destination}`); const relPath = toPosixPath(path.relative(workdir, destination)); record.localFiles[density] = { relPath, absPath: destination, format, sourceUrl: content.url, f ...[truncated 2473 chars]
Remediation
View remediation
Vulnerability Patterns
  • 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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (22)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description promises a tool that takes Zeplin screen URLs, exports one or more screens into a structured layer tree with local assets, and packages the result as a zip file. This code chunk does something materially different: it operates on DOM elements representing an existing preview/layer description, supports hover/selection, multi-select, highlight syncing between canvas and description panel, context-menu copying of prompt payloads, and editing/storing custom per-layer prompts in localStorage. It serializes selected layers/subtrees to JSON or a minified schema and copies them to the clipboard, but there is no code for ingesting Zeplin URLs, downloading screens/assets, writing files, or building a zip archive. The primary purpose shown here is an interactive preview and prompt-copy client, not a Zeplin export pipeline. Therefore the description does not accurately represent the supplied code chunk.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
98% confidence
Finding

The skill explicitly asks the user to create and send a Zeplin personal access token into the conversation flow. Requesting raw credentials through chat is dangerous because it normalizes secret sharing with the agent and increases the chance of token leakage via logs, transcripts, screenshots, or downstream tool misuse.

Content

Scanner excerpt · SKILL.md (reported line 67)May include surrounding context.

  • Exit code 1: reply with
text
No access token was found for project {projectId_masked}.

Please provide a Zeplin Personal Access Token for this project:
1. Open Zeplin -> avatar menu -> Profile Settings

Credential Access

High
Category
Privilege Escalation
Confidence
98% confidence
Finding

The instruction to provide a Zeplin Personal Access Token directly to the skill is a credential collection pattern. In the context of an agent skill, this is especially risky because the token may be retained in conversation history or accessible to other tooling, enabling unauthorized access to the user's Zeplin projects.

Content

Scanner excerpt · SKILL.md (reported line 69)May include surrounding context.

text
No access token was found for project {projectId_masked}.

Please provide a Zeplin Personal Access Token for this project:
1. Open Zeplin -> avatar menu -> Profile Settings
2. Open Personal access tokens -> Create new token
3. Copy the token and send it back

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

Telling the user to create a new personal access token and then copy and send it back is direct credential harvesting behavior, even if intended for legitimate access. The surrounding note 'Never print tokens in the conversation' does not mitigate the core issue that the skill still solicits the secret through the conversational channel.

Content

Scanner excerpt · SKILL.md (reported line 71)May include surrounding context.

Please provide a Zeplin Personal Access Token for this project:

  1. Open Zeplin -> avatar menu -> Profile Settings
  2. Open Personal access tokens -> Create new token
  3. Copy the token and send it back
text

Known Vulnerable Dependency: axios==1.12.2 — 16 advisory(ies): CVE-2026-44494 (axios Vulnerable to Full Man-in-the-Middle via Prototype Pollution Gadget in `co); CVE-2026-44495 (axios Vulnerable to Credential Theft and Response Hijacking via Prototype Pollut); CVE-2025-62718 (Axios has a NO_PROXY Hostname Normalization Bypass that Leads to SSRF) +13 more

High
Category
Supply Chain
Confidence
96% confidence
Finding

The lockfile pins axios 1.12.2, which the supplied advisory data marks as affected by multiple issues including SSRF-related proxy bypass and prototype-pollution-based MITM/credential theft scenarios. This skill fetches remote Zeplin resources from user-supplied screen URLs, so an HTTP client flaw is security-relevant because outbound requests, redirects, proxy handling, and response trust are part of the core behavior.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: form-data==4.0.4 — 1 advisory(ies): CVE-2026-12143 (form-data: CRLF injection in form-data via unescaped multipart field names and f)

High
Category
Supply Chain
Confidence
80% confidence
Finding

form-data 4.0.4 is reported as vulnerable to CRLF injection through unescaped multipart field names/filenames. If this skill ever constructs multipart requests using user-influenced names, URLs, or asset metadata while exporting or uploading artifacts, an attacker may be able to manipulate request structure or smuggle unintended headers/content.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The markdown shows commands that require ZEPLIN_TOKEN and invoke an export process against a Zeplin URL, which implies authenticated network access and handling of potentially sensitive design data. The README does not include any warning about protecting the token, avoiding shell history leakage, or the fact that project data will be fetched and written locally.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill instructs the user to provide a Zeplin personal access token and then stores it locally in ~/.zeplin-skill-config.json without explicitly warning the user that the credential will be persisted on disk. This creates a credential-handling risk because users may disclose long-lived secrets without informed consent, and those tokens may later be exposed through local compromise, backups, or overly broad file access.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · export_project.mjs (reported line 48)May include surrounding context.

js
}

const log = createLogger(QUIET);
const ZEPLIN_API = "https://api.zeplin.dev/v1";
const apiHeaders = { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" };

// Zeplin API helpers

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script builds Authorization headers from ZEPLIN_TOKEN and sends them in HTTP requests to the Zeplin API. While the code exits if the token is missing, there is no user-facing warning, comment, or disclosure near the network transmission about sending credentials and project data to an external service.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script launches a local HTML file using a shell command via execSync, which exceeds the core export function and creates an unnecessary execution surface. Even though the path is quote-escaped, invoking a shell to open generated content can trigger unintended local application execution and is especially risky because the HTML is derived from remote Zeplin content.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code invokes a local OS command via execSync to open Finder/browser windows, introducing unnecessary command execution capability for a task that should only export design data. Although the path is quoted and internal, shelling out still expands the attack surface and can trigger unintended local actions without clear user consent.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill goes beyond a pure export operation by generating an HTML artifact and then automatically opening local paths, which creates side effects not clearly implied by the stated purpose. Even though the HTML is generated from Zeplin-derived content rather than obvious attacker-supplied shell input, auto-launching rendered content can expose users to unexpected browser execution, local file handling risks, and trust-boundary violations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The helper issues HTTPS requests to arbitrary asset URLs and writes the responses to local files, but there is no confirmation prompt and no user-facing disclosure in comments or docstrings about these side effects. While internal logging exists elsewhere for verbose mode and failures, this function itself silently performs network and filesystem operations that affect user data and system state.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The template inserts meta.preview directly into the generated HTML without escaping or sanitization, which allows arbitrary HTML and script-capable markup to be embedded in the exported document. If meta.preview can contain untrusted Zeplin-derived content or attacker-controlled data, opening the generated file can trigger stored/self-XSS, leading to script execution in the browser context, data theft from the page, or tampering with copied prompts and local state.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The code extracts image.assetName, image.url, or image.originalUrl and later stores the resulting identifier in the minified output. This can propagate external URLs or asset references, but the file contains no confirmation prompt, warning comment, or user-facing disclosure about including such data.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The generated document sets lang="zh-CN" unconditionally, which forces a specific language/locale in the output for all users. This is a natural-language policy concern because the file provides no user opt-in, fallback, or justification that the skill is region-specific.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The code creates output directories under a build path or a user-supplied workdir, and later writes index.html and per-screen exports. Although the output path is logged after creation, there is no prior warning or descriptive comment making the file-writing behavior explicit for users running the skill.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

Generated files and directories are opened automatically without prior warning or confirmation, which is a risky default for a local automation tool. This can surprise users, leak sensitive project names or paths on-screen, and cause unreviewed rendering of generated HTML content.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: follow-redirects==1.15.11 — 1 advisory(ies): CVE-2026-40895 (follow-redirects leaks Custom Authentication Headers to Cross-Domain Redirect Ta)

Low
Category
Supply Chain
Confidence
84% confidence
Finding

follow-redirects 1.15.11 is flagged for leaking custom authentication headers across cross-domain redirects. In a skill that likely uses API tokens to access Zeplin and may follow remote asset redirects, this can expose bearer tokens or other secrets to unintended domains if a redirect chain is malicious or compromised.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
89% confidence
Finding

The dependency uses a caret range, which allows newer minor/patch versions of @zeplin/sdk to be installed without explicit review. This creates supply-chain risk because a compromised or breaking upstream release could be pulled into future installs, which is relevant for a skill that processes external Zeplin content and may handle local asset export.

Content

Scanner excerpt · package.json (reported line 13)May include surrounding context.

json
"license": "ISC",
  "description": "",
  "dependencies": {
    "@zeplin/sdk": "^1.34.0",
    "dotenv": "^17.2.3"
  }
}

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
90% confidence
Finding

The dotenv dependency is also specified with a caret range, allowing automatic uptake of newer releases that have not been vetted by the project. While common in development, this still introduces avoidable supply-chain exposure if an upstream package release is malicious or vulnerable.

Content

Scanner excerpt · package.json (reported line 14)May include surrounding context.

json
"description": "",
  "dependencies": {
    "@zeplin/sdk": "^1.34.0",
    "dotenv": "^17.2.3"
  }
}