Skip to content

Security: pimteters/boardop

Security

SECURITY.md

Security Policy

Status

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).

Design risks you are accepting by running it

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.1 by 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 configured MA3_IP. With the default loopback setup this means only processes on your own machine can reach it. Keep MA3_IP on 127.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.json feeds 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. Unlike deploy_plugin (human review-gated), query_state is gate-free by design, so the eval branch is constrained BEFORE load by eval_allowed(): it accepts ONLY a single leading allow-listed Name() 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 check DataPool().Plugins.<name>.)

Handling secrets

  • API keys live in config.py, which is gitignored. Only config.example.py (placeholders) is committed. Never commit config.py.
  • Rotate any key that was ever committed to git history.

Reporting a vulnerability

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.

There aren't any published security advisories