Runabot Network Architecture & Firewall#

This page describes the complete network topology of a Runabot tenant environment: how traffic flows between every component, and where authentication/authorisation and network firewall controls are enforced.

Legend#

SymbolMeaning
πŸ”₯Cilium / Kubernetes NetworkPolicy or CiliumNetworkPolicy firewall boundary
πŸ”Authentication / Authorisation check (Ory Kratos + OpenFGA)
➑️Allowed flow
βœ‹Blocked by default (explicit allowlist required)

Component Diagram#

The diagram below shows one user’s namespace (dev-alice) inside the cluster. All egress from bot and addon pods is default-deny at the eBPF level; only explicitly whitelisted FQDNs or cluster endpoints are reachable.

flowchart TD
    %% ─── External actors ──────────────────────────────────────────
    Browser["🌐 User Browser<br />(HTTPS)"]
    Internet["☁️ Internet<br />(LLM APIs, web search)"]

    %% ─── Ingress layer ────────────────────────────────────────────
    subgraph cluster["Kubernetes Cluster  (Cilium CNI)"]

        subgraph ingress_ns["Namespace: traefik (ingress)"]
            Traefik["Traefik Ingress<br />IngressRoute"]
        end

        %% ─── Auth layer ───────────────────────────────────────────
        subgraph auth_ns["Namespace: runabot (API + Auth)"]
            KratosUI["Ory Kratos<br />(login, registration)"]
            APIServer["Runabot API Server<br />ConnectRPC<br />πŸ” Kratos session check<br />πŸ” OpenFGA role check"]
            OpenFGA["OpenFGA<br />(relationship store)"]
        end

        %% ─── User namespace ───────────────────────────────────────
        subgraph user_ns["Namespace: dev-alice  (label: owner=alice)"]
            direction TB

            subgraph bot_pod["Bot Pod"]
                OpenClaw["OpenClaw Bot<br />(openclaw binary)"]
            end

            subgraph metamcp_pod["MetaMCP Addon Pod"]
                MetaMCP["MetaMCP Proxy<br />(MCP gateway)"]
            end

            subgraph olla_pod["Olla LLM Addon Pod  (optional)"]
                Olla["Olla<br />(local LLM proxy)"]
            end

            subgraph fetch_pod["secure-fetch Addon Pod"]
                SecureFetch["secure-fetch<br />(MCP server)<br />DSPy content validator<br />MCP Elicitation"]
            end
        end

        %% ─── Cluster DNS ──────────────────────────────────────────
        KubeDNS["kube-dns<br />(port 53)"]

    end

    %% ═══════════════════════════════════════════════════════════════
    %% FLOWS
    %% ═══════════════════════════════════════════════════════════════

    %% User browser β†’ ingress
    Browser -->|"HTTPS :443"| Traefik

    %% Traefik β†’ Kratos (login page)
    Traefik -->|"/_auth/*"| KratosUI

    %% Traefik β†’ API (all /api/* traffic)
    Traefik -->|"/api/*<br />πŸ” session cookie validated<br />by Kratos middleware"| APIServer

    %% API β†’ OpenFGA (role check on every request)
    APIServer <-->|"gRPC<br />(cluster-internal)"| OpenFGA

    %% Traefik β†’ OpenClaw (WebSocket + REST, proxied by API)
    Traefik -->|"/bots/alice/*<br />πŸ”₯ NetworkPolicy:<br />only from traefik ns<br />πŸ” X-Forwarded-User injected<br />by API proxy"| OpenClaw

    %% Bot β†’ MetaMCP (MCP over stdio/HTTP)
    OpenClaw -->|"MCP<br />πŸ”₯ CiliumNP: intra-user only<br />(same owner label)"| MetaMCP

    %% MetaMCP β†’ Addons (only enabled tools)
    MetaMCP -->|"MCP<br />(selected tools only)"| SecureFetch
    MetaMCP -->|"MCP<br />(if enabled)"| Olla

    %% secure-fetch β†’ Internet (fetching external URLs)
    SecureFetch -->|"HTTPS<br />πŸ”₯ CiliumCCNP toFQDNs whitelist<br />(admin-approved domains only)<br />βœ‹ all others blocked"| Internet

    %% Bot β†’ Olla (LLM inference, alternative to cloud APIs)
    OpenClaw -.->|"HTTP (local LLM)<br />πŸ”₯ CiliumNP: intra-user only"| Olla

    %% Bot β†’ Internet (cloud LLM APIs, if no local Olla)
    OpenClaw -->|"HTTPS to api.openai.com etc.<br />πŸ”₯ CiliumCCNP toFQDNs whitelist<br />(admin-approved + user-approved FQDNs)<br />βœ‹ all IP-direct attempts blocked"| Internet

    %% DNS inspection (mandatory for every workload with egress)
    OpenClaw -->|"DNS :53 UDP/TCP<br />πŸ”₯ Cilium DNS proxy<br />(maps IPs β†’ FQDNs)"| KubeDNS
    SecureFetch -->|"DNS :53"| KubeDNS
    Olla -->|"DNS :53"| KubeDNS

    %% API manages Cilium policies dynamically
    APIServer -->|"K8s API: CRUD<br />CiliumNetworkPolicy<br />(user whitelist updates)"| user_ns

    %% ─── Styling ───────────────────────────────────────────────────
    classDef firewall stroke:#f38ba8,stroke-width:2px,stroke-dasharray:5 5
    classDef auth    stroke:#a6e3a1,stroke-width:2px
    classDef pod     stroke:#89b4fa,stroke-width:1px
    classDef ext     stroke:#fab387,stroke-width:2px,color:#1e1e2e,fill:#fab387

    class Traefik firewall
    class APIServer auth
    class KratosUI auth
    class OpenFGA auth
    class OpenClaw,MetaMCP,SecureFetch,Olla pod
    class Browser,Internet ext

Firewall Rules Reference#

Global Default-Deny (CiliumClusterwideNetworkPolicy)#

Applied to all pods carrying the runabot.de/workload-type label:

# Applied automatically to every bot and addon pod
egressDeny:
  - toEntities:
      - world   # blocks all internet egress by default

Any outbound connection not covered by an explicit toFQDNs or toCIDRSet allow rule is silently dropped at the eBPF layer β€” before the TCP handshake.

DNS Inspection (mandatory for every workload)#

egress:
  - toEndpoints:
      - matchLabels:
          k8s-app: kube-dns
    toPorts:
      - ports:
          - port: "53"
            protocol: UDP
          - port: "53"
            protocol: TCP
        rules:
          dns:
            - matchPattern: "*"   # Cilium intercepts all DNS; maps IPs β†’ FQDNs

Why this matters: Without DNS inspection, Cilium cannot correlate the IP address a bot dials to the FQDN it resolved. The DNS interception step is what makes toFQDNs whitelisting enforceable.

Admin-Approved FQDN Whitelist (CiliumClusterwideNetworkPolicy)#

Managed by site administrators via the Runabot Admin UI or directly as Kubernetes manifests in Git:

# Example: runabot-default-whitelist-bots
egress:
  - toFQDNs:
      - matchName: api.openai.com
      - matchName: auth0.openai.com
      - matchName: api.anthropic.com
      - matchName: api.mistral.ai

Changes to these rules are pull-request-gated. Full Git history is available for SOC 2 audit purposes.

User-Defined Whitelist (CiliumNetworkPolicy, namespace-scoped)#

Bot owners can add their own FQDNs via the Settings UI. The Runabot API:

  1. Validates the requested FQDN against the admin blacklist
  2. Rejects entries that match a globally denied pattern
  3. Writes a CiliumNetworkPolicy to the user’s namespace
# Example: user alice adds api.github.com for her coding assistant
# PUT /api/v1/bots/alice-bot/egress  { "allowed_fqdns": ["api.github.com"] }
# β†’ Runabot API creates:
egress:
  - toFQDNs:
      - matchName: api.github.com
    description: "GitHub API β€” added by alice 2026-03-22"

Intra-User Isolation (CiliumClusterwideNetworkPolicy)#

# Applied per bot instance at deploy time
endpointSelector:
  matchLabels:
    app.kubernetes.io/instance: "alice-openclaw"
egress:
  - toEndpoints:
      - matchLabels:
          dev.runabot.de/owner: "alice"   # only alice's own addons

This ensures Alice’s bot can reach Alice’s MetaMCP, Olla, and secure-fetch pods, but cannot reach Bob’s addons or any other cluster service.


Authentication & Authorisation Reference#

Where Identities Are Checked#

BoundaryMechanismWhat is checked
Browser β†’ TraefikTLS (cert-manager)Certificate validity
Traefik β†’ APIOry Kratos session cookieValid session + CSRF
API β†’ Bot WebSocketX-Forwarded-User headerInjected by API after Kratos validation; bot trusts trusted-proxy mode only
API β†’ Any resourceOpenFGAuser:alice can:access bot:alice-bot tuple must exist
API β†’ Admin endpointsOpenFGA admin roleAdditional role check per operation
MetaMCP β†’ AddonInternal API keyBOOTSTRAP_API_KEYS generated per-namespace by the Addon controller

OpenFGA Relationship Model#

user:alice  β†’  owner  β†’  bot:alice-openclaw
user:alice  β†’  owner  β†’  addon:alice-metamcp
user:alice  β†’  owner  β†’  addon:alice-secure-fetch
admin:bob   β†’  admin  β†’  tenant:dev

The Runabot API enforces these relationships synchronously on every request via the ConnectRPC interceptor chain. A missing tuple returns 403 Forbidden before any business logic executes.


Prompt Injection Defence (secure-fetch)#

The secure-fetch addon adds a second line of defence for any content fetched from the internet:

  1. Input sanitisation β€” URLs and queries are matched against an allowlist; control characters are stripped.
  2. DSPy content validation β€” fetched content is analysed by a secondary LLM for hidden instructions (prompt injection patterns, exfiltration payloads).
  3. MCP Elicitation β€” if risk is detected, the server pauses and presents the user with an explicit Allow/Block decision. The decision is logged.
  4. GEPA feedback loop β€” user decisions are collected to continuously improve the detection prompt.

This protection does not exist in vanilla GitHub Copilot’s Bing web search integration.