Skip to content

Security

ChiR24 edited this page Sep 30, 2026 · 6 revisions

Set it up · Security: local, tokened, deliberate

This server lets a program drive your editor: create, change and delete assets, run console commands, even run Python. The defaults keep that to your own machine and to clients that hold your project's token.

On this page · Defaults · How a request is checked · Capability tokens · LAN access · Scopes · Consent · Other safeguards · Refusal codes · Reporting

Defaults

Area Default
🏠 Network Both routes listen on 127.0.0.1 only. The WebSocket listener is on 8090 and 8091; the native HTTP server is off, and uses port 3000 when on.
🔑 Capability token Required on both routes
✋ Consent Required in every call to 62 capabilities: all deletes, plus some other writes
⌨️ Console commands Filtered: no command chaining, no quit or crash commands
📁 Asset paths /Game, /Engine, /Script, /Temp, /Niagara, plus any you add
🛡️ The plugin's own settings Out of reach: automation can't read or change them, so an assistant can't lift its own limits

How a request is checked

The plugin is the authority. The Node.js server checks requests early so it can fail fast, but the plugin re-checks every request, whichever route it came from, before it reaches the editor's queue. A refused request does no editor work at all.

flowchart TB
    req(["Request from a client"]) --> tok["① Capability token"]
    tok -- "ok" --> sc["② Scope"]
    sc -- "ok" --> con["③ Consent"]
    con -- "ok" --> pol["④ Project · paths · console · quota"]
    pol -- "ok" --> q[["Editor queue · game thread"]]
    tok -- "missing or wrong" --> r1["401 Invalid capability token"]
    sc -- "not held" --> r2["SCOPE_NOT_GRANTED"]
    con -- "no grant" --> r3["CONSENT_REQUIRED"]
    pol -- "outside the rules" --> r4["PATH_NOT_PERMITTED<br/>COMMAND_BLOCKED · QUOTA_EXCEEDED"]
    classDef check fill:#30363d,stroke:#8b949e,color:#f0f6fc
    classDef bad fill:#da3633,stroke:#f85149,color:#ffffff
    classDef good fill:#238636,stroke:#3fb950,color:#ffffff
    class tok,sc,con,pol check
    class r1,r2,r3,r4 bad
    class q good
Loading

Capability tokens

The first time the editor starts with the plugin, it generates a random 32-byte token and writes it, as 64 hex characters, to:

<YourProject>/Saved/MCP/capability-token
Who How the token is presented
🌐 Native HTTP clients In the X-MCP-Capability-Token header. Without it they get 401 Invalid capability token.
🧩 The stdio server Reads the file itself (it finds the project through UE_PROJECT_PATH) and presents the token in its WebSocket handshake. MCP_AUTOMATION_CAPABILITY_TOKEN overrides the file, for when the server can't read the project folder.
✏️ Your own token A value typed into Capability Token in the plugin settings replaces the generated file
  • Tokens are compared in constant time, and never written to logs, receipts or health output.
  • A session can't switch tokens halfway through.
  • The file is readable by your OS user, like the rest of the project. If Saved/ sits on a shared drive, tighten its permissions. Saved/ is normally not committed; keep it that way.

To rotate the token, delete Saved/MCP/capability-token (or clear Capability Token), restart the editor, and update your HTTP clients. The native server notices a new file immediately; WebSocket clients pick it up after the restart.

Turning the token off

Warning

Unticking Require Capability Token lets any program running on your machine drive the editor through either route, with no credentials. That can be acceptable on a single-user machine where nothing else runs. Keep it on in every other case, and always with LAN access (the native server refuses to bind to a LAN address without it).

Even with the token off, the native server refuses requests that come from web pages (requests with an Origin header). Web pages are only let through while token auth is on, so a website open in your browser can't reach the editor.

LAN access

Both routes listen on loopback only. To reach the editor from another machine:

  1. Plugin: turn on Allow Non Loopback and set Listen Host to the editor machine's LAN address (or 0.0.0.0). This one switch opens both the WebSocket listener and the native HTTP server.
  2. Token: keep Require Capability Token on. Consider giving the remote client a scoped token rather than the main one.
  3. Firewall: restart the editor and allow the ports only from the machines that need them.
  4. Client machine:
    • stdio route: set MCP_AUTOMATION_ALLOW_NON_LOOPBACK=true, MCP_AUTOMATION_HOST=<editor address> and MCP_AUTOMATION_CAPABILITY_TOKEN=<token>. For encryption, turn on Enable TLS in the plugin (PEM certificate and key) and set MCP_AUTOMATION_USE_TLS=true.
    • native route: point the client at http://<editor address>:3000/mcp with the token header.

