The data boundary
Anyray is fully self-hosted. The whole system — gateway, console, optimizer, the
trace backend, and every datastore — runs from one root docker-compose
inside your environment. Nothing leaves the box. There is no Anyray vendor, no SaaS,
no billing API, no license check, and no egress of any kind — not even a content-free
counter. Self-hosted is the boundary: it's air-gapped by construction.
Inside that boundary, two more guarantees protect your users' content:
- Prompt/response content is encrypted at rest by default — no human ever reads it through the console.
- The only metadata Anyray records is a content-free spend store — who/team, model, provider, tokens, cost, latency. Never content.
Everything lives inside the green box. No arrow crosses it — there is nowhere for your data to go. Content is encrypted at rest; the spend store is content-free.
What Anyray records (and what it doesn't)
Anyray records exactly two things, both inside your environment:
- A content-free spend store. For every request: who and which team (from the
x-anyray-metadataheader), the model, the provider, token counts, cost, latency, and status — never the prompt or response. This powers Spend-by-user/team in the console and the admin-gated summary atGET /admin/spend. - Per-request traces with content encrypted at rest. The gateway exports traces to a
self-hosted trace backend. By default these are metadata-only; where content is
stored it is encrypted with AES-256-GCM under
ANYRAY_CONTENT_KEY. Humans see ciphertext in the console — decrypting requires the key and is an offline, authorized audit step, never something a UI exposes.
No prompts or responses are ever shown to a human, and nothing is ever sent off the box.
Content modes
The org-wide content mode is set by ANYRAY_CONTENT_MODE and is changeable at runtime from
the console Privacy settings page (every change is audit-logged):
encrypted(default) — content stored, encrypted at rest (AES-256-GCM).off— store no content at all; only the content-free metadata is kept.plaintext— content stored in the clear. This is deploy-gated: it only takes effect when the operator setsANYRAY_ALLOW_PLAINTEXT=trueat deploy time. The fail-safe never degrades up to plaintext on its own.
Why it's built this way
- Compliance scope stays small. Because no data ever leaves your environment, there is no third-party data processor to add to GDPR / SOC2 scope. (Not legal advice.)
- Shorter vendor security review. "It's self-hosted, your data never leaves, and content is encrypted at rest" is a much easier review to pass.
- No trust required. There's no egress to audit and no vendor to take on faith — the guarantee is structural, not a promise.
Learning stays inside the boundary too
If you opt into adaptive optimization (roadmap), the optimizer learns from your traffic entirely on-prem — it reads only the local spend store and traces and writes only your local optimizer config. Nothing learned is ever sent anywhere, because nothing is ever sent anywhere. "Anyray gets better at your costs" stays fully compatible with "your data never leaves your environment."