> ## Documentation Index
> Fetch the complete documentation index at: https://vvennela-ftl-18-95.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Credential Swap Proxy

> HTTP/HTTPS intercepting proxy that swaps shadow credentials for real ones

The credential swap proxy allows the coding agent to run code with real API credentials without ever seeing the actual values. The agent works with "shadow" credentials while the proxy transparently swaps them for real ones.

## Architecture

The proxy intercepts all HTTP/HTTPS traffic from the sandbox and performs credential substitution:

```python Source: ftl/proxy.py:1-22 theme={null}
"""HTTP/HTTPS intercepting proxy that swaps shadow credentials for real ones.

When the coding agent runs user code inside the sandbox, that code makes real
API calls using shadow credentials (e.g. STRIPE_SECRET_KEY=ftl_shadow_...).
This proxy intercepts those calls and swaps in the real keys before they reach
the upstream server — so live code can run and test against real APIs while the
agent never learns the actual credentials.

Architecture:
    - HTTP requests:  proxy receives full request, swaps in headers/body, forwards
    - HTTPS requests: MITM via CONNECT tunnel with a per-host TLS leaf cert signed
                      by an ephemeral CA installed in the container trust store
"""
```

**1. Agent sees shadow credentials**

Environment variables contain placeholder values like `ftl_shadow_sk_test_...`

**2. Code makes API request**

User code runs and makes HTTP/HTTPS requests with shadow credentials

**3. Proxy intercepts traffic**

All requests route through the proxy via HTTP\_PROXY/HTTPS\_PROXY env vars

**4. Credentials swapped**

Shadow values replaced with real credentials from swap table

**5. Request forwarded**

Modified request sent to upstream API with real credentials

## HTTPS MITM

For HTTPS traffic, the proxy performs man-in-the-middle interception:

### Ephemeral CA Certificate

```python Source: ftl/proxy.py:45-82 theme={null}
def _generate_ca():
    """Generate an ephemeral CA certificate + private key. Returns (key, cert)."""
    from cryptography import x509
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography.x509.oid import NameOID

    key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    name = x509.Name([
        x509.NameAttribute(NameOID.COMMON_NAME, "FTL Proxy CA"),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, "FTL"),
    ])
    now = datetime.now(timezone.utc)
    cert = (
        x509.CertificateBuilder()
        .subject_name(name)
        .issuer_name(name)
        .public_key(key.public_key())
        .serial_number(x509.random_serial_number())
        .not_valid_before(now)
        .not_valid_after(now + timedelta(days=1))
        .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
        .sign(key, hashes.SHA256())
    )
```

> [!NOTE]
>
> The CA certificate is ephemeral (1-day validity) and generated fresh for each session.

### Per-Host Leaf Certificates

For each HTTPS host, the proxy generates a leaf certificate signed by the CA:

```python Source: ftl/proxy.py:182-208 theme={null}
def _get_ssl_context(self, hostname):
    with self.server.ssl_ctx_lock:
        if hostname in self.server.ssl_ctx_cache:
            return self.server.ssl_ctx_cache[hostname]

    key_pem, cert_pem = _generate_leaf_cert(
        hostname,
        self.server.ca_key_pem,
        self.server.ca_cert_pem,
    )

    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)

    # Write combined PEM to a temp file, load it, then delete
    with tempfile.NamedTemporaryFile(
        mode="wb", suffix=".pem", delete=False
    ) as tf:
        tf.write(key_pem + cert_pem)
        tmp_path = tf.name
    try:
        ctx.load_cert_chain(tmp_path)
    finally:
        os.unlink(tmp_path)
```

## Credential Swapping

The swap happens at the byte level in both headers and body:

```python Source: ftl/proxy.py:135-146 theme={null}
def _swap(self, data: bytes) -> bytes:
    """Replace shadow credential bytes with real credential bytes."""
    for shadow, real in self.server.swap_table.items():
        if shadow.encode() in data:
            data = data.replace(shadow.encode(), real.encode())
    return data

def _swap_str(self, s: str) -> str:
    for shadow, real in self.server.swap_table.items():
        if shadow in s:
            s = s.replace(shadow, real)
    return s
```

## Installation in Container

The proxy CA must be installed in the container's trust store:

```python Source: ftl/proxy.py:377-395 theme={null}
def install_ca_in_container(self, sandbox):
    """Install the proxy CA into the container's trust store.

    - System store (Python, curl): /usr/local/share/ca-certificates/ + update-ca-certificates
    - Node.js store: /tmp/ftl-proxy-ca.crt, referenced via NODE_EXTRA_CA_CERTS

    Must be called after sandbox.boot() and before the agent runs.
    """
    import base64
    cert_b64 = base64.b64encode(self.ca_cert_pem).decode()
    cmds = (
        # System CA store (curl, Python requests, etc.)
        f"echo '{cert_b64}' | base64 -d"
        f" > /usr/local/share/ca-certificates/ftl-proxy.crt"
        f" && update-ca-certificates -q"
        # Node.js CA file (Claude Code, any other Node apps)
        f" && echo '{cert_b64}' | base64 -d > /tmp/ftl-proxy-ca.crt"
    )
    sandbox.exec_as_root(cmds)
```

> [!WARNING]
>
> Node.js requires a separate CA file via `NODE_EXTRA_CA_CERTS` because it doesn't use the system trust store.

## Agent Domain Bypass

The agent's own API calls bypass the proxy to avoid SSL issues:

```python Source: ftl/proxy.py:364-376 theme={null}
# Domains the agent uses for its own API calls — nothing to swap here,
# keep them out of the proxy so Node.js SSL verification is never an issue.
_AGENT_DOMAINS = (
    "api.anthropic.com,"           # Claude Code
    "*.amazonaws.com,"             # Kiro / Bedrock
    "amazonaws.com,"
    "api.openai.com,"              # in case agent uses OpenAI directly
    "oauth2.googleapis.com,"       # Google auth
    "accounts.google.com"
)

_NO_PROXY_BASE = "localhost,127.0.0.1,::1"
```

These domains are added to `NO_PROXY` to route directly without interception.

## Usage Example

```python Setup theme={null}
from ftl.proxy import CredentialSwapProxy

# Create swap table: shadow -> real
swap_table = {
    "ftl_shadow_sk_test_123": "sk_live_real_key_456",
    "ftl_shadow_api_key": "real_api_key_789"
}

proxy = CredentialSwapProxy(swap_table)
proxy.start()
proxy.install_ca_in_container(sandbox)
```

```python Environment Variables theme={null}
# Inject proxy settings into sandbox
env = proxy.env_vars()
# {
#   "HTTP_PROXY": "http://host.docker.internal:12345",
#   "HTTPS_PROXY": "http://host.docker.internal:12345",
#   "NO_PROXY": "localhost,127.0.0.1,::1,api.anthropic.com,...",
#   "NODE_EXTRA_CA_CERTS": "/tmp/ftl-proxy-ca.crt"
# }
```

```python Cleanup theme={null}
# Stop proxy when done
proxy.stop()
```

## Requirements

The proxy requires the `cryptography` library:

```bash theme={null}
pip install -e ".[proxy]"
```

> [!TIP]
>
> The proxy runs on `127.0.0.1` with a random free port, accessible from Docker via `host.docker.internal`.

## Security Model

> [card] Ephemeral CA
>
> CA certificate has 1-day validity and is generated per-session

> [card] Memory-Only
>
> Real credentials never written to disk or logs

> [card] Agent Isolation
>
> Agent only sees shadow credentials in environment

> [card] Selective Bypass
>
> Agent API domains bypass proxy to avoid trust issues


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.