High risk. Don't ship without significant remediation.
Scanned 5/3/2026, 8:40:00 PMยทCached resultยทFast Scanยท88 rulesยทHow we decide โ
AIVSS Score
High
Severity Breakdown
0
critical
3
high
11
medium
0
low
MCP Server Information
Findings
This package carries significant security concerns with a D grade and safety score of 70/100, driven by 3 high-severity findings and 11 medium-severity issues. The primary risks stem from tool poisoning vulnerabilities (3 instances), server configuration weaknesses (8 instances), and potential resource exhaustion problems, which could allow attackers to compromise your system or degrade service availability. Installation is not recommended without thorough remediation of these vulnerabilities, particularly the high-severity tool poisoning risks.
No known CVEs found for this package or its dependencies.
figma-developer-mcp vulnerable to command injection in get_figma_data tool
Scan Details
Want deeper analysis?
Fast scan found 14 findings using rule-based analysis. Upgrade for LLM consensus across 5 judges, AI-generated remediation, and cross-file taint analysis.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasnโt shipped yet.
14 of 14 findings
14 findings
The CLI tool accepts --env flag to load environment variables from a custom .env file, but the description does not disclose this ENV MUTATION side effect of reading and loading environment variables into process.env via dotenv.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | |
| 3 | import { cli } from "cleye"; |
| 4 | import { getServerConfig, UsageError } from "./config.js"; |
Remediation
Either remove the undeclared side effect or amend the tool description + input schema to disclose it. Add machine-readable `destructiveHint`, `networkHint`, `filesystemHint` annotations when the MCP spec supports them.
The CLI tool with --noTelemetry flag initializes telemetry that makes NETWORK calls to PostHog for analytics, but the description does not disclose this TELEMETRY/ANALYTICS side effect when telemetry is enabled (default behavior).
Evidence
| 1 | #!/usr/bin/env node |
| 2 | |
| 3 | import { cli } from "cleye"; |
| 4 | import { getServerConfig, UsageError } from "./config.js"; |
Remediation
Either remove the undeclared side effect or amend the tool description + input schema to disclose it. Add machine-readable `destructiveHint`, `networkHint`, `filesystemHint` annotations when the MCP spec supports them.
release.yml downloads and executes mcp-publisher binary from remote GitHub URL without verification, enabling arbitrary code execution during MCP registry publish step.
Evidence
| 37 | - name: Install dependencies |
| 38 | run: pnpm install |
| 39 | if: ${{ steps.release.outputs.release_created }} |
| 40 | |
| 41 | - name: Type check |
| 42 | run: pnpm type-check |
| 43 | if: ${{ steps.release.outputs.release_created }} |
| 44 | |
| 45 | - name: Build |
| 46 | run: pnpm build |
| 47 | if: ${{ steps.release.outputs.release_created }} |
Remediation
Tool code must be static at registration time. Do not fetch and exec handler bodies from remote URLs. Do not mutate `server.tool` / `@mcp.tool` registrations post-startup. Pin a content hash and verify at load; reject drift. Version-bump and re-register through the normal publish flow, not in-process.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 193 | ); |
| 194 | console.log("- Authentication Method: Personal Access Token (X-Figma-Token)"); |
| 195 | } else { |
| 196 | console.log("- Authentication Method: Per-request X-Figma-Token header"); |
| 197 | } |
| 198 | console.log(`- FRAMELINK_PORT: ${port.value} (source: ${configSources.port})`); |
| 199 | console.log(`- FRAMELINK_HOST: ${host.value} (source: ${configSources.host})`); |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 40 | componentPropertyCount: number; |
| 41 | /** True if any node in the raw API response has non-empty `boundVariables`. */ |
| 42 | hasVariables: boolean; |
| 43 | /** Wall-clock ms spent on the Figma API fetch (network + parse). */ |
| 44 | fetchMs: number; |
| 45 | /** Wall-clock ms spent on the simplification walk. */ |
| 46 | simplifyMs: number; |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 62 | ): Promise<{ data: T; rawSize: number }> { |
| 63 | const { redactFromResponseBody = [], ...fetchOptions } = options; |
| 64 | try { |
| 65 | const response = await fetch(url, fetchOptions); |
| 66 | |
| 67 | if (!response.ok) { |
| 68 | const responseHeaders: Record<string, string> = {}; |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | # Contributing to Framelink MCP for Figma |
| 2 | |
| 3 | Thank you for your interest in contributing to the Framelink MCP for Figma! This guide will help you get started with contributing to this project. |
| 4 | |
| 5 | ## Philosophy |
| 6 | |
| 7 | ### Unix Philosophy for Tools |
| 8 | |
| 9 | This project adheres to the Unix philosophy: tools should have one job and few arguments. We keep our tools as simple as possible to avoid confusing LLMs during calling. Configurable options that are more project-level (i.e., unlikely to change between requests |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | <a href="https://www.framelink.ai/?utm_source=github&utm_medium=referral&utm_campaign=readme" target="_blank" rel="noopener"> |
| 2 | <picture> |
| 3 | <source media="(prefers-color-scheme: dark)" srcset="https://www.framelink.ai/github/HeaderDark.png" /> |
| 4 | <img alt="Framelink" src="https://www.framelink.ai/github/HeaderLight.png" /> |
| 5 | </picture> |
| 6 | </a> |
| 7 | |
| 8 | <div align="center"> |
| 9 | <h1>Framelink MCP for Figma</h1> |
| 10 | <h3>Give your coding agent access to your Figma data.<br/>Implement designs in any framework in |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
File registers a state-changing HTTP route (POST / PUT / PATCH / DELETE) but no CSRF protection middleware is applied anywhere in the file. If the server uses cookie-based session auth, a cross-site request from any origin can hit this route while the user's cookies ride along. Apply CSRF middleware: - Express: `csurf` / `csrf-csrf` / `lusca.csrf()` - FastAPI: `fastapi-csrf-protect` - Flask: `flask_wtf.csrf.CSRFProtect` Or, if the route is a JSON API authenticated by bearer tokens (no co
Evidence
| 1 | import { type NextFunction, type Request, type Response } from "express"; |
| 2 | import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; |
| 3 | import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js"; |
| 4 | import { Server } from "http"; |
| 5 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 6 | import { ProxyAgent, EnvHttpProxyAgent, setGlobalDispatcher } from "undici"; |
| 7 | import { Logger } from "./utils/logger.js"; |
| 8 | import { hasProxyEnv, |
Remediation
Apply CSRF middleware at the route or router level: - Express: `app.use(csurf())` / `csrf-csrf` package - FastAPI: `fastapi-csrf-protect` with `Depends(...)` - Flask: `CSRFProtect(app)` from `flask_wtf.csrf` Or move to bearer-token auth and set `SameSite=Strict` / `SameSite=Lax` on any session cookies. Document the choice in the project README so reviewers can confirm intent.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 28 | version: 10.10.0 |
| 29 | if: ${{ steps.release.outputs.release_created }} |
| 30 | |
| 31 | - uses: actions/setup-node@v4 |
| 32 | with: |
| 33 | node-version: 24 |
| 34 | registry-url: "https://registry.npmjs.org" |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 17 | env: |
| 18 | FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true |
| 19 | steps: |
| 20 | - uses: googleapis/release-please-action@v4 |
| 21 | id: release |
| 22 | |
| 23 | - uses: actions/checkout@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 23 | - uses: actions/checkout@v4 |
| 24 | if: ${{ steps.release.outputs.release_created }} |
| 25 | |
| 26 | - uses: pnpm/action-setup@v4 |
| 27 | with: |
| 28 | version: 10.10.0 |
| 29 | if: ${{ steps.release.outputs.release_created }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 10 | name: Lint, Type Check, Test |
| 11 | runs-on: ubuntu-latest |
| 12 | steps: |
| 13 | - uses: actions/checkout@v4 |
| 14 | - uses: ./.github/actions/setup |
| 15 | |
| 16 | - name: Scan for hidden characters |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 20 | - uses: googleapis/release-please-action@v4 |
| 21 | id: release |
| 22 | |
| 23 | - uses: actions/checkout@v4 |
| 24 | if: ${{ steps.release.outputs.release_created }} |
| 25 | |
| 26 | - uses: pnpm/action-setup@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc