Security Considerations for Remote Unity MCP Server: Authentication, Isolation, and Fail-Closed Design

Running a remote Unity MCP server requires strict API-key authentication, per-user session isolation, and fail-closed defaults to prevent unauthorized code execution and cross-user data leakage.

The CoplayDev/unity-mcp project enables AI assistants to control Unity Editor instances through a Model Context Protocol (MCP) server. When operating in remote-hosted mode, the Python bridge accepts network connections from external clients, making robust security architecture essential to protect against remote code execution and session hijacking.

Threat Model and Attack Surface

Remote hosting exposes the Unity Editor to network-based threats that are absent in local-only deployments. The threat model prioritizes preventing arbitrary command execution and enforcing strict boundary controls between users.

Remote Code Execution Risks

The most critical vulnerability is unauthorized Unity Editor control via crafted MCP messages. According to the source code in Server/src/transport/unity_instance_middleware.py, every tool call passes through UnityInstanceMiddleware which validates the presence of a user_id before forwarding commands. Without this injection step, the middleware raises a RuntimeError and returns an HTTP 401 response, blocking the request entirely.

Session Leakage and Cross-User Contamination

In multi-tenant scenarios, one user must never access another's Unity instance. The PluginRegistry class in Server/src/transport/plugin_registry.py maintains a dual-index mapping system: local mode uses _hash_to_session, while remote-hosted mode uses _user_hash_to_session keyed by the tuple (user_id, project_hash). Any attempt to list sessions without providing an explicit user_id raises a ValueError, preventing enumeration attacks.

Authentication Architecture

The authentication flow implements defense-in-depth with external validation, in-memory caching, and transport-layer verification.

API Key Validation Flow

When a client sends an MCP request to the HTTP endpoint /mcp, the server expects the header X-API-Key: <key>. The UnityInstanceMiddleware.on_call_tool method extracts this header and delegates validation to ApiKeyService.validate() in Server/src/services/api_key_service.py.

The validation process follows this strict sequence:

  1. Cache Check: The service checks an internal dict _cache mapping api_key to (valid, user_id, metadata, expires_at).
  2. External Validation: If the cache misses or the entry expired, the service performs an HTTP POST to the configurable validation_url with the key in the request body.
  3. Result Handling: Definitive responses (HTTP 200/401) are cached for the duration specified by --api-key-cache-ttl (default 300 seconds). Transient failures (5xx, timeouts) are never cached, ensuring temporary outages do not permanently lock out users.
  4. Context Injection: Valid keys result in ctx.set_state("user_id", result.user_id), making the identity available to downstream tools.

Credential Safety: API keys are redacted to xxxx...yyyy format before any logging occurs, and plaintext keys are never persisted to disk.

WebSocket Security for Plugin Connections

Unity plugins connect via WebSocket to /hub/plugin as implemented in Server/src/transport/plugin_hub.py. The PluginHub.on_connect method enforces the same X-API-Key header requirement. Invalid authentication triggers specific close codes:

  • 4401: Missing API key
  • 4403: Invalid API key
  • 1013: Authentication service unavailable (retry later)

This prevents unauthenticated WebSocket sessions from reaching the Unity Editor.

Session Isolation Mechanisms

Once authenticated, the server must ensure each user interacts only with their assigned Unity instance.

User-Scoped Session Registry

The PluginRegistry class differentiates between local and remote modes through storage structure. In remote-hosted mode, sessions are stored in _user_hash_to_session with a composite key of (user_id, project_hash). This design prevents UUID collisions and enforces logical separation at the storage layer.

Unity Instance Middleware Injection

The UnityInstanceMiddleware in Server/src/transport/unity_instance_middleware.py calls get_session_key(ctx) to resolve the active instance. The resolution order prefers client_id, falls back to user:{user_id}, and finally "global". However, when --http-remote-hosted is enabled, the global fallback is unreachable, ensuring every operation is tied to an authenticated identity.

Caching Strategy and Resilience

The ApiKeyService implements a fail-soft cache for availability without compromising security. Only definitive validation results (explicit valid=True or valid=False with 401 responses) enter the cache. Network timeouts, connection errors, and 5xx responses return valid=False with cacheable=False, forcing immediate retry on the next request.

