Skip to content

Provider Isolation

Providers communicate with the sidecar over NATS, not HTTP on localhost. They run as standalone Kubernetes Deployments, not as additional containers in the session pod. This is a deliberate security decision.

Kubernetes NetworkPolicy operates at the pod level, not the container level. All containers in a pod share a network namespace. If a provider container runs inside the session pod:

  • It has localhost access to the sidecar’s full HTTP API (:9090), not just the proxy endpoint
  • It has localhost access to the agent’s inject endpoint (:8788), allowing it to manipulate the conversation
  • It can probe every port on localhost
  • NetworkPolicy cannot restrict intra-pod traffic

A compromised provider container inside the pod could inject messages into the agent, call sidecar endpoints it shouldn’t, or exfiltrate data through the agent’s SSE stream.

With providers as separate pods communicating over NATS:

Attack vectorIn-pod (HTTP)Separate pod (NATS)
Access agent inject endpointYes (localhost)No (different pod)
Access sidecar HTTP APIYes (localhost)No (different pod)
Access databasesDepends on pod-level NetworkPolicyBlocked by NetworkPolicy
Snoop other sessionsShares pod with one sessionBlocked by NATS subject ACLs
NetworkPolicy effectiveNo (intra-pod)Yes if cluster CNI enforces and chart is rendered with NetworkPolicy enabled (today: not shipped in helm)

Today, every provider deployment shares one mTLS client cert with CN=x1agent-provider. That cert’s NATS permissions are:

publish: ["_INBOX.>", "x1.audit.>"]
subscribe: ["x1.provider.>", "_INBOX.>"]

So providers cannot publish to session subjects (no agent injection), cannot read session events (no agent output), and cannot inject user input. They CAN, however, subscribe to every other provider’s domain — a compromised messaging-slack provider can sniff graph-provider traffic.

Per-provider certs (CN=provider-graph, CN=provider-messaging) are the v2 tightening: each provider gets its own cert and the NATS ACLs narrow to its own domain. Tracked as a follow-up.

Status: not enforced in the helm chart today. A reference NetworkPolicy exists at deploy/k8s/dev/networkpolicy.yaml but is not part of any helm install. OrbStack’s CNI doesn’t enforce NetworkPolicy at all. Production CNIs (Calico / Cilium / GKE Dataplane v2 / etc.) will enforce the manifests below if you kubectl apply them yourself; ship-in-helm is a follow-up gated by networkPolicy.enabled (TODO).

The intended shape:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: provider-egress
spec:
podSelector:
matchLabels:
x1agent.io/component: provider
policyTypes: [Egress]
egress:
- to:
- podSelector:
matchLabels:
app: nats
ports:
- port: 4222
# Provider's own backing service (e.g., Neo4j)
# Added per-provider via additional NetworkPolicy

Providers can reach NATS and their own backing service. Nothing else. No direct internet access. All external API calls go through the sidecar’s credential proxy.

The chart-shipped NATS deployment requires mTLS — there is no token-auth path. cert-manager issues the server cert and per-component client certs (api, sidecar, providers) from an internal CA. The NATS server runs with verify_and_map: true, which extracts the client cert’s CN and matches it against the authorization.users block.

nats-server.conf
tls {
cert_file: "/certs/server-cert.pem"
key_file: "/certs/server-key.pem"
ca_file: "/certs/ca.pem"
verify: true
}
authorization {
users = [
{ user: "CN=sidecar", permissions: { publish: ">", subscribe: ">" } }
{ user: "CN=graph-provider", permissions: { publish: "x1.provider.graph.>", subscribe: "x1.provider.graph.>" } }
{ user: "CN=files-provider", permissions: { publish: "x1.provider.files.>", subscribe: "x1.provider.files.>" } }
]
}

See Configuration: NATS mTLS for cert-manager wiring and the per-component cert layout.