# Configure the model gateway

Source: https://www.testifysec.com/docs/badger/how-to/configure-gateway/

Build and configure hardened-gateway, the service that holds the model credential, audits every request and checks the signed profile.

The model gateway, `hardened-gateway`, is the only way a managed Badger session reaches a model. It forwards `POST /v1/chat/completions` (or, if configured, `POST /v1/responses`) from a Unix socket to one HTTPS endpoint and one model that you choose. The session never sees the provider credential.

 

Status: available on `main`, in `testifysec/gateway/`. It does not claim FIPS 140-3 validation.

 

## Build

 

With a Go toolchain already installed (the gateway uses only the standard library):

 

```sh
cd testifysec/gateway
CGO_ENABLED=0 go build -trimpath -o /protected/bin/hardened-gateway .
go test ./...
```

 

## Write the configuration

 

The configuration, the credentials and the audit spool belong to the gateway's own service user. The gateway refuses a configuration or credential file that is not a regular file readable only by its owner, in a directory whose mode is exactly `0700`. Keep credentials off persistent disk: the example takes the provider token over an inherited pipe and reads the collector token from a memory-backed file system.

 

```json
{
  "schema": "testifysec.hardened-opencode.gateway/v1",
  "socket": "/run/hardened-session/gateway.sock",
  "spool": "/var/lib/hardened-session/audit",
  "providerURL": "https://model.example.invalid/v1/chat/completions",
  "providerTokenFd": 3,
  "collectorURL": "https://collector.example.invalid/v1/logs",
  "collectorTokenFile": "/run/hardened-session/collector.token",
  "model": "approved",
  "tenant": "example-tenant",
  "session": "example-session",
  "agent": "spiffe://platform.example.invalid/tenant/example/agent/example",
  "profileDigest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "maxRecords": 1000,
  "maxOfflineSeconds": 300,
  "timeoutSeconds": 120,
  "developmentLoopback": false,
  "platformConfigFile": "/etc/hardened-session/platform.json"
}
```

 

| Key | Meaning |
| --- | --- |
| `socket` | The Unix socket the gateway listens on. Mount this one file into the session at `/run/hardened-gateway/gateway.sock`, the path the managed build is compiled with. |
| `spool` | Directory for the audit spool. One gateway per spool; a lock refuses a second. |
| `providerURL` | The model endpoint. The protocol path may sit under a prefix, such as `/broker/v1/chat/completions`. |
| `protocol` | Optional. `chat-completions` (default) or `responses`, which accepts only the stateless subset with `store: false`. |
| `providerTokenFile` or `providerTokenFd` | Where the provider credential comes from. See below. |
| `providerCertSha256` | Optional. Lowercase hex SHA-256 of the provider's SubjectPublicKeyInfo, for a provider no system root signs. |
| `collectorURL`, `collectorTokenFile` | OTLP/HTTP log endpoint for audit events, and its bearer token. An empty token path is only for a collector whose TLS boundary already authorizes the gateway. |
| `model` | The exact model name sent to the provider. |
| `tenant`, `session`, `agent`, `profileDigest` | Recorded in every audit event. |
| `maxRecords`, `maxOfflineSeconds` | When the spool holds this many undelivered records, or delivery has failed this long, new requests get 503 and no provider call. |
| `timeoutSeconds` | Upper bound on one provider call. |
| `developmentLoopback` | Allows cleartext to literal loopback addresses, for the test harness only. |
| `platformConfigFile` | The TestifySec platform session check. Required unless `developmentLoopback` is true; see [the platform page](https://www.testifysec.com/docs/badger/concepts/platform). |
| `signedProfileFile`, `admissionExecutable`, `admissionSha256` | The signed profile and the pinned admission executable that verifies it before every provider request. |

 

Unknown keys are refused. Production URLs need TLS with normal certificate and hostname checks, and the gateway ignores proxy environment variables. There is no switch to turn TLS verification off; install your enclave's roots in the host trust store, or pin the provider's key.

 

## Provide the credentials

 

**Provider token.** With `providerTokenFd`, the gateway reads the token from an inherited file descriptor (3 or higher) that the process starting it, such as the session broker, holds open: each line replaces the token in memory, an invalid line or a closed pipe clears it, and with no token the gateway makes no provider call. The token never touches disk, the command line or the environment. `providerTokenFile` exists for test harnesses; a plaintext token file on persistent storage does not meet the hardening requirement.

 

**Collector token.** `collectorTokenFile` is read from a file. Put it on a memory-backed file system such as `/run`, in a `0700` directory owned by the gateway's user, and write it at service start from your secret store. Do not leave it as a plaintext file under `/etc`.

 

## Run it

 

```sh
hardened-gateway -config /etc/hardened-session/gateway.json
```

 

Run one gateway per session. The socket is a bearer capability: anyone who can open it can send model requests as that session, so never share one between sessions or expose it through a network listener.

 

## What the audit records

 

Before each provider call, the gateway writes a start record to the spool and syncs it; the terminal record replaces it when the call ends. Records hold IDs, timestamps, the tenant, session, agent, profile and model, the outcome, status and byte counts. They never hold prompts, source code, response bodies, headers or credentials. Denied requests are recorded too. Records are delivered to the collector at least once; deduplicate on `event.id`.

 

Collector templates for Loki and OpenSearch are in `testifysec/gateway/`. They are starting points, not tested deployments.

 

## Check it

 

`python3 uat.py --go /absolute/path/to/go --report /tmp/gateway-uat.json` compiles the gateway and runs it against synthetic provider and collector services on loopback: streaming, credential isolation, denied-request auditing, offline bounds, restart recovery. The report is an unsigned local observation.

Reference generated from the product documentation. Match commands and support details to your installed release.
