This is a proof-of-concept / hobby project, not production software. It is provided
as-is under the Functional Source License 1.1 (FSL-1.1-ALv2), with no warranty
(see LICENSE and NOTICE).
These are inherent to what the tool does, not bugs:
- Arbitrary code execution by design. Generated Lua is written to disk and run on the
console via
Lua "dofile(...)". The plugin review gate (REVIEW_PLUGINS, on by default) is the only barrier, and it is deliberately skipped during auto-heal. Keep the gate on, and read plugins before approving them. See the Safety section of the README. - The OSC feedback port is unauthenticated. The bridge listens for console replies on
PYTHON_OSC_PORT_IN(UDP,127.0.0.1by default). Any datagram reporting a console error can start an auto-heal turn, and its text is fed to the model as part of that prompt — so the feedback port is untrusted input in the same way show data is. Mitigation:OSC_TRUSTED_SOURCES_ONLY(on by default) drops datagrams whose source address is neither loopback nor the configuredMA3_IP. With the default loopback setup this means only processes on your own machine can reach it. KeepMA3_IPon127.0.0.1— pointing it at a non-loopback address the host owns is what would expose this port to your network. - Prompt-injection surface via show data.
show_context.jsonfeeds showfile object names into the model prompt each turn. Treat show context as untrusted input. Mitigation: the injected block is wrapped in a per-session random nonce boundary (--- SHOWFILE CONTEXT <nonce> ---) and the system prompt tells the model only that nonce-tagged block is data, never instructions — so an object whose name forges a delimiter can't break out of the data region (it can't predict the nonce). This raises the bar but is not a guarantee; a model can still be swayed by adversarial content, so naming your own objects remains the real protection for a personal tool. - Remote model calls. Prompts (including show data) are sent to Anthropic and/or Google per their terms. Do not run it against shows whose data you cannot send to a third party.
- The read path evaluates addresses as Lua, behind a shape+allowlist gate. The QUERYAI
responder's
resolve(addr)runs an address as a Lua expression (load("return "..addr)) when it looks like an accessor chain (e.g.Root().ColorTheme…), so a query address is executed, not just looked up. Unlikedeploy_plugin(human review-gated),query_stateis gate-free by design, so the eval branch is constrained BEFOREloadbyeval_allowed(): it accepts ONLY a single leading allow-listedName()call followed by a plain accessor chain (^%a+%(%)[%w_%.%[%]]*$), which forbids a second(, quotes,:method calls, and whitespace — every way to smuggle a statement or side-effecting call (e.g. the(function() Cmd('Delete…') return Root() end)()trick) past"return ". A non-object result is also rejected. It is still code execution, so the same trust assumptions as the plugin path apply — but the shape gate is the real boundary, not the"return "prefix alone. (The bridge itself uses this same gated path for a couple of internal reads, e.g. the startup pool-presence checkDataPool().Plugins.<name>.)
- API keys live in
config.py, which is gitignored. Onlyconfig.example.py(placeholders) is committed. Never commitconfig.py. - Rotate any key that was ever committed to git history.
This is a personal project without a formal disclosure process. There is no guaranteed response time.
- Non-sensitive reports: open a GitHub issue.
- Anything that should not be public: use GitHub's private vulnerability reporting on the Security tab, which opens a report visible only to the maintainer.
Please don't include your config.py, session logs, or anything else containing API keys in
a report.