---
description: Defense-in-depth controls in Vibepit including container hardening, network isolation, CIDR blocking, DNS filtering, and mTLS authentication.
---

# Security Model

Vibepit applies defense-in-depth controls to reduce risk when you run AI coding agents. No single layer is sufficient on its own; each control limits the blast radius if another fails. This page explains what each control does and why it exists.

For a catalog of specific threats and mitigations, see the [Threat Model](threat-model.md). For how the components fit together at runtime, see the [Architecture](architecture.md).

## Default-deny network posture

A Vibepit session starts with no network access. DNS resolution, HTTP/HTTPS requests, and direct IP connections all fail unless you explicitly allowlist them. This forces you to declare every external dependency up front, making the agent's network surface auditable and reproducible.

## Container hardening

The sandbox container runs with several kernel-level restrictions:

**Read-only root filesystem.** The container's root filesystem is mounted read-only (`ReadonlyRootfs: true`). This prevents the agent from persisting modifications to system binaries, libraries, or configuration files. A writable `/tmp` (mounted as tmpfs) is available for transient scratch data, and the home directory is a persistent volume, but the OS layer itself cannot be altered.

**All Linux capabilities dropped.** The container starts with every Linux capability removed (`CapDrop: ["ALL"]`). Capabilities like `CAP_NET_RAW`, `CAP_SYS_ADMIN`, and `CAP_DAC_OVERRIDE` are unavailable, which prevents raw socket creation, filesystem namespace manipulation, and permission bypass.

**`no-new-privileges`.** The `no-new-privileges` security option prevents processes inside the container from gaining additional privileges through setuid or setgid binaries. Even if such a binary exists on a mounted volume, executing it will not escalate privileges.

**Non-root `code` user.** The sandbox container runs as the unprivileged `code` user. If an attacker escapes the process sandbox but remains inside the container, they operate without root privileges, limiting what they can access on the host kernel.

**Init process.** The container runs with an init process (`Init: true`) as PID 1. This ensures proper signal forwarding to child processes and reaps zombie processes, preventing resource leaks during long-running agent sessions.

## Network isolation

Each session creates an internal Docker bridge network (`Internal: true`). Containers on an internal network have no default route to the host or the internet. The only path to the outside is through the proxy container, which is dual-homed: it connects to both the internal session network and the default bridge network. All DNS and HTTP/HTTPS traffic from the sandbox routes through this proxy, where it is subject to allowlist filtering.

## CIDR blocking

Even if a domain resolves and passes the allowlist, the proxy blocks connections to private and reserved IP ranges:

| CIDR | Purpose |
|---|---|
| `10.0.0.0/8` | Private network (RFC 1918) |
| `172.16.0.0/12` | Private network (RFC 1918) |
| `192.168.0.0/16` | Private network (RFC 1918) |
| `127.0.0.0/8` | Localhost / loopback |
| `169.254.0.0/16` | Link-local (APIPA) |
| `fc00::/7` | IPv6 unique local addresses |
| `fe80::/10` | IPv6 link-local addresses |
| `::1/128` | IPv6 loopback |

These blocks apply by default regardless of allowlist rules, preventing an allowlisted domain from being used to reach internal services via DNS rebinding or other IP-level attacks. The `CIDRBlocker` accepts additional custom ranges via `block-cidr` if your environment requires broader restrictions.

If you deliberately need to reach an otherwise-blocked range, `allow-cidr` punches an explicit exception: any IP within an `allow-cidr` range is permitted even if it falls inside a blocked range.

## DNS filtering

The proxy runs a DNS server on port 53, configured as the sole resolver for sandbox containers. Only domains that match an allowlist rule receive a valid response; all other queries are refused.

Allowlist rules support two forms:

- **Exact match**: `example.com` matches only `example.com`.
- **Wildcard**: `*.example.com` matches exactly one subdomain label (such as `api.example.com`) but does **not** match the apex domain `example.com` or deeper subdomains like `a.b.example.com`. Use `**.example.com` to match one or more subdomain levels.

You can add DNS rules at startup via configuration or at runtime using the `allow-dns` command. Rules are additive and applied atomically using lock-free concurrency, so updates do not block in-flight queries.

## HTTP/HTTPS filtering

The proxy filters all HTTP and HTTPS traffic using a `domain:port` allowlist. HTTPS connections use the `CONNECT` method, so the proxy sees the target hostname and port without terminating TLS.

