> ## Documentation Index
> Fetch the complete documentation index at: https://docs.declaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Security Proxy

> How the transparent TLS proxy intercepts sandbox traffic, generates per-sandbox CA certificates, and runs the PII and injection scanning pipeline.

The security proxy runs host-side in each sandbox's own network namespace, outside the guest VM, when PII scanning, injection defense, or transformation rules are enabled. It acts as a transparent edge proxy for all outbound traffic from the sandbox workload — and because it sits outside the guest on the only egress path, the workload cannot bypass it.

## Role in the architecture

The security proxy sits between the sandbox workload and the internet:

```mermaid theme={null}
graph LR
    Workload["Agent Workload\n(Python/Node/etc)"]
    IPT["iptables REDIRECT\n:443 -> proxy port"]
    Proxy["Security Proxy\n(Go, host netns)"]
    Dest["Destination\n(e.g. api.openai.com)"]

    Workload -->|"HTTPS"| IPT
    IPT --> Proxy
    Proxy -->|"TLS ClientHello\npeek for SNI"| SNI["SNI extracted\n(no decrypt)"]
    SNI -->|"domain check"| Policy{"Policy?"}
    Policy -->|"blocked"| Reject["TCP RST"]
    Policy -->|"allowed, no scan"| FwdDirect["Forward directly\n(TLS passthrough)"]
    Policy -->|"allowed, PII/injection"| edge proxy["TLS edge proxy\ndecrypt + scan + re-encrypt"]
    edge proxy --> Dest
    Dest -->|"response"| edge proxy
    edge proxy -->|"rehydrate PII"| Workload
```

## Per-sandbox CA certificates

Each sandbox gets a unique CA certificate generated at creation time using ECDSA P-256.

```go theme={null}
// From infra/orchestrator/internal/tcpfirewall/security.go
func GenerateSandboxCA(sandboxID string) (*x509.Certificate, *ecdsa.PrivateKey, []byte, error) {
    key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
    template := &x509.Certificate{
        Subject: pkix.Name{
            Organization: []string{"Declaw Sandbox CA"},
            CommonName:   "Declaw CA - " + sandboxID,
        },
        NotBefore:    time.Now().Add(-1 * time.Hour),
        NotAfter:     time.Now().Add(24 * time.Hour),
        IsCA:         true,
        KeyUsage:     x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
    }
    // ... create and parse cert
}
```

The CA cert is:

1. Written to `/opt/declaw/run/<sandbox_id>/ca.pem`
2. Injected into the sandbox VM's trust store at boot (before envd starts)
3. Used by the security proxy to sign leaf certificates on-the-fly for each destination hostname

When the agent code makes an HTTPS request to `api.openai.com`, the proxy presents a certificate signed by the sandbox CA (which the VM trusts), terminates the TLS connection, reads the plaintext body, runs the scanning pipeline, then establishes a new TLS connection to the real `api.openai.com` and forwards the (possibly modified) request.

## Leaf certificate generation

For each new HTTPS destination, the proxy generates a short-lived leaf certificate signed by the sandbox CA:

```go theme={null}
func generateCertForHost(hostname string, caCert *x509.Certificate, caKey *ecdsa.PrivateKey) (*tls.Certificate, error) {
    key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
    template := &x509.Certificate{
        SerialNumber: randomSerial(),
        Subject:      pkix.Name{CommonName: hostname},
        DNSNames:     []string{hostname},
        NotBefore:    time.Now().Add(-1 * time.Hour),
        NotAfter:     time.Now().Add(1 * time.Hour),
        KeyUsage:     x509.KeyUsageDigitalSignature,
        ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
    }
    certDER, _ := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
    return &tls.Certificate{...}, nil
}
```

## TLS passthrough mode

When only network policies are configured (no PII scanning, no transformations), the proxy operates in **passthrough mode**:

1. Peek at the TLS ClientHello to extract the SNI hostname (without decrypting)
2. Check the SNI against the domain allowlist/denylist
3. If allowed, forward the raw TCP stream directly — no TLS termination, no decryption

This means pure network policies have zero TLS overhead. TLS interception only activates when body inspection is required.

## Scanning pipeline execution

When TLS interception is active, the proxy:

1. Terminates TLS from the client
2. Reads the HTTP request headers and body
3. Runs the pipeline stages in order:
   * Transform rules (outbound) → body may be modified
   * PII scanner → PII tokens substituted, session map updated
   * Injection defense → body checked; blocked if injection detected
4. Establishes TLS to the real destination
5. Forwards the modified request
6. Receives the response
7. Runs the response pipeline (inbound transforms, then PII rehydration; the response body is not injection-scanned or blocked — it's passed through, with untrusted content captured as session context for the optional judge)
8. Returns modified response to the workload

## Namespace binding

The proxy is a per-sandbox, host-side component that manages the TCP listener for a sandbox's network namespace. It is created in the host's root namespace but listens on a socket bound inside the sandbox namespace using `ip netns exec`.

```
Host namespace: Orchestrator <-> proxy
Sandbox namespace: iptables REDIRECT -> proxy listening socket
sandbox VM: Workload -> (via TAP) -> iptables -> proxy socket
```

The proxy binds separate listeners for HTTP (port 80) and HTTPS (port 443), with a third port for other TCP traffic that only performs domain-level filtering without edge proxy.

## Domain matching

Domain matching supports three formats:

| Format             | Example            | Behavior                        |
| ------------------ | ------------------ | ------------------------------- |
| Exact              | `api.openai.com`   | Matches only the exact hostname |
| Wildcard           | `*.openai.com`     | Matches any direct subdomain    |
| Regex (prefix `~`) | `~.*\.openai\.com` | Full RE2 regex match            |

```go theme={null}
func isDomainMatch(hostname string, domains []string) bool {
    h := strings.ToLower(hostname)
    for _, d := range domains {
        if strings.HasPrefix(d, "~") {
            // regex match
        } else if d == h {
            return true  // exact
        } else if strings.HasPrefix(d, "*.") {
            suffix := d[1:]  // ".openai.com"
            if strings.HasSuffix(h, suffix) {
                return true  // wildcard subdomain
            }
        }
    }
    return false
}
```

## Guardrails Service integration

The proxy sends PII scan requests and injection scan requests to the Guardrails Service HTTP API at `GUARDRAILS_URL`. Each request is a JSON POST with the text to scan and the scanner types to use.

If the Guardrails Service is unreachable (timeout of 10 seconds), the proxy falls back to the built-in regex scanner transparently. No error is surfaced to the workload.

## Audit event streaming

The proxy writes audit events after each request/response cycle, and the orchestrator collects them and exposes them through the API.