Caution

The native server speaks plain HTTP, so the token crosses the network unencrypted. Beyond a network you fully trust, reach it through an SSH tunnel or a TLS reverse proxy rather than exposing the port.

Scopes and scoped tokens

Every capability requires one of four scopes:

Scope Covers
Read Capabilities that only look
Write Capabilities that change things
Destructive Capabilities that delete
Admin Everything

Membership is exact: holding Write does not grant Read; only Admin covers everything. An action the catalog doesn't know demands Admin, so unknown requests are refused by default. The main token (generated or typed in) is Admin.

For anything narrower, add entries under Security › Scoped Tokens › Scoped Capability Tokens:

Field Meaning
Profile A label for the token
Token The secret the client presents, exactly like the main token
Scopes Any of Read, Write, Destructive. Admin is not allowed on a scoped token.
Allowed Path Prefixes Confine the token to these content roots, e.g. /Game/Sandbox/
Allowed Projects Limit the token to these projects
Max Requests Per Minute · Max Tool Calls Per Minute Per-minute quotas for this token, counted across both routes

Typical uses: a read-only token for an assistant that should only look, a token confined to one folder for an experimental agent, or a rate-limited token on a shared machine. With a path-limited token, write targets must be named explicitly: when the plugin can't prove that a write stays inside the allowed prefixes, it refuses it.

Consent

62 capabilities declare a consent mode: explicit, or elevated for the most destructive, such as bulk deletes. A call to one of them must carry a grant that names that capability:

"consent": { "capability": "control_actor.delete", "acknowledge": "explicit", "nonce": "62A12661-4F6A-1C97-339E-69921EE9E2A7" }
  • describe returns the exact grant, as consentGrant. Send it back unchanged.
  • A grant covers one call to one capability. On the native route its nonce makes it single-use: a replay is refused with CONSENT_REUSED. The stdio route issues grants without a nonce.
  • It is never inferred from localhost, from earlier calls, or from an idempotency key.
  • elevated also satisfies an explicit requirement, not the other way round.
  • Consent is not permission: the caller still needs the capability's scope.
  • The plugin checks consent itself, on both routes.

Tip

Consent makes deleting work a deliberate step the model has to take, rather than a side effect of a loosely worded request. To approve such calls yourself, use your client's tool-approval setting for the unreal tool.

Other safeguards

Safeguard What it does
📁 Paths Asset paths must fall under /Game, /Engine, /Script, /Temp or /Niagara, plus the prefixes in MCP_ADDITIONAL_PATH_PREFIXES. /Content/... maps to /Game/.... File-system paths are checked for traversal and symbolic links, once when accepted and again just before use.
⌨️ Console commands Command chaining (;, &&, ||, |, backticks, newlines) is refused, as are commands that quit or crash the editor (quit, exit, shutdown, crash, gpucrash, …) and attempts to reach Python or a shell through the console.
🐍 Python system_control.execute_python exists. It needs Write scope and explicit consent per call, and scripts are limited to 1 MB. Anything that can run Python can do anything the editor can, so treat that consent as a real decision.
🚦 Rate limits Native HTTP allows 16 sessions and 32 connections, and per session 600 requests and 120 tool calls a minute. The WebSocket listener has optional per-client message limits (off by default).

Refusal codes

Code Meaning
SCOPE_NOT_GRANTED The token doesn't hold the scope this capability requires
CONSENT_REQUIRED The capability needs a consent grant, and no matching one was sent
CONSENT_REUSED A single-use grant was sent a second time
PATH_NOT_PERMITTED A path is outside the allowed prefixes, or couldn't be proven to be inside them
PROJECT_NOT_PERMITTED The token isn't allowed on this project
QUOTA_EXCEEDED The token's per-minute quota is used up. The one refusal worth retrying later.
COMMAND_BLOCKED A console command matched the block list

Reporting a vulnerability

Important

Please report security problems privately, through GitHub's private vulnerability reporting, not in a public issue. Published advisories are listed under the repository's Security tab.


🏠 Home

Get started
🚀 Quick Start
📦 Installation
🔌 Connecting Clients

Use it
🧭 Using the Gateway
🧰 Tools Reference
📚 Resources and Prompts

Set it up
⚙️ Configuration
🔐 Security

Help
🩺 Troubleshooting
💬 FAQ
⬆️ Upgrading from 0.5.x

Contribute
🛠️ Development


Covers the 0.6 line · Releases · Discussions

Clone this wiki locally