TDPilot executes AI-driven instructions inside a running TouchDesigner session. That is inherently a sensitive position. This document covers what we do protect against, and what we honestly do not.
MCP client ── stdio ──> MCP server ── HTTP POST ──> TouchDesigner
(Claude) (uv run) (127.0.0.1) WebServer DAT + callbacks
Both legs speak JSON; the TD-side callback file is what actually runs code.
When you call td_exec_python:
- Client token scan - fast substring match against a blocklist. Defeatable by string concatenation, so it is never the authority.
- Client AST scan - parses the code with
ast.parseand rejects any import/call/attribute/subscript nodes that touch dangerous builtins or modules. Catches the string-concat bypasses that layer 1 misses. - Server env gate - the TD callbacks file refuses to serve when
TD_MCP_SHARED_SECRETis empty ANDTD_MCP_REQUIRE_AUTH=1(the default). Installers always generate a secret. Secrets are compared in constant time. - Server CORS / Sec-Fetch-Site - rejects cross-site browser fetches.
Access-Control-Allow-Origin: *is never emitted. - Server token and AST scan - same policy as the client, duplicated so a malicious MCP server implementation cannot bypass the TD-side check.
- Sandboxed globals - restricted and standard modes run user code with a
custom
__builtins__dict that omits the dangerous builtins.
| Mode | Default | Imports | Builtins | TD API (via td_exec_python) |
Intended for |
|---|---|---|---|---|---|
| off | no | — | — | td_exec_python disabled entirely |
Read-only MCP clients |
| restricted | yes | blocked | curated (no getattr/hasattr/type) | .text= + .par.file= blocked; .par.* writes + node methods allowed¹ |
Default agent loop |
| standard | no | 14 whitelisted (json, math, re, …) | curated | same restricted blocks + 14 whitelisted imports | Agent loop with safe helpers |
| full | no | unrestricted | unrestricted | unrestricted | Developer sessions only |
¹ restricted is a Python-level sandbox, not a TD-graph read-only mode. It
blocks shell-exec escapes (e.g. the os module's system / popen helpers,
subprocess), all imports, dunder reflection (__subclasses__, __bases__,
__builtins__ subscript), and two specific TD-side vectors: .text = …
writes on DATs and .par.file = … dynamic path writes. It does NOT prevent
parameter writes (op('x').par.amp = 2.5), node method calls
(op('x').destroy(), parent().copy(...)), or most of the TouchDesigner
Python API. Use TD_MCP_EXEC_MODE=off if you need td_exec_python fully
disabled. Note that write-mutating tools (td_set_params, td_create_node,
etc.) are never gated by exec mode — exec mode only controls the
td_exec_python escape hatch.
1. A compromised MCP client. The MCP server trusts the process that invoked it over stdio. If your Claude client is malicious, TDPilot has no way to know.
2. Code stashed in a Text DAT and then called via mod.<dat>.
Restricted mode now blocks .text = ... writes and create(textDAT) calls,
but a creative caller might still construct DAT content via allowed
operators. If you rely on restricted mode as a hard boundary, audit the
project's existing DATs — they become part of the attack surface.
3. Exhaustion attacks (CPU, GPU, memory). There is a 30-second timeout but no per-client quota. A runaway LLM can burn frames or load a huge TOP.
4. Filesystem reads via TD's native operators. File In DATs, Movie File
In TOPs, and similar can read any file readable by the TD process.
Restricted mode blocks setting .par.file dynamically, but pre-wired
operators are untouched. Mitigate by running TD as a user without access to
sensitive files.
5. Network calls from TD's native operators. Web Client DATs, WebSocket DATs, OSC In/Out CHOPs — none are gated by exec mode. Mitigate at the OS or network layer.
6. TD-graph mutation under "restricted" mode. A common misreading of
the exec-modes table: restricted is a Python-level sandbox (no imports,
no shell exec, no dunder reflection, no .text= on DATs, no .par.file=
on operators), not a TD-graph read-only mode. Parameter writes
(op('/project1/noise1').par.amp = 2.5), node destruction
(op('x').destroy()), node creation through the TD Python API, and most
other TD API method calls are not blocked. If you need a read-only
posture, set TD_MCP_EXEC_MODE=off — that disables td_exec_python
entirely. The write-mutating tools (td_set_params, td_create_node,
td_connect_nodes, td_project_lifecycle, …) are a separate surface;
they are governed by the SafetyManager bounds system, not by exec mode.
TDPilot is designed for local single-user sessions where you trust the AI
client (Claude Desktop, Claude Code, and similar). It is NOT designed to be
multi-tenant or internet-exposed. If you need that, put TDPilot behind a
reverse proxy with real auth and treat exec mode as off.
Open an issue at https://github.com/dreamrec/TDPilot/issues with a
security: prefix, or contact the maintainer for confidential disclosure.