Operators can tune the cache duration via the CLI argument --api-key-cache-ttl. Lower values (e.g., 60 seconds) provide faster revocation response at the cost of increased load on the external authentication service.

Fail-Closed Security Defaults

The architecture follows a fail-closed philosophy where any error or missing credential results in denial rather than accidental exposure:

Component Failure Mode Security Response
ApiKeyService._validate_external Timeout or connection error Returns non-cacheable invalid result
PluginHub.on_connect Auth service unavailable Closes WebSocket with code 1013
UnityInstanceMiddleware._inject_unity_instance Missing user_id in remote mode Raises RuntimeError → HTTP 401
list_sessions() Called without user_id Raises ValueError

Configuration and Deployment Hardening

Proper deployment requires explicit opt-in flags and external validation endpoints.

Required Startup Configuration

Remote-hosted mode activation requires both the feature flag and the validation endpoint. The server aborts startup with exit code 1 if --api-key-validation-url is missing while --http-remote-hosted is set.

python -m Server.main \
    --http-remote-hosted \
    --api-key-validation-url https://auth.internal.company.com/validate \
    --api-key-cache-ttl 300 \
    --allow-lan-bind

Security-critical flags:

  • --allow-lan-bind: Binds HTTP to 0.0.0.0 instead of loopback (disabled by default)
  • --allow-insecure-remote-http: Permits plain http:// URLs (disabled by default; HTTPS enforced)
  • --api-key-service-token-header: Authenticates the server itself to the external validation service

Integration Example

Unity clients must include the header during WebSocket initialization:

var ws = new WebSocket("wss://mcp.company.com/hub/plugin");
ws.SetRequestHeader("X-API-Key", userApiKey);
ws.Connect();

On the server side, tool implementations access the validated instance through the context:

@McpTool(...)
async def update_scene(ctx: Context, command: str):
    # Injected by UnityInstanceMiddleware after auth validation

    unity_instance = ctx.get_state("unity_instance")
    await unity_instance.send_command(command)

Summary

  • Remote Unity MCP server security relies on mandatory API-key validation via the X-API-Key header, enforced by ApiKeyService and UnityInstanceMiddleware.
  • Session isolation is achieved through PluginRegistry's user-scoped index _user_hash_to_session, preventing cross-user access to Unity instances.
  • Fail-closed defaults ensure that authentication failures, missing credentials, or service outages result in immediate denial rather than accidental access.
  • Caching strategy stores only definitive validation results for 300 seconds by default, while transient errors trigger immediate retry without poisoning the cache.
  • Credential protection redacts API keys from logs and never stores them in plaintext.

Frequently Asked Questions

How does the remote Unity MCP server prevent unauthorized code execution?

The server prevents unauthorized code execution through UnityInstanceMiddleware in Server/src/transport/unity_instance_middleware.py, which validates every incoming request for a user_id before forwarding it to the Unity Editor. Unauthenticated requests raise a RuntimeError and return an HTTP 401 response, ensuring that only validated sessions can trigger editor commands.

What happens if the external authentication service goes down?

If the external validation URL becomes unreachable, ApiKeyService._validate_external returns a non-cacheable invalid result, causing immediate retry on subsequent requests. For WebSocket connections, PluginHub.on_connect closes the connection with code 1013 (service unavailable), signaling clients to retry later without caching the failure state.

Can one user access another user's Unity project in remote-hosted mode?

No. The PluginRegistry in Server/src/transport/plugin_registry.py uses a user-scoped index _user_hash_to_session that keys sessions by (user_id, project_hash). Additionally, the list_sessions method requires an explicit user_id parameter in remote mode; omitting it raises a ValueError, preventing session enumeration or cross-tenant access.

How are API keys protected in server logs?

All API keys are redacted to a masked format (xxxx...yyyy) before any logging occurs in ApiKeyService. The keys are validated against an external endpoint and cached in memory only as hashed or masked identifiers, ensuring plaintext credentials never appear in log files or persistent storage.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →