# Deploy proxy server

> Run `ati proxy`, bind/port/`--env-keys`, optional `--features db` persistence, passthrough and HMAC sig-verify flags, VM/systemd examples, and health/JWKS probes.

- Repository: Parcha-ai/ati
- GitHub: https://github.com/Parcha-ai/ati
- Human docs: https://grok-wiki.com/public/docs/parcha-ai-ati-a9d4398f11fa
- Complete Markdown: https://grok-wiki.com/public/docs/parcha-ai-ati-a9d4398f11fa/llms-full.txt

## Source Files

- `src/proxy/server.rs`
- `src/main.rs`
- `deploy/examples/vm/README.md`
- `deploy/examples/vm/systemd/ati.service`
- `docs/PERSISTENCE.md`
- `src/core/passthrough.rs`
- `src/core/sig_verify.rs`

---

---
title: "Deploy proxy server"
description: "Run `ati proxy`, bind/port/`--env-keys`, optional `--features db` persistence, passthrough and HMAC sig-verify flags, VM/systemd examples, and health/JWKS probes."
---

`ati proxy` starts the same `ati` binary as an Axum HTTP server that holds provider credentials, loads manifests from `<ati_dir>/manifests`, validates JWT or DB-backed virtual keys on named routes, and optionally fronts raw upstream HTTP via passthrough manifests with HMAC sandbox signature verification.

```mermaid
flowchart LR
  subgraph sandbox["Agent sandbox"]
    CLI["ati run / SDK"]
  end
  subgraph proxy["ati proxy"]
    SIG["sig_verify middleware"]
    AUTH["auth middleware"]
    NAMED["/call /mcp /skills /help …"]
    PT["passthrough fallback"]
    KR["Keyring"]
  end
  subgraph upstream["Upstream APIs"]
    API["HTTP / MCP / OpenAPI"]
    EDGE["Passthrough upstreams"]
  end
  CLI -->|"Bearer JWT or Ati-Key"| SIG
  SIG --> AUTH
  AUTH --> NAMED
  AUTH --> PT
  NAMED --> KR
  PT --> KR
  KR --> API
  KR --> EDGE
```

## Prerequisites

| Requirement | Notes |
|---|---|
| Built `ati` binary | Release: `cargo build --release` or musl artifact from GitHub releases |
| ATI directory | `manifests/`, optional `keyring.enc` or `credentials`, optional `skills/` |
| JWT validation keys (production) | `ATI_JWT_PUBLIC_KEY` (ES256) or `ATI_JWT_SECRET` (HS256) on the proxy host |
| Sandbox routing | Set `ATI_PROXY_URL` and `ATI_SESSION_TOKEN` in agent environments |

For a first local smoke test without TLS or passthrough:

```bash
ati init --proxy
ati proxy --port 8090 --ati-dir ~/.ati
```

Default listen address is `127.0.0.1:8090` when `--bind` is omitted.

## Command reference

```bash
ati proxy [OPTIONS]
```

| Flag | Default | Purpose |
|---|---|---|
| `--port` | `8090` | TCP port |
| `--bind` | `127.0.0.1` | Bind address; use `0.0.0.0` for all interfaces |
| `--ati-dir` | `~/.ati` (or `ATI_DIR`) | Manifests, keyring, skills root |
| `--env-keys` | off | Load credentials from `ATI_KEY_*` env vars instead of disk keyring |
| `--migrate` | off | Apply embedded Postgres migrations when `ATI_DB_URL` is set (requires `db` feature build) |
| `--enable-passthrough` | off | Compile and serve `handler = "passthrough"` routes |
| `--sig-verify-mode` | `log` | HMAC verification: `log`, `warn`, or `enforce` |
| `--sig-drift-seconds` | `60` | Max clock skew for signature timestamp |
| `--sig-exempt-paths` | built-in defaults | Comma-separated path globs exempt from HMAC check |

<ParamField body="--env-keys" type="boolean">
When set, the proxy builds the keyring from every non-empty environment variable prefixed with `ATI_KEY_`. The prefix is stripped and the remainder is lowercased to form the keyring entry name (for example `ATI_KEY_FINNHUB_API_KEY` → `finnhub_api_key`). No `keyring.enc` read occurs.
</ParamField>

<ParamField body="--enable-passthrough" type="boolean">
Enables manifest-driven raw HTTP/WebSocket reverse proxying on the router fallback. Passthrough routes skip JWT auth and rely on HMAC sig-verify (and upstream credential injection from the keyring). Without this flag, unmatched paths return 404.
</ParamField>

<ParamField body="--sig-verify-mode" type="enum">
`log` — always allow; structured-log validity (safe rollout default). `warn` — allow; set `X-Signature-Status` on responses. `enforce` — reject invalid or missing signatures with `403`; startup fails if `sandbox_signing_shared_secret` is absent in enforce mode.
</ParamField>

Production edge deployments often bind loopback and terminate TLS in front:

```bash
ati proxy \
  --bind 127.0.0.1 \
  --port 8080 \
  --ati-dir /var/lib/ati \
  --enable-passthrough \
  --sig-verify-mode enforce \
  --sig-drift-seconds 60
```

<Warning>
Do not expose `--enable-passthrough` on a public interface with `--sig-verify-mode log` or `warn` and no signing secret configured. Passthrough has no JWT layer; non-blocking sig-verify modes leave those routes effectively unauthenticated.
</Warning>

## ATI directory layout

The proxy resolves manifests at `<ati_dir>/manifests/*.toml` and skills at `<ati_dir>/skills/`. With `--ati-dir /var/lib/ati`, manifests belong at `/var/lib/ati/manifests/` (not under `/etc/ati` unless that path is your `ati_dir`).

| Path | Role |
|---|---|
| `<ati_dir>/manifests/` | Provider and tool definitions |
| `<ati_dir>/keyring.enc` | Encrypted credentials (preferred) |
| `<ati_dir>/credentials` | Plaintext fallback if no keyring |
| `<ati_dir>/skills/` | Local skill registry |

Keyring load order without `--env-keys`:

1. Decrypt `keyring.enc` with a one-shot session key (sealed mode)
2. Else decrypt with persistent local key material
3. Else load `credentials` plaintext file
4. Else empty keyring (authenticated tools fail at runtime)

Send `SIGHUP` after `ati edge rotate-keyring` to hot-reload the keyring and signing secret without restarting the process.

## Credential modes

<Tabs>
<Tab title="Encrypted keyring">

Standard production path: populate `keyring.enc` via `ati key set` or `ati edge bootstrap-keyring`, restrict ownership (`ati:ati`, mode `0600`), run the proxy without `--env-keys`.

</Tab>
<Tab title="Environment keys">

Container-friendly mode:

```bash
export ATI_KEY_FINNHUB_API_KEY="..."
export ATI_KEY_SANDBOX_SIGNING_SHARED_SECRET="..."   # HMAC secret for sig-verify
ati proxy --env-keys --port 8090
```

Signing secret keyring name remains `sandbox_signing_shared_secret` (env: `ATI_KEY_SANDBOX_SIGNING_SHARED_SECRET`).

</Tab>
</Tabs>

## Authentication on named routes

Named ATI routes (`/call`, `/mcp`, `/help`, `/tools`, `/skills`, `/skillati/*`, …) pass through `auth_middleware` after sig-verify (when not exempt).

| Credential | Header | Behavior |
|---|---|---|
| JWT | `Authorization: Bearer <jwt>` | Validated against `ATI_JWT_PUBLIC_KEY` or `ATI_JWT_SECRET`; scopes enforced per handler |
| Virtual key | `Authorization: Ati-Key <raw>` | DB lookup when persistence is connected; synthesizes scope claims |
| Dev (no JWT config) | none | Requests allowed unless an `Ati-Key` header is present |

Public endpoints bypass auth: `/health`, `/.well-known/jwks.json`.

Passthrough-matched requests skip JWT and use HMAC verification instead.

Configure signing keys and token issuance on the proxy host before production traffic — see the JWT configuration page in Related pages.

## HMAC sandbox signatures

Sandboxes sign outbound requests with HMAC-SHA256 over `{timestamp}.{METHOD}.{path}` using the shared secret in keyring entry `sandbox_signing_shared_secret`.

<ParamField body="X-Sandbox-Signature" type="header" required>
Format: `t=<unix_ts>,s=<hex_hmac>`. Optional `X-Sandbox-Job-Id` is logged only.
</ParamField>

Default exempt paths (overridable with `--sig-exempt-paths`):

- `/healthz`, `/health`
- `/.well-known/jwks.json`
- `/root/*`, `/npm/*`, `/otel/*` (package caches and OTel forwarding)

Rollout pattern from the VM example:

1. Start with `--sig-verify-mode log` for at least 24 hours.
2. Confirm structured logs show `valid=true` and no unexpected `valid=false`.
3. Switch to `--sig-verify-mode enforce` and restart (or reload unit).

## Passthrough manifests

Passthrough providers use `handler = "passthrough"` in TOML. At startup the proxy builds a longest-prefix router from `host_match`, `path_prefix`, `base_url`, auth injection, deny paths, and optional `forward_authorization_paths` for virtual-key upstreams.

Clone `deploy/examples/vm/manifests/example-passthrough.toml` per upstream. Enable routing only with `--enable-passthrough`.

```toml
[provider]
name = "example-service"
handler = "passthrough"
base_url = "https://api.example.com"
path_prefix = "/example"
strip_prefix = true
```

## Optional Postgres persistence (`db` feature)

Persistence is opt-in at compile time and runtime.

<CodeGroup>
```bash title="Build with db"
cargo build --release --features db --target x86_64-unknown-linux-musl
```

```bash title="Pre-built -db artifact"
curl -fsSL https://github.com/Parcha-ai/ati/releases/latest/download/ati-x86_64-unknown-linux-musl-db.tar.gz \
  | tar xz && sudo mv ati /usr/local/bin/
```
</CodeGroup>

| Variable / flag | Purpose |
|---|---|
| `ATI_DB_URL` | Postgres connection string; unset → `db: disabled` in health |
| `--migrate` | Apply embedded migrations on startup (idempotent) |
| `ATI_ADMIN_TOKEN` | Bearer for `/admin/keys/*` when DB is connected |

Startup behavior:

- `ATI_DB_URL` unset → proxy starts; persistence disabled
- `ATI_DB_URL` set, DB unreachable → proxy **exits** with error
- `ATI_DB_URL` set, binary built without `db` → proxy **exits** with rebuild hint

PR 1 schema only: tables exist; per-call audit and virtual-key writes land in later PRs. See `docs/PERSISTENCE.md` for schema and operations detail.

## VM and systemd example

`deploy/examples/vm/` documents a static-egress edge VM: Caddy terminates TLS on `:443` and reverse-proxies to `ati` on `127.0.0.1:8080`.

<Steps>
<Step title="Install binary and layout">

```bash
curl -L https://github.com/Parcha-ai/ati/releases/latest/download/ati-x86_64-unknown-linux-musl \
  -o /usr/local/bin/ati && chmod +x /usr/local/bin/ati
useradd --system --no-create-home --shell /usr/sbin/nologin ati
install -d -o ati -g ati -m 0750 /var/lib/ati/manifests
```

</Step>
<Step title="Install unit files">

Copy `deploy/examples/vm/systemd/ati.service` to `/etc/systemd/system/`. The unit runs:

```ini
ExecStart=/usr/local/bin/ati proxy \
    --bind 127.0.0.1 \
    --port 8080 \
    --ati-dir /var/lib/ati \
    --enable-passthrough \
    --sig-verify-mode log \
    --sig-drift-seconds 60
```

Hardening includes `ProtectSystem=strict`, `NoNewPrivileges=true`, and `LimitMEMLOCK=64M` for keyring `mlock`.

</Step>
<Step title="Bootstrap keyring">

```bash
sudo -u ati ati edge bootstrap-keyring \
  --vault "Production Secrets" \
  --item "ATI Edge VM Keyring" \
  --ati-dir /var/lib/ati \
  --op-token-file /etc/op-service-account-token
systemctl enable --now ati
```

</Step>
</Steps>

Optional: `systemd/ati.service.d/otel.conf.example` when built with `--features otel`; weekly keyring rotation via `ati-rotate-keyring.timer`.

## Health and JWKS probes

Use these for load balancers, systemd `ExecStartPost`, and deploy gates.

:::endpoint GET /health
Liveness and inventory snapshot. No authentication.

<ResponseField name="status" type="string">
Always `"ok"` when the handler runs.
</ResponseField>

<ResponseField name="version" type="string">
Crate version from the running binary.
</ResponseField>

<ResponseField name="tools" type="number">
Count of public tools after MCP/OpenAPI discovery at startup.
</ResponseField>

<ResponseField name="providers" type="number">
Loaded provider count.
</ResponseField>

<ResponseField name="skills" type="number">
Local skill registry count.
</ResponseField>

<ResponseField name="auth" type="string">
`"jwt"` when JWT validation is configured; `"disabled"` otherwise.
</ResponseField>

<ResponseField name="db" type="string">
`"disabled"` or `"connected"` (pool configured; not a live DB probe in PR 1).
</ResponseField>
:::

<RequestExample>
```bash
curl -fsS http://127.0.0.1:8090/health | jq
```
</RequestExample>

<ResponseExample>
```json
{
  "status": "ok",
  "version": "0.8.0-rc.7",
  "tools": 104,
  "providers": 10,
  "skills": 0,
  "auth": "jwt",
  "db": "disabled"
}
```
</ResponseExample>

:::endpoint GET /.well-known/jwks.json
JWKS document for ES256 JWT validation by sandboxes or orchestrators. Requires `ATI_JWT_PUBLIC_KEY` at proxy startup.

Returns `404` with `{"error":"JWKS not configured"}` when only HS256 secret is configured (no public key PEM).
:::

<RequestExample>
```bash
curl -fsS http://127.0.0.1:8090/.well-known/jwks.json | jq
```
</RequestExample>

<Check>
`/health` and `/.well-known/jwks.json` are exempt from JWT and default HMAC exempt lists — suitable for unauthenticated probes.
</Check>

## Connect sandboxes

Point agents at the proxy; credentials stay on the server:

```bash
export ATI_PROXY_URL="http://proxy-host:8090"
export ATI_SESSION_TOKEN="<jwt-from-ati-token-issue>"
ati run github:search_repositories --query "ati"
```

The client sends `Authorization: Bearer` on every proxy request. Scope enforcement matches local mode.

## Troubleshooting

| Symptom | Likely cause | Mitigation |
|---|---|---|
| Proxy exits at startup with enforce + passthrough | Missing `sandbox_signing_shared_secret` | Load keyring or set `ATI_KEY_SANDBOX_SIGNING_SHARED_SECRET` with `--env-keys` |
| `401` on `/call` | JWT missing, invalid, or wrong audience | Check `ATI_JWT_*` on proxy; re-issue token with matching `aud` |
| `403` on passthrough paths | Sig-verify enforce mode | Fix sandbox signer; verify clock drift within `--sig-drift-seconds` |
| Tools count `0` in `/health` | Empty or wrong `manifests/` path | Confirm `--ati-dir` and directory permissions |
| Authenticated tools fail | Empty keyring | Add keys via `ati key set` or env vars |
| `db: connected` but admin 503 | `ATI_ADMIN_TOKEN` unset | Set admin bearer for `/admin/keys/*` |
| `EAGAIN` / mlock warnings | Low `LimitMEMLOCK` | Raise systemd `LimitMEMLOCK` (VM unit uses `64M`) |

Structured startup log line includes `auth`, `keyring` source, `passthrough` route count, `sig_verify_mode`, and `sig_verify_secret` boolean for operator review.

## Related pages

<CardGroup>
<Card title="Execution modes" href="/execution-modes">
When sandboxes use `ATI_PROXY_URL` versus local keyring mode and threat-model trade-offs.
</Card>
<Card title="Configure JWT and keys" href="/configure-jwt-and-keys">
`ati init --proxy`, ES256/HS256 env vars, and token issuance for `ATI_SESSION_TOKEN`.
</Card>
<Card title="Proxy API reference" href="/proxy-api-reference">
Full route list, request shapes, and admin endpoints.
</Card>
<Card title="Environment variables" href="/environment-variables">
`ATI_PROXY_URL`, `ATI_DB_URL`, `ATI_ADMIN_TOKEN`, JWT, and OTel variables.
</Card>
<Card title="Security and production" href="/security-and-production">
Proxy hardening, SSRF, download allowlists, and sig-verify threat model.
</Card>
<Card title="Build, test, and troubleshooting" href="/build-test-and-troubleshooting">
Musl builds, feature flags, and E2E scripts including `scripts/test_proxy_server_e2e.sh`.
</Card>
</CardGroup>