Each rule specifies a domain pattern and a port pattern:

- **Domain matching** follows the same exact and wildcard semantics as DNS filtering.
- **Port patterns** accept an exact port number or `*` for any port.

A request must match both the domain and port components of at least one rule to be allowed. Rules are purely additive: you can add them at startup or at runtime with the `allow-http` command, but you cannot remove them during a session.

## mTLS control API

The proxy exposes a control API for runtime administration (adding allowlist entries, streaming logs). This API is secured with mutual TLS (mTLS) to prevent the sandbox container or other processes from issuing unauthorized control commands.

Each session generates ephemeral Ed25519 key pairs:

1. An ephemeral CA signs a server certificate and a client certificate.
2. The CA private key is discarded immediately after signing. No additional certificates can be issued for the session.
3. The server certificate's Subject Alternative Name (SAN) is restricted to `127.0.0.1`, so it is only reachable from the host.
4. TLS 1.3 is enforced as the minimum version.
5. The server requires and verifies client certificates against the ephemeral CA (`RequireAndVerifyClientCert`).

Because the CA key is discarded after signing, an attacker who compromises the proxy at runtime cannot mint new client certificates. Server credentials are passed to the proxy container via environment variables and never touch disk. Client credentials (CA cert, client cert, client key) are written to `$XDG_STATE_HOME/vibepit/sessions/<sessionID>/` with `0600` permissions so that CLI subcommands can authenticate from separate processes. These files are deleted when the session ends.

## SSH authentication

In daemon mode (`vibepit up`), the sandbox container runs an SSH server for remote access. SSH authentication uses ephemeral Ed25519 keypairs generated per session:

1. Two keypairs are generated at session startup: a **client keypair** and a **host keypair**.
2. The client private key and host public key remain on the host in `$XDG_STATE_HOME/vibepit/sessions/<sessionID>/` with `0600` permissions (private keys) and `0644` permissions (public keys).
3. The host private key is bind-mounted read-only into the sandbox container. The client public key is passed via environment variable.
4. Only public key authentication is accepted — no passwords.
5. The SSH server verifies the connecting client's key against the single authorized public key.
6. The SSH client (`vibepit connect`) verifies the server's host key against the stored public key, preventing MITM attacks.

All SSH credentials are deleted when the session ends (`vibepit down`). Because keys are never reused across sessions, a compromised key cannot grant access to another.

## SSH network access

The SSH port is not directly accessible from the host. It is forwarded through the proxy container, which publishes it to `127.0.0.1` on a random port. This means SSH is only reachable from the local machine.

## Proxy image

The proxy container runs on `gcr.io/distroless/base-debian13`. Distroless images contain no shell, no package manager, and no OS-level utilities. This minimizes the attack surface of the proxy itself: even if an attacker achieves code execution inside the proxy container, there are no tools available to escalate or pivot.

## What this is not

Vibepit is not VM-level isolation. The sandbox container shares the host kernel, which means:

- **Container escapes** through kernel vulnerabilities can grant host access.
- **Runtime bugs** in Docker or Podman can weaken isolation guarantees.
- **Host misconfiguration** (such as mounting the Docker socket into the container) can bypass all controls.

Treat Vibepit as defense in depth: multiple independent controls that collectively reduce risk. It is not absolute containment. For workloads that require stronger isolation guarantees, consider running Vibepit inside a VM.

### Container runtimes and VM boundaries

Some container runtimes run containers inside a lightweight Linux VM:

- **Docker Desktop** (macOS, Windows, and Linux) runs a VM using the platform's hypervisor (Apple Virtualization framework, Hyper-V/WSL2, or KVM).
- **Podman Machine** (macOS, Windows) runs a Fedora CoreOS VM.

When a VM is present, a container escape lands in the VM guest, not directly on your host. This adds a boundary that does not exist when using **Docker Engine** on Linux or **Podman** on Linux (including Podman Desktop), where containers run natively and share the host kernel.

However, these VMs are designed for developer convenience, not as security boundaries. File sharing, socket forwarding, and networking features bridge the VM boundary in various ways, and the project directory is bind-mounted through it. Vibepit does not control or harden this VM layer, so you should not treat it as a guaranteed security control — but it does reduce the practical risk of a container escape reaching your host.
