OmniForge

Product guides · Trusted local networks only

Security and limitations

[!CAUTION] OmniForge is built for a trusted LAN. It is not production-grade, it isn't safe to expose to the internet, and it doesn't isolate scripts from one another or from itself. Anyone who can reach port 8000 can read, change and run every tool. Every tool can read everything the application can, including its MCP key.

This page lists what OmniForge does and doesn't protect, as the code stands today. Nothing here is hidden in the product either: the dashboard's Overview, Connect and Help screens and the built-in omniforge_instructions tool say the same.

Who can do what

Surface Authentication What an unauthenticated caller can do
Dashboard GET / None Everything the UI offers
Management API /api/* None List every tool including its source code, create, edit, rename, enable, disable, delete and run tools
POST /mcp Optional Bearer key (OMNIFORGE_API_KEY) With no key set: list, call and create tools. With a key: nothing (HTTP 401)
GET /mcp None Receive a two-line stub event stream. No data is exposed
/docs, /redoc, /openapi.json, /docs/oauth2-redirect None FastAPI's built-in interactive API docs. "Try it out" can call the unauthenticated API
/assets/*, /sw.js None (public by design) Fetch 14 allow-listed static files

The API key therefore only gates POST /mcp. Anyone who can reach the dashboard or /api can do more than an MCP client can, without any key.

MCP access means code execution

omniforge_create_tool saves arbitrary code, and tools/call runs it. Anyone holding the MCP key, or anyone at all when no key is set, can run code inside the container. Treat the key like a shell password.

The key itself

Scripts are not isolated

Every run is python3 <file> or node <file>, started with subprocess.run inside the application container, as the same user (UID 1000), with a copy of the application's environment. Consequently:

Container hardening (--read-only, --cap-drop ALL, no-new-privileges, no Docker socket) protects the host. It does nothing to separate scripts from OmniForge or from each other. Only run code, and packages, you trust as much as the app itself.

Resource limits that don't exist

Limit Status
Memory per script Not enforced. OMNIFORGE_MAX_SCRIPT_MEMORY_MB is read but never applied. Cap the container with Docker's --memory.
Output size Not capped. stdout and stderr are buffered in full in the server's memory and returned whole.
Timeout Configured value + 15 s (45 s by default). On expiry, only the direct child process is killed. Grandchildren keep running.
CPU None. Use Docker's --cpus.
Concurrency or rate limiting None. (slowapi is installed but not used.)
Disk Package caches are never cleaned automatically. See Dependencies (repository access required).

Availability: one slow tool stalls everything

The run endpoint (POST /api/tools/{name}/run) and the MCP endpoint (POST /mcp) are async handlers that call the blocking subprocess.run, and the blocking pip/npm install, directly on the event loop. The image runs a single uvicorn worker. While one tool runs or installs, the whole server stops responding: the dashboard, /api and every other MCP client wait.

A first run with packages can block for up to 120 s of install plus a 125 s lock wait. A normal run can block for up to the script timeout, 45 s by default. A tool that loops forever blocks for the full timeout on every call. See Configuration → Workers (repository access required) for a partial, untested mitigation.

Browser-based attacks

There's no CSRF protection and no Origin or Host checking.

Keep OmniForge on a segment where browsing the web isn't routine, or bind it to an interface only trusted devices can reach.

MCP transport gaps

Supply chain

What is protected

Control Detail
No shell anywhere Arguments reach scripts as JSON on stdin, never through a shell, the command line or environment variables.
Strict input validation Tool names match ^[a-zA-Z][a-zA-Z0-9_-]{0,62}$. Package names have strict patterns. Schemas are Draft 2020-12 with local references only, and remote references are never fetched.
Arguments validated before execution Calls that don't match the schema never start a process.
No echo of submitted values Validation errors leave out the submitted values, code and secrets.
Installer environment scrubbed pip and npm get a minimal environment, with no app keys, PIP_*/NPM_* configuration or proxy variables. Installer error output has secret-looking values and URLs redacted and is truncated to 3000 characters.
Atomic, never-destructive caches Installs stage into a unique directory, publish with an atomic rename and never delete directories they didn't create.
Optimistic concurrency If-Match revisions stop stale tabs overwriting, deleting, toggling or testing changed tools (HTTP 412).
Static file allow-list Only 14 named files are served from /assets/. Path traversal, .env, databases and source files return 404.
XSS-safe dashboard All dynamic text is set with textContent. The template contains no innerHTML.
Careful PWA cache Only public assets are cached. No HTML, API data, code, results or credentials.
Non-root container Runs as UID 1000. Works with a read-only root, noexec /tmp, all capabilities dropped and no-new-privileges.
Parameterised SQL Every database query uses bound parameters.

Deployment checklist

Gaps a public deployment would need closing

These are known and tracked for future work. None of them is implemented today.


← Home · Next: Backup and restore → (repository access required)