# Egress Auth

Egress auth rules tell Sandbox0 when to apply outbound authentication for matching traffic.

This configuration is carried on the network surface as `network.egress.credentialRules`, but conceptually it belongs to the credential system:

- `credentialBindings` define what auth material exists.
- `credentialRules` define where and when that material is used.
- `trafficRules` still decide whether the traffic itself is allowed.

Bindings are configured under the same `network` object everywhere:

- template `spec.network`
- sandbox claim `config.network`
- runtime update `PUT /api/v1/sandboxes/{id}/network`

## Binding Fields

| Field | Required | Description |
|-------|----------|-------------|
| `ref` | Yes | Stable local name matched by `credentialRules[*].credentialRef` |
| `sourceRef` | Yes | Region-scoped credential source reference to resolve |
| `projection` | Yes | How the source is shaped for runtime injection |
| `cachePolicy.ttl` | No | Optional cache lifetime duration string such as `5m` or `1h` for resolved material |

## Binding Example

This binding projects a stored token into an `Authorization` header:

```yaml
credentialBindings:
    - ref: gh-token
      sourceRef: github-source
      projection:
          type: http_headers
          httpHeaders:
              headers:
                  - name: Authorization
                    valueTemplate: "Bearer {{token}}"
```

In this example, `{{token}}` refers to the `token` key inside a `static_headers` source's `values` map. If the referenced key is missing, header rendering fails.

## Rule Fields

| Field | Required | Description |
|-------|----------|-------------|
| `credentialRef` | Yes | Binding reference to resolve |
| `protocol` | No | Supported values: `http`, `https`, `grpc`, `tls`, `ssh`, `socks5`, `mqtt`, `redis` |
| `domains` | No | Domain match list |
| `ports` | No | Port/protocol constraints |
| `httpMatch` | No | HTTP request-level matcher for `http`, `https`, and `grpc` rules |
| `tlsMode` | No | `passthrough` or `terminate-reoriginate` |
| `failurePolicy` | No | `fail-closed` or `fail-open` |
| `rollout` | No | `enabled` or `disabled` |

## HTTPS Trust Material

When a credential rule uses TLS interception such as `protocol: https`, `protocol: grpc`, or `protocol: tls` with `tlsMode: terminate-reoriginate`, Sandbox0 provides the netd MITM CA to sandbox containers and configures procd-managed processes to trust it automatically.

- File: `/var/run/sandbox0/netd/mitm-ca.crt`
- Env: `SANDBOX0_NETD_MITM_CA_FILE=/var/run/sandbox0/netd/mitm-ca.crt`

At procd startup, Sandbox0 writes a combined CA bundle containing the image's system roots plus the netd MITM CA, then exports it through common TLS environment variables such as `SSL_CERT_FILE`, `NODE_EXTRA_CA_CERTS`, `REQUESTS_CA_BUNDLE`, `CURL_CA_BUNDLE`, `GIT_SSL_CAINFO`, and `AWS_CA_BUNDLE`. Applications normally do not need to handle the MITM CA themselves.

## Example Policy

This policy allows access to the GitHub API, binds a token source, and injects an `Authorization` header for matching HTTPS requests:

```yaml
mode: block-all
egress:
    trafficRules:
        - name: allow-github-api
          action: allow
          domains:
              - api.github.com
          ports:
              - port: 443
                protocol: tcp
    credentialRules:
        - name: github-auth
          credentialRef: gh-token
          protocol: https
          domains:
              - api.github.com
          ports:
              - port: 443
                protocol: tcp
credentialBindings:
    - ref: gh-token
      sourceRef: github-source
      projection:
          type: http_headers
          httpHeaders:
              headers:
                  - name: Authorization
                    valueTemplate: "Bearer {{token}}"
```

Here, `{{token}}` is rendered from the `token` entry stored in the bound `static_headers` source. Keep the projection template keys aligned with the source contents.

## HTTP Request Matching

Use `httpMatch` when a credential should only be injected for specific API requests on a broader allowed destination. Non-matching requests still follow `trafficRules`, but do not resolve or inject the credential.

For `protocol: https` and `protocol: grpc`, request matching requires `tlsMode: terminate-reoriginate`.

```yaml
mode: block-all
egress:
    trafficRules:
        - name: allow-github-api
          action: allow
          domains:
              - api.github.com
          ports:
              - port: 443
                protocol: tcp
    credentialRules:
        - name: github-write-auth
          credentialRef: gh-token
          protocol: https
          tlsMode: terminate-reoriginate
          domains:
              - api.github.com
          ports:
              - port: 443
                protocol: tcp
          httpMatch:
              methods:
                  - POST
                  - PUT
                  - PATCH
                  - DELETE
              pathPrefixes:
                  - /repos/
              headers:
                  - name: accept
                    values:
                        - application/vnd.github+json
credentialBindings:
    - ref: gh-token
      sourceRef: github-source
      projection:
          type: http_headers
          httpHeaders:
              headers:
                  - name: Authorization
                    valueTemplate: "Bearer {{token}}"
```

## SSH Transparent Proxy

SSH egress auth uses a transparent proxy model. The sandbox-side SSH client authenticates to netd with a fake public/private key pair, and netd authenticates to the upstream SSH server with the private key stored in a `static_ssh_private_key` credential source.

The upstream private key is never written into the sandbox. The sandbox only needs the fake private key that matches `projection.sshProxy.sandboxPublicKeys`.

```yaml
mode: block-all
egress:
    trafficRules:
        - name: allow-git-ssh
          action: allow
          domains:
              - github.com
          ports:
              - port: 22
                protocol: tcp
          appProtocols:
              - ssh
    credentialRules:
        - name: git-ssh-auth
          credentialRef: git-ssh
          protocol: ssh
          domains:
              - github.com
          ports:
              - port: 22
                protocol: tcp
credentialBindings:
    - ref: git-ssh
      sourceRef: github-ssh-key
      projection:
          type: ssh_proxy
          sshProxy:
              upstreamUsername: git
              sandboxPublicKeys:
                  - ssh-ed25519 AAAA... sandbox-fake-key
              knownHosts:
                  - github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
```

For Git-over-SSH, use the normal Git SSH URL and configure Git or OpenSSH in the sandbox to use the fake private key. Netd terminates the sandbox SSH session, verifies the fake key, verifies the upstream host key against `knownHosts`, and re-originates the session with the stored upstream private key.

## Update Runtime Policy

