Skip to content

appsec: authN/authZ audit #7

Description

@fuzeone-bot

AppSec audit: authN / authZ / FuzeFront-auth compliance / input validation

Scope audited: all HTTP/event handlers. The entire HTTP surface lives in bridge-server.js (FigmaMcpBridgeServer, default port 3001 / launched on 3015). Other .js files (cursor-mcp-client.js, test.js, analyze-design.js, get-pages.js, search-elements.js, design-system-analyzer.js) are HTTP clients; code.js is the Figma plugin sandbox that fetches the bridge — no server handlers there.

Endpoints (bridge-server.js):

  • GET /mcp/sse — SSE stream; emits server-info
  • POST /mcp/request — MCP JSON-RPC (initialize, tools/list, tool calls forwarded to plugin)
  • POST /plugin/connect — flips figmaConnected = true
  • GET /plugin/get-requests — returns ALL pending MCP requests
  • POST /plugin/send-response — submits a response for any requestId
  • GET /status, GET /health

Findings

CRITICAL — No authentication on any route (bridge-server.js:51-95 handleRequest dispatch; all handle* methods).
Every endpoint is fully unauthenticated. Combined with Access-Control-Allow-Origin: * (bridge-server.js:53) and manifest networkAccess.allowedDomains: ["*"], any web page or local process the user visits can drive the bridge and, through the connected plugin, read/manipulate the user's Figma document (create/delete pages & nodes, exfiltrate document contents). There is no token, no origin allowlist, no localhost-binding enforcement (server listens on all interfaces).

HIGH — Missing object/field-level authZ (BOLA/BOPLA): GET /plugin/get-requests (handlePluginGetRequests, ~line 300) and POST /plugin/send-response (handlePluginSendResponse, ~line 315).

  • get-requests returns every queued MCP request (Array.from(this.pendingPluginRequests.values())) to any caller — no per-caller scoping. Queued tool payloads / design data leak to any client.
  • send-response accepts an attacker-chosen requestId and resolves the matching pending MCP request with attacker-controlled content — no binding between the responder and the original requester. This is cross-client response injection / forgery (BOLA on requestId).
  • POST /plugin/connect lets any caller set the global figmaConnected flag with no shared secret, enabling spoofing of the plugin side.

HIGH — FuzeFront-auth non-compliance (architecture-guidelines §1).
The product implements no authN/authZ and does not integrate FuzeFront for identity. Note: there is no local password store and no self-minted user JWT (no bcrypt/argon2/jwt.sign), so this is not a local-credential/self-token violation — it is absence of the mandated FuzeFront auth. The bridge must sit behind FuzeFront authN/authZ (or, if it is strictly a localhost developer tool, that boundary must be enforced and documented).

MEDIUM — Input validation absent (processMcpRequest ~line 165; handlePluginSendResponse).
Request bodies are JSON.parse'd then fields used unchecked: request.params.name (request.params may be undefined → unhandled crash / DoS), request.method, and { requestId, response } are never schema-validated. No size limits on streamed bodies (req.on('data') concatenation is unbounded → memory DoS).

Concrete fixes

  1. AuthN: require a per-session bearer token (generated by the plugin at startup, shown in the plugin UI, sent as Authorization: Bearer); reject all requests lacking it. Bind the listener to 127.0.0.1 only. Replace Access-Control-Allow-Origin: * with an explicit allowlist (or drop CORS for a localhost-only tool) and tighten manifest allowedDomains.
  2. AuthZ (BOLA/BOPLA): scope pendingPluginRequests to the authenticated plugin session; get-requests/send-response must only see/affect requests owned by the caller's session. Validate that requestId belongs to the caller before resolving. Gate /plugin/connect behind the same session token.
  3. FuzeFront compliance: integrate FuzeFront authN/authZ for the control plane, OR formally classify this as a localhost-only dev tool with enforced loopback binding + token, and record the exception per architecture-guidelines §1.
  4. Input validation: validate JSON-RPC envelope and each handler's body against a schema (method/params/requestId types, required fields); cap request body size; guard request.params before dereferencing .name.

Severity counts: Critical 1, High 2, Medium 1.
Owner: backend-engineer (do NOT implement here — tracked for the responsible engineer).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions