Figma-to-Reactor: agent-driven design translation with CLI watch + Figma plugin#178
Conversation
01d32c6 to
205b525
Compare
|
Security review notes for the Figma bridge/plugin changes: The Figma plugin is reasonably constrained in terms of Figma API permissions, but the local bridge substantially expands the trust boundary. I would recommend addressing the items below before merging, because the current design exposes selected Figma design data and local file/process operations through unauthenticated localhost endpoints. Main recommended actions:
Overall: the concept is workable, but it needs an explicit local-bridge security boundary. The key fix is to make the bridge a capability-bearing session (random token + restricted origins + scoped commands), and to treat design content as untrusted data before it reaches any agent or executable code path. |
codemonkeychris
left a comment
There was a problem hiding this comment.
See the security review comments... some of this may be bogus (might be limitations on how secure we can be, Figma may force us to be more open than we would want), but please add a security review to your spec, and harden as much as you can within the bounds of what Figma allows.
Re-architected the plugin. Got rid of the bridge architecture and am now exclusively relying on Figma's get_figma_data for live sync, so that should self resolve most of the security issues brought up by the agent. Let's see what the agent's security review is now. |
🔒 Security Threat Model — STRIDE AnalysisScope: PR #178 new features (CLI System DiagramTrust Boundaries
Findings
F-1: LLM code injection via Figma text content (🟠 Medium)The agent uses Figma design data to generate C# source code. A malicious Figma collaborator could craft text content (e.g., a text layer containing Mitigations already in place:
DREAD: Damage 3, Reproducibility 2, Exploitability 2, Affected 2, Discoverability 2 = 11/25 Recommend: Add a rule to F-2: ANSI escape injection in stderr (🟡 Low)Figma frame names flow into DREAD: Damage 1, Reproducibility 3, Exploitability 1, Affected 1, Discoverability 1 = 7/25 Recommend: Optionally strip non-printable characters from STRIDE Pass by Boundary (No Findings)TB-1: Network — FigmaClient → api.figma.com
TB-3: Config files → API key extraction
TB-4: Plugin clipboard → terminal shell
TB-5: Figma plugin sandbox
Rejected Candidates
Recommendations
|
251ccdc to
752015c
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The FigmaBridge relay server and figma-plugin had several security vulnerabilities: - Access-Control-Allow-Origin: * on localhost HTTP/WS endpoints - Arbitrary filesystem writes from unauthenticated WebSocket messages - Shell command injection via unsanitized paths to pwsh/dotnet - No authentication on any endpoint - Temporary .ps1 script generation with user-controlled values The bridge's only purpose (live push sync) is not needed for the primary URL-based workflow where the agent calls the Figma MCP server directly using a scoped personal access token. Removed: - tools/FigmaBridge/ (ASP.NET Core relay server) - tools/figma-plugin/ (Figma plugin source) Updated: - skills/figma.md: removed Mode B / bridge references - docs/specs/033-figma-to-reactor.md: added Appendix A documenting the security rationale for removal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the removed FigmaBridge with a secure polling-based approach. The agent runs 'mur figma watch <url>' as a background process, which polls the Figma REST API lastModified timestamp and emits JSON events to stdout when the design changes. The agent then re-fetches via the existing Figma MCP server (get_figma_data) — no bridge, no open ports. New files: - src/Reactor.Cli/Figma/FigmaClient.cs — Figma REST API client with URL parser (file_key + node_id extraction) and lastModified polling - src/Reactor.Cli/Figma/FigmaWatchCommand.cs — watch command with configurable interval, structured JSON stdout events, error recovery - src/Reactor.Cli/Figma/FigmaCommand.cs — mur figma dispatcher Security properties (vs old bridge): - No open ports (stdio only, no HTTP/WS server) - No CORS surface - Auth via FIGMA_API_KEY env var (same scoped PAT as Figma MCP) - No filesystem writes (stdout events only) - No shell execution from external input Updated: - skills/figma.md — added watch-based sync workflow - docs/specs/033-figma-to-reactor.md — replaced removal appendix with new architecture section (polling watch + future webhook gates) - 11 unit tests for URL parsing (all pass) Figma Webhooks V2 FILE_UPDATE has a 30-min inactivity debounce, making it unsuitable for live sync. FILE_VERSION_UPDATE (immediate, explicit) is documented as a future Tier 2 option for team workflows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rebuild the Figma plugin without the bridge server. The plugin now: - Shows the selected frame name, dimensions, and node ID - Provides a copyable 'mur figma watch' command for live sync - Generate button constructs a 'copilot' CLI command with the frame's Figma URL and copies it to clipboard - Run button copies 'dotnet watch run --project ...' command - Supports both new project (mur --create) and existing project paths Security: no network access (manifest: allowedDomains: none), no WebSocket, no bridge server. All commands are copy-paste to terminal. The plugin is purely a UI for constructing CLI invocations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Figma's plugin sandbox doesn't support the ?? operator. Downlevel tsconfig target from ES2020 to ES6 so TypeScript emits compatible ternary expressions instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…literals - Accept FRAME, COMPONENT, COMPONENT_SET, SECTION, and any node with children (GROUP, INSTANCE) — not just FRAME - Replace template literals with string concatenation (ES6 target emits them but older Figma sandboxes may choke) - Show diagnostic message when selection is rejected (node type) - Remove nullish coalescing from source (use || instead) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
figma.fileKey returns null in dev mode and for unsaved files, which caused figmaUrl to be null and the UI to disable all buttons even with a frame selected. Now: buttons enable when any frame is selected. If fileKey is unavailable, the watch command shows a hint to paste the URL manually, and Generate uses a placeholder the user can replace. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…allback figma.fileKey is only available to private org plugins — community and dev plugins get undefined. This commit: 1. Adds enablePrivatePluginApi: true to manifest.json (works for private org plugins) 2. Adds a URL input fallback: when fileKey is unavailable, the plugin shows a text field where the user pastes any Figma URL from the same file. The file key is extracted and persisted via clientStorage. 3. Once the file key is resolved (either way), the watch command and generate button populate normally. The URL only needs to be pasted once per file — clientStorage persists it across plugin sessions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rompt Generate button now copies a Start-Process command that opens a new pwsh terminal window and runs copilot CLI — no manual copy-paste of the prompt needed. Just paste the one-liner into any terminal. The prompt now instructs Copilot to read skills/figma.md and skills/design.md for translation rules, theme tokens, typography, and layout patterns — ensuring accurate Reactor code generation. Run button also uses Start-Process to launch dotnet watch in a new terminal window. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Start-Process approach launched a blank frozen terminal. Revert to copying a simple 'copilot -p ...' command the user pastes directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add explicit shell command and env var setup steps so the agent can follow the watch workflow without ambiguity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
skills/figma.md: - Expanded control mapping from 13 controls to 50+ covering the full Reactor DSL: BasicInput, DateAndTime, StatusAndInfo, Navigation, Layout, Collections, DialogsAndFlyouts, MenusAndToolbars, Media - Every entry uses the EXACT factory signatures from Dsl.cs — no approximate/incorrect parameter names - Added Button icon pattern, Accent/Subtle button styles matching ReactorGallery usage (AccentButtonStyle instead of Resources hack) figma-plugin ui.html: - Generate prompt now includes mur figma watch instruction — after generating code, the agent starts watching for changes automatically - Existing project mode focuses on watch+sync workflow: fetches current design, updates code, then watches for ongoing changes - Both modes reference skills/figma.md and skills/design.md for accurate pixel-fidelity translation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…caping Double quotes in the prompt text (from URLs with query params) broke shell parsing. Switch to PowerShell single-quote wrapping with '' escaping for any literal single quotes in the prompt. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Scaffolded via mur --create, translates Figma design NXvmCFnkYESqhSpEg4icEl (node 2017-2054) into a Reactor WinUI app with TitleBar, NavigationView, hero banner, section heading, and 3 cards using theme tokens. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
F-1 (Medium): Add rule 13 to figma.md requiring agents to sanitize Figma text content before embedding as C# string literals — escape quotes, backslashes, and strip control characters. F-2 (Low): Add SanitizeForStderr helper in FigmaWatchCommand that strips control characters (U+0000-U+001F except tab/newline) from Figma file names before writing to stderr, preventing ANSI escape injection. Test coverage: Add FigmaApiKeyResolverTests (8 tests covering mcpServers/servers/flat formats, env block, colon separator, malformed JSON, missing file) and FigmaWatchSanitizeTests (7 tests covering control char stripping, ESC sequences, null/bell chars). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
No way for the plugin to detect external process state, so the generating/running status dots were misleading. Buttons now stay in their default visual state. Re-generate label still updates when switching to existing project mode. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove samples/apps/FigmaApp (standalone sample app no longer needed) - Add tools/figma-plugin/README.md with plugin setup, build, install, and usage instructions covering the full workflow: frame selection, prompt generation, live sync via mur figma watch, and troubleshooting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
752015c to
63f121c
Compare
| foreach (var c in value) | ||
| { | ||
| if (c < '\u0020' && c != '\t' && c != '\n' && c != '\r') | ||
| continue; // strip control characters | ||
| sb.Append(c); | ||
| } |
| foreach (var arg in args.EnumerateArray()) | ||
| { | ||
| var argStr = arg.GetString(); | ||
| if (argStr == null) continue; | ||
|
|
||
| var match = Regex.Match(argStr, @"--figma-api-key[=:](.+)", RegexOptions.IgnoreCase); | ||
| if (match.Success) | ||
| return match.Groups[1].Value.Trim(); | ||
| } |
| catch | ||
| { | ||
| // Malformed config — silently skip | ||
| } |
| catch (Exception ex) | ||
| { | ||
| consecutiveErrors++; | ||
| Console.Error.WriteLine($"[mur figma watch] Error: {ex.Message}"); | ||
| if (consecutiveErrors >= 5) | ||
| { | ||
| Console.Error.WriteLine("[mur figma watch] Too many errors. Stopping."); | ||
| return 1; | ||
| } | ||
| } |
| var copilotConfig = Path.Combine( | ||
| Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), | ||
| ".copilot", "mcp-config.json"); |
| } | ||
|
|
||
| // 3. VS Code workspace MCP config (relative to cwd) | ||
| var vscodeConfig = Path.Combine(Directory.GetCurrentDirectory(), ".vscode", "mcp.json"); |
| public void TryExtract_NonexistentFile_ReturnsNull() | ||
| { | ||
| var key = FigmaApiKeyResolver.TryExtractFromMcpConfigFile( | ||
| Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".json")); |
|
|
||
| private static string WriteTempJson(string content) | ||
| { | ||
| var path = Path.Combine(Path.GetTempPath(), $"test-mcp-config-{Guid.NewGuid()}.json"); |
| public void TryExtract_NonexistentFile_ReturnsNull() | ||
| { | ||
| var key = FigmaApiKeyResolver.TryExtractFromMcpConfigFile( | ||
| Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".json")); |
Figma-to-Reactor Design Translation
End-to-end tooling for translating Figma designs (built with the Windows UI Kit) into Reactor C# code via AI agent workflows.
Architecture
What's included
Spec
docs/specs/033-figma-to-reactor.md— Full design spec covering architecture, layout IR, control mapping, token resolution, code generation, preview loop, validation model, and fidelity levels.Skills
skills/figma.md— Agent skill with Figma → Reactor translation rules: layout containers, WinUI control mapping (40+ controls), Theme token resolution, typography, corner radii, and two sync modes (one-shot URL paste + watch-based continuous sync).skills/design.md— Expanded withAppTheme.Register()guidance, corner radius resources, and theme-aware resource patterns.skills/design-docs/theme-aware-resources.md— Custom token registration viaAppTheme.Register().CLI:
mur figma watchsrc/Reactor.Cli/Figma/— Polling-based live sync. Watches a Figma file'slastModifiedtimestamp via the REST API and emits JSON events to stdout when the design changes. The agent reads these events and re-fetches via the Figma MCP server — no bridge, no WebSocket, no open ports.FigmaApiKeyResolver— Auto-discovers the Figma API key fromFIGMA_API_KEYenv var,~/.copilot/mcp-config.json, or.vscode/mcp.json.FigmaClient— Lightweight REST client with clear error messages for 429 rate limits vs 403 auth failures, plus retry+backoff.Figma Plugin
tools/figma-plugin/— Figma plugin (reactor-figma-sync) with compact native-density UI:copilot -pprompt with skill references, Figma URL, andmur figma watchbaked in. Auto-switches to existing mode after first generate.Program.cs, applies targeted changes only.dotnet watch runcommand (existing project mode only).Security model
allowedDomains: ["none"]— no network from Figma sandbox.Depends on