Back to skill

Security audit

Figma Sync

Security checks for vulnerabilities and agentic risk

Overview

This Figma sync skill is mostly purpose-aligned, but it needs review because unsafe cache path handling and weak write-back validation could cause unintended local writes or malformed Figma plugin operations.

Review before installing. Use only trusted Figma file keys and patch specs, run it in a constrained project directory, avoid using --execute unless you understand the companion plugin flow, and treat Web React + Tailwind support as incomplete. The Figma token use is expected, but the cache path and pluginSpec validation should be hardened before broad use.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/figma_common.py:34
Finding
Unsanitized Figma File Key Allows Cache Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/figma_common.py:34-50` **Vulnerability Type**: Path traversal and unintended filesystem writes **Risk Level**: Medium ### Vulnerable Code ```python def cache_path(file_key: str) -> Path: p = CACHE_DIR / file_key p.mkdir(parents=True, exist_ok=True) return p def api_get(path: str, file_key: str = "", use_cache: bool = True, params: dict = None) -> dict: """GET from Figma API with retry, backoff, and ETag caching.""" url = f"{FIGMA_API}{path}" hdrs = headers() etag_file = None cache_file = None if use_cache and file_key: cp = cache_path(file_key) safe = path.replace("/", "_").strip("_") cache_file = cp / f"{safe}.json" etag_file = cp / f"{safe}.etag" ``` The response and ETag are subsequently written to these paths: ```python if cache_file: cache_file.write_text(json.dumps(data, sort_keys=True)) if etag_file and "ETag" in resp.headers: etag_file.write_text(resp.headers["ETag"]) ``` ### Technical Analysis The `file_key` value originates from the command-line `--file-key` argument and is directly combined with `.figma-cache` as a filesystem path. The implementation does not reject absolute paths, path separators, or traversal components such as `..`. In Python, combining a `Path` with an absolute second operand discards the preceding base path. Relative traversal components can similarly cause the resolved path to leave `.figma-cache`. A successful API request can therefore cause response JSON and ETag data to be written outside the intended cache boundary. The generated filenames are derived from the API path and have fixed `.json` or `.etag` suffixes, which limits arbitrary filename control but does not prevent unauthorized directory creation or unintended file replacement. ### Attack Path 1. An attacker or untrusted automation supplies a crafted value such as an absolute path or a file key containing `../`. 2. A p ...[truncated 923 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `file_key` against the documented Figma key format and reject path separators, absolute paths, empty values, and traversal components. 2. Use a filesystem-safe cache identifier, such as a SHA-256 digest of the file key, rather than the raw value. 3. Resolve the candidate cache path and verify that it remains under the resolved cache root before creating directories or writing files. 4. Apply the same containment validation to cache filenames derived from API paths. 5. Use restrictive directory and file permissions where supported. Example hardening: ```python def cache_path(file_key: str) -> Path: if not re.fullmatch(r"[A-Za-z0-9_-]+", file_key): raise ValueError("Invalid Figma file key") root = CACHE_DIR.resolve() cache_id = hashlib.sha256(file_key.encode()).hexdigest() target = (root / cache_id).resolve() if target.parent != root: raise ValueError("Cache path escapes cache directory") target.mkdir(parents=True, exist_ok=True) return target ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/figma_push.py:33
Finding
Malformed and Unsupported Operations Are Exported Despite Validation Errors<![CDATA[ ## Vulnerability Details **File Location**: `scripts/figma_push.py:33-57` and `scripts/figma_push.py:154-161` **Vulnerability Type**: Fail-open validation of plugin operation specifications **Risk Level**: Medium ### Vulnerable Code ```python def validate_operation(op: dict) -> list: """Validate a single operation. Returns list of warnings.""" warnings = [] op_type = op.get("type", "") if op_type not in SUPPORTED_OPS: warnings.append(f"Unknown operation type: {op_type}") if not op.get("nodeId") and op_type not in ("createComponent", "createFrame"): warnings.append(f"Operation {op_type} missing nodeId") return warnings def validate_patch_spec(patch_spec: dict) -> tuple: """Validate entire patch spec. Returns (valid_ops, all_warnings).""" ops = patch_spec.get("operations", []) valid = [] all_warnings = [] for i, op in enumerate(ops): warnings = validate_operation(op) if warnings: for w in warnings: all_warnings.append(f"Op[{i}]: {w}") valid.append(op) return valid, all_warnings ``` Every operation, including one that generated validation warnings, is later exported: ```python plugin_spec = { "version": "1.0.0", "fileKey": file_key, "operations": ops, "generatedAt": datetime.now(timezone.utc).isoformat(), } write_json(out / "pluginSpec.json", plugin_spec) ``` ### Technical Analysis `validate_patch_spec()` names its result `valid`, but it appends every operation unconditionally. Unknown operation types, missing node identifiers, and other malformed data therefore only produce warnings; they are not rejected or removed. The resulting operation list is written to `pluginSpec.json`, which is intended to be loaded by a companion Figma plugin. This creates a fail-open trust boundary: untrusted patch contents that the Python code recognizes as invalid are still delegated to another execution environment. Validation is al ...[truncated 1594 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Fail closed: do not append an operation when validation produces an error. 2. Distinguish fatal validation errors from informational warnings. 3. Reject the complete patch before generating `pluginSpec.json` if any fatal error exists. 4. Define strict, operation-specific schemas covering: - Allowed operation types - Required and optional fields - Field data types - Node identifier format - Maximum string and collection sizes - Numeric ranges - Rejection of unknown fields 5. Require the companion plugin to repeat the same validation before performing any mutation. 6. Consider cryptographically signing generated specifications if they cross an untrusted storage or transport boundary. 7. Clearly report that zero operations were exported when all supplied operations are invalid. A minimal fail-closed change would be: ```python for i, op in enumerate(ops): warnings = validate_operation(op) if warnings: for warning in warnings: all_warnings.append(f"Op[{i}]: {warning}") continue valid.append(op) ``` The caller should then serialize only `valid` and preferably abort when `all_warnings` contains fatal validation errors. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (8)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The code clearly matches part of the description: it reads Figma data, extracts tokens/styles, generates a normalized design model, produces React Native Expo code, and downloads/export assets. However, several declared capabilities are absent or materially underimplemented in this chunk. There is no logic to push changes back to Figma, no diffing of local models against Figma, and no minimal patch generation. Also, the declared web output specifically says 'Web React + Tailwind code,' but the implemented web generator returns only a skeletal React component with a TODO comment and no Tailwind conversion. Therefore the description overstates the implemented behavior for this supplied code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The declared description presents a multi-capability Figma sync skill covering read, extract, generate, push, and diff workflows. This specific code chunk is much narrower: it validates a patch spec, fetches existing node state, logs operations, and emits pluginSpec.json and pushResult.json. Its own docstring explicitly states that the Figma REST API is read-only for file content and that actual node mutations require a companion plugin. That means the chunk partially aligns with the 'push to figma' portion, but not with the broader declared functionality, and even that push behavior is indirect rather than true direct write-back. This is a material description-to-behavior mismatch for the supplied code chunk.

Credential Access

High
Category
Privilege Escalation
Content
## Authentication

Set `FIGMA_TOKEN` env var with a Personal Access Token or OAuth token.

```
Authorization: Bearer <FIGMA_TOKEN>
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill advertises operations that require sensitive capabilities—reading environment variables, network access to Figma, and local file read/write—but does not declare any explicit tool scope or permissions boundary. In an agent environment, that increases the chance the skill will be invoked with broader-than-necessary authority, enabling accidental token exposure, unauthorized filesystem access, or unintended outbound requests.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code calls the Figma API to export images and then fetches the returned image URLs with requests.get, which is a network operation that transmits the supplied file key and requests remote assets. While the script logs progress, it does not provide any user disclosure about the external network access or downloaded asset retrieval beyond routine internal logging.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The skill description explicitly includes generating "Web React + Tailwind code," which implies functional code generation from Figma designs. In this file, the web path produces only a minimal component skeleton with a TODO, so the actual behavior falls short of the described capability.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The manifest says the skill can generate "Web React + Tailwind code," but this function returns only a placeholder React component with a TODO comment and no Tailwind output. The docstring reinforces that this is a generator, while the actual behavior is effectively a stub, creating a direct contradiction between documented intent and implementation.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The script creates output directories and writes designModel.json, tokens.json, codePlan.json, generated component files, and downloaded PNG assets. Although these writes are part of the tool's purpose, this file does not include a clear disclosure in comments, docstrings, or CLI help that running it will modify the filesystem under the chosen output directory.

Static analysis

No suspicious patterns detected.