DEV Community

Cover image for Building a Secure Remote Execution Agent for AI: Loopback-Only REST, Outbound Tunnels, and MCP Integration
Michael Odhiambo
Michael Odhiambo

Posted on

Building a Secure Remote Execution Agent for AI: Loopback-Only REST, Outbound Tunnels, and MCP Integration

If you are designing infrastructure where AI or mobile clients need authenticated access to Linux hosts without exposing public ports, this architecture solves that problem deterministically. Vela eliminates inbound network access entirely while preserving authentication across local AI, cloud agents, and mobile control planes. Below is the production-grade implementation rather than a demo.

Key Architectural Guarantees

  • Zero inbound exposure: Vela REST API binds only to 127.0.0.1:8765. Remote access uses outbound WebSocket tunnels exclusively.
  • MCP is an adapter, not an owner: vela-mcp translates LLM tool calls but never defines capabilities. Truth lives in the Vela /meta/tools endpoint.
  • Auth preservation across boundaries: The relay (velavps) injects credentials but never bypasses native Vela authentication. Tenant isolation is enforced at the data layer.
  • Fail-safe capability discovery: vela-mcp queries /meta/tools per request. If the query fails, it advertises no tools (fail-open) rather than stale or cached state.
  • Destructive actions are gated everywhere: Confirmation requirements such as run_update(confirm=true) are enforced at both the MCP description and Vela execution layers.

System Topology

Image of how the ecosystem works

Access Modes at a Glance

Mode Transport Network Path Consumer
Local AI MCP stdio AI to vela-mcp to Vela REST (direct) Claude Desktop / Cline
Remote AI MCP Streamable HTTP AI to vela-mcp to velavps to Tunnel to Vela REST Claude.ai / cloud agents
Mobile Human REST + X-Secret Android to velavps to Tunnel to Vela REST vela-android

Security Model: Non-Negotiable Boundaries

This is not theoretical. Every decision here addresses real attack surfaces in remote execution systems.

  • Loopback-only execution: Vela REST API never accepts remote connections. All remote traffic flows through the tunnel agent subsystem, which has no listening socket.
  • Secret-as-identity multitenancy: velavps isolates tenants via composite PK (relay_secret, agent_id). Each agent record is owned by exactly one secret.
  • Credential scope discipline: The relay injects X-Secret into forwarded requests but cannot escalate privileges. Vela validates auth independently on every request.
  • Pairing anti-enumeration: Activation codes are SHA-256 hashed with TTLs. Generic error responses prevent username or agent ID harvesting.
  • Known debt (tracked): Android stores relaySecret in plaintext Room DB. Migration to EncryptedSharedPreferences/Keystore is planned for v1.6. Agent PINs are already encrypted. Issue #84

⚠️ Critical: Even localhost MCP traffic requires valid JWT or relay_secret. There is no implicit trust for loopback communication.

Failure Modes: What Breaks When

Most docs describe happy paths. Here is what actually happens when components fail:

Failure Impact Recovery
Vela REST API stops All tool invocation fails (local + remote). Tunnel may linger briefly. Restart Vela service.
Tunnel agent crashes Remote access lost. Local MCP/stdio unaffected. Auto-reconnect attempted; velavps marks agent offline after 3 missed heartbeats.
velavps unavailable All remote clients fail. Local Vela + local MCP continue normally. Restore velavps; tunnels auto-reestablish.
vela-mcp stops MCP clients lose tool access. Vela, Android, and relay remain operational. Restart vela-mcp; no state loss.
Internet outage Local paths work. Remote paths fail until connectivity returns. Tunnel agent retries with exponential backoff.

Concurrency note: Vela uses first-come-first-served execution like SSH sessions. No client priority exists. Tool timeout is hard-limited to 30s.

Protocol Contracts (Wire-Level)

Click to expand protocol specifications

  • WebSocket tunnel (Vela to velavps): JSON messages include heartbeat, forward_request, forward_response, and forward_response_start/chunk/end. Outbound-only.
  • MCP transport: JSON-RPC over stdio (local) or Streamable HTTP POST /mcp/{agent_id} (cloud).
  • Pairing flow: POST /agents/register/start then GET /status (poll) then POST /pair/complete (Android) then POST /activate (Vela)
  • Assistant SSE events: Polymorphic JSON keyed by type includes thinking, tool, content, gate, screenshot, error, and done. Chunked through tunnel for reliable relay.
  • Capability discovery: GET /meta/tools returns only tools the connected Vela instance supports. Response example:
{
  "tools": [
    {
      "name": "run_command",
      "description": "Execute shell command with confirmation gate",
      "inputSchema": { "type": "object", "required": ["cmd", "confirm"] }
    }
  ],
  "version": "1.4.2",
  "host_id": "abc123"
}
Enter fullscreen mode Exit fullscreen mode

Architectural Principles (Non-Negotiables)

These are constraints that prevented entire classes of bugs rather than suggestions:

  1. Local execution stays local via loopback binding.
  2. Remote access uses outbound connectivity only.
  3. Relay routes while Vela executes. Never conflate the two.
  4. MCP adapts capabilities but does not own them.
  5. Clients decouple from execution by using the same capabilities via different interfaces.
  6. Auth preservation applies across all boundaries.
  7. Capability discovery is host-driven because /meta/tools is the source of truth.

Resources

Questions about implementation details should check the repo issues or open a discussion. This architecture is battle-tested but scrutiny is welcome. 🔒

Top comments (0)