<Endpoint method="PUT">
/api/v1/sandboxes/{'{id}'}/network
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `_, err = sandbox.UpdateNetworkPolicy(ctx, apispec.SandboxNetworkPolicy{
    Mode: apispec.SandboxNetworkPolicyModeBlockAll,
    Egress: apispec.NewOptNetworkEgressPolicy(apispec.NetworkEgressPolicy{
        TrafficRules: []apispec.TrafficRule{
            {
                Name: apispec.NewOptString("allow-github-api"),
                Action: apispec.TrafficRuleActionAllow,
                Domains: []string{"api.github.com"},
                Ports: []apispec.PortSpec{
                    {
                        Port: 443,
                        Protocol: apispec.NewOptString("tcp"),
                    },
                },
            },
        },
        CredentialRules: []apispec.EgressCredentialRule{
            {
                Name: apispec.NewOptString("github-auth"),
                CredentialRef: "gh-token",
                Protocol: apispec.NewOptEgressAuthProtocol(apispec.EgressAuthProtocolHTTPS),
                Domains: []string{"api.github.com"},
                Ports: []apispec.PortSpec{
                    {
                        Port: 443,
                        Protocol: apispec.NewOptString("tcp"),
                    },
                },
                FailurePolicy: apispec.NewOptEgressAuthFailurePolicy(apispec.EgressAuthFailurePolicyFailClosed),
            },
        },
    }),
    CredentialBindings: []apispec.CredentialBinding{
        {
            Ref: "gh-token",
            SourceRef: "github-source",
            Projection: apispec.ProjectionSpec{
                Type: apispec.CredentialProjectionTypeHTTPHeaders,
                HttpHeaders: apispec.NewOptHTTPHeadersProjection(apispec.HTTPHeadersProjection{
                    Headers: []apispec.ProjectedHeader{
                        {
                            Name: "Authorization",
                            ValueTemplate: "Bearer {{token}}",
                        },
                    },
                }),
            },
        },
    },
})
if err != nil {
    log.Fatal(err)
}`
    },
    {
      label: "Python",
      language: "python",
      code: `from sandbox0.apispec.models.sandbox_network_policy import SandboxNetworkPolicy

sandbox.update_network_policy(
    SandboxNetworkPolicy.from_dict({
        "mode": "block-all",
        "egress": {
            "trafficRules": [
                {
                    "name": "allow-github-api",
                    "action": "allow",
                    "domains": ["api.github.com"],
                    "ports": [{"port": 443, "protocol": "tcp"}],
                },
            ],
            "credentialRules": [
                {
                    "name": "github-auth",
                    "credentialRef": "gh-token",
                    "protocol": "https",
                    "domains": ["api.github.com"],
                    "ports": [{"port": 443, "protocol": "tcp"}],
                    "failurePolicy": "fail-closed",
                },
            ],
        },
        "credentialBindings": [
            {
                "ref": "gh-token",
                "sourceRef": "github-source",
                "projection": {
                    "type": "http_headers",
                    "httpHeaders": {
                        "headers": [
                            {
                                "name": "Authorization",
                                "valueTemplate": "Bearer {{token}}",
                            },
                        ],
                    },
                },
            },
        ],
    })
)`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `await sandbox.updateNetworkPolicy({
    mode: 'block-all',
    egress: {
        trafficRules: [
            {
                name: 'allow-github-api',
                action: 'allow',
                domains: ['api.github.com'],
                ports: [{ port: 443, protocol: 'tcp' }],
            },
        ],
        credentialRules: [
            {
                name: 'github-auth',
                credentialRef: 'gh-token',
                protocol: 'https',
                domains: ['api.github.com'],
                ports: [{ port: 443, protocol: 'tcp' }],
                failurePolicy: 'fail-closed',
            },
        ],
    },
    credentialBindings: [
        {
            ref: 'gh-token',
            sourceRef: 'github-source',
            projection: {
                type: 'http_headers',
                httpHeaders: {
                    headers: [
                        {
                            name: 'Authorization',
                            valueTemplate: 'Bearer {{token}}',
                        },
                    ],
                },
            },
        },
    ],
});`
    },
    {
      label: "CLI",
      language: "bash",
      code: `# Structured flags: good for scripts and one-off updates
s0 sandbox network update --mode block-all \\
  --traffic-rule '{"name":"allow-github-api","action":"allow","domains":["api.github.com"],"ports":[{"port":443,"protocol":"tcp"}]}' \\
  --credential-binding '{"ref":"gh-token","sourceRef":"github-source","projection":{"type":"http_headers","httpHeaders":{"headers":[{"name":"Authorization","valueTemplate":"Bearer {{token}}"}]}}}' \\
  --credential-rule '{"name":"github-auth","credentialRef":"gh-token","protocol":"https","domains":["api.github.com"],"ports":[{"port":443,"protocol":"tcp"}],"failurePolicy":"fail-closed"}' \\
  -s sb_abc123

# Policy file: easier to read and review for larger policies
cat <<'EOF' > network.yaml
mode: block-all
egress:
    trafficRules:
        - name: allow-github-api
          action: allow
          domains:
              - api.github.com
          ports:
              - port: 443
                protocol: tcp
    credentialRules:
        - name: github-auth
          credentialRef: gh-token
          protocol: https
          domains:
              - api.github.com
          ports:
              - port: 443
                protocol: tcp
          failurePolicy: fail-closed
credentialBindings:
    - ref: gh-token
      sourceRef: github-source
      projection:
          type: http_headers
          httpHeaders:
              headers:
                  - name: Authorization
                    valueTemplate: "Bearer {{token}}"
EOF

s0 sandbox network update --policy-file network.yaml -s sb_abc123`
    }
  ]}
/>

<Callout variant="info">
For supported protocols, auth material is resolved and injected by the egress path. The sandbox process does not need direct access to the raw credential source contents.
</Callout>

## Next Steps

<CardGroup>
  <Card title="Overview" href="/docs/template" cta="Continue">
    Define reusable sandbox environments before creating or scaling sandboxes.
  </Card>

  <Card title="Custom Images" href="/docs/template/images" cta="Continue">
    Build and reference custom container images for sandbox templates.
  </Card>
</CardGroup>
