Light It Up: Agentic Guardrail Bypass to Code Execution in FastLED's MCP Server (CVE-2026-78812)

FastLED is the library you reach for when you want LEDs to do cheerful things. Its repository also ships an MCP server that will run a command on your machine if an AI agent asks it to.
Suppose there is an AI agent, encountering an external instruction to run a "setup" command. The agent passes it to FastLED's MCP server, which executes it as the developer, with their source code, tokens, and SSH keys in reach. The one guardrail the project has for this lives in a different layer, matches on text, and does not recognize a Python one-liner carrying a hex-encoded payload.
CVE: CVE-2026-78812 · Severity: High (CVSS 4.0: 8.5 · CVSS 3.1: 7.8) · CWE: CWE-78, OS Command Injection · Affected: FastLED/FastLED mcp_server.py at commit ff853d5f3f8ee16d4a55e8f3dd5a6c0e30593f39 (March 14, 2026). run_specific_command is unchanged in this respect on master as of commit f1efdf0 (October 20, 2026).
What Is FastLED, and Its MCP Server?
FastLED is a widely used library for driving addressable LEDs on Arduino and other microcontrollers. Alongside the library, the repository ships mcp_server.py, an MCP (Model Context Protocol) server for AI-assisted development of the project itself, registered for Cursor in .cursor/mcp.json. It gives AI agents a set of project tools for running tests, compiling examples, linting, and a general-purpose command runner. The file's header is addressed to those agents. It tells them how to run Python and shell commands, and to ask before deleting anything.
The command runner is run_specific_command. It takes a command string and an optional working_directory and runs the command in the project context. Giving an agent a way to run project commands is a reasonable thing to want. The question is what stands between that tool and content the agent should not trust.
The Vulnerability
Root Cause
run_specific_command is meant to run commands, so command execution itself is not the bug. The bug is that nothing that holds sits between untrusted content and that call. Two things combine.
1. The server executes whatever it is given
The whole handler is a split and an exec:
command = arguments["command"]
working_directory = arguments.get("working_directory", ".")
work_dir = project_root / working_directory
result = await run_command(command.split(), work_dir)
run_command hands that list to asyncio.create_subprocess_exec:
process = await asyncio.create_subprocess_exec(
*cmd,
cwd=cwd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
)
There is no shell, which rules out classic metacharacter injection. There is also no allowlist of executables, no validation of arguments, and no restriction on the working directory beyond joining it to the project root.
2. The guardrail is a string filter in another layer
The repository's .claude/settings.json registers a PreToolUse hook with matcher *, which runs ci/hooks/check_forbidden_commands.py on every tool call, MCP calls included. The hook reads the command field and checks it against a short list of forbidden build tools (ninja, meson, gcc, clang, gdb, and similar) plus a few regular expressions, including patterns meant to catch those tools in the middle of a chained command. This is a (perfectly sensible) hygiene check for keeping agents away from the wrong build tools.
The Bypass
The hook matches on text and the handler splits on whitespace, so a payload that contains no forbidden words and no whitespace passes both. In shape:
python3 -c exec(bytes.fromhex('<hex-encoded payload>').decode())
The real command exists only inside the encoded string, so the hook never sees it. The absence of spaces is deliberate: it keeps the whole expression as a single argument after command.split().
In the proof of concept, the decoded payload ran whoami and wrote the result to a file in the working directory. The same technique can chain any further commands, including the ones the hook exists to stop.
Impact
Execution of arbitrary commands as the user running the MCP server. The practical risk is to developers who connect AI coding agents to this server on a machine holding source code, tokens, or other credentials.
Fix Status
No fix known. On master (commit f1efdf0, October 20, 2026), run_specific_command still splits the string and executes it with no validation.
The tool's purpose is to run project commands. The vulnerability is that it is reachable from untrusted content, through an AI agent or another MCP client, with no control in the server that holds, and the one check in the repository is trivially evaded.
Disclosure Timeline
| Date | Event |
|---|---|
| Mar 30, 2026 | Vulnerability reported to the maintainer by direct message, using the contact listed in the repository's security policy |
| Jun 13, 2026 | Maintainer acknowledged the report and chose not to publish a formal GitHub security advisory |
| Sep 2, 2026 | CVE-2026-78812 assigned by MITRE |
| Oct 21, 2026 | Advisory published |
What You Can Do With This
If you run FastLED's MCP server:
- Don't connect it to an agent that processes untrusted content on a machine that holds secrets. Treat the agent's tool calls as untrusted.
- Run it in a container or VM with no credentials mounted.
- Require human approval for each command the agent proposes, and read what it actually runs.
- Don't treat the repository's hook as a security control. It keeps agents off the wrong build tools; it does not stop a determined payload.
- If you only use FastLED as a library, you are not affected.
If you build MCP servers that execute commands:
- Put the boundary in the server. A check in the client or agent layer protects only the clients that run it. The server is the only component that sees every call.
- Don't filter command strings. Any check that matches patterns in text can be defeated by encoding, quoting, or an interpreter that builds the real command at runtime.
- Allowlist executables and validate arguments. Resolve the binary by full path and accept only arguments you expect. A tool that must run an interpreter on caller-supplied code is a code execution tool and should be documented as one.
- Ask whether a general-purpose command tool is needed at all. If purpose-built tools cover the workflows, the generic one only adds attack surface.
- Treat every tool call as untrusted input. Tool calls come from model output, which is shaped by everything the model has read.
References
- CVE-2026-78812 (cve.org)
- FastLED
mcp_server.py - Vulnerable commit ff853d5:
run_specific_commandandrun_command - Hook at ff853d5:
ci/hooks/check_forbidden_commands.py
The Big Picture
A well-maintained, widely used project shipped a command runner with no boundary of its own, and the one guardrail in the repository lives in a different layer and matches on text. If that is what the developer tooling in a popular repository looks like, what is hiding in the MCP servers your team installed for convenience (or fun) and never looked at again?
mcp-audit gives security teams visibility into which MCP servers are connected across developer machines, what tools they expose, and where the risk is before the next CVE makes it your problem.