# gaze-proxy
[](https://crates.io/crates/gaze-proxy)
[](https://docs.rs/gaze-proxy)
[](https://github.com/CertaMesh/gaze#license)
`gaze-proxy` is the feature-gated HTTP proxy runtime for LLM SDK base-URL swaps.
It preserves each provider's native wire shape and uses adapters only to locate
PII-bearing fields before calling the supplied `gaze::Pipeline`.
## Cargo
```toml
[dependencies]
gaze-proxy = "0.17.0"
```
## Quickstart
```bash
cargo install gaze-cli --version 0.17.0
gaze proxy start
```
Then point SDKs at the daemon:
```bash
export OPENAI_BASE_URL=http://127.0.0.1:8787/v1
export ANTHROPIC_BASE_URL=http://127.0.0.1:8787
export GEMINI_BASE_URL=http://127.0.0.1:8787
```
The default config is written to `~/.config/gaze/proxy.toml` and includes:
```toml
bind = "127.0.0.1:8787"
session_ttl = "30m"
rulepack = "core"
[adapters.openai]
upstream = "https://api.openai.com/"
[adapters.anthropic]
upstream = "https://api.anthropic.com/"
[adapters.gemini]
upstream = "https://generativelanguage.googleapis.com/"
```
## Providers
- OpenAI: `POST /v1/chat/completions`, `/v1/completions`, `/v1/responses`
- Anthropic: `POST /v1/messages`
- Gemini: `POST /v1beta/models/*:{generateContent,streamGenerateContent,countTokens}`
Adapters inspect text, tool calls/results, and function arguments in native JSON without transcoding.
## Locale
Recognizers that declare `locales = [...]`, `postal.de`, `postal.us`, the national phone
recognizers, only run when the active locale chain intersects that list. The proxy resolves
that chain once and uses it for both the primary surface pass and the outbound residual
re-scan, so the two can never disagree about what counts as PII.
`gaze proxy` has no `--locale` flag; the chain comes from the policy you pass:
```toml
locale = ["de-DE"]
```
```bash
gaze proxy serve --policy ./gaze.toml
```
With no policy the chain is `["global"]` and locale-gated recognizers stay inert. Library
adopters set it explicitly, passing the same chain the pipeline was assembled under:
```rust,ignore
let core = gaze_assembly::CorePipelineConfig::new()
.with_bundled_rulepack("core-extended")
.with_locale(&[gaze::LocaleTag::DeDe])
.build()?;
let config = ProxyConfig::new(bind, adapters).with_locale_chain(core.locale_chain().clone());
```
Locale chains end in `global`; adding locales widens detection.
## Daemon commands
```bash
gaze proxy serve
gaze proxy start
gaze proxy status
gaze proxy stop
gaze proxy restart
```
Pidfiles live under the platform local-data directory, never `/tmp`. Stale
pidfiles are revalidated with process liveness checks and cleaned before start.
Lifecycle operations coordinate through a persistent `<pidfile>.lock` sidecar.
Do not remove this sidecar or edit the pidfile while daemon commands are active;
cleanup removes only the owned pidfile and leaves the sidecar in place.
`gaze proxy logs --follow` is also available for local daemon inspection.
## Security notes
The strict Anthropic Messages profile rebuilds its outbound headers from a closed
allowlist: `content-type`, `x-api-key`, `anthropic-version`, and an optional,
explicitly allowlisted `anthropic-beta`. Unconfigured `Authorization`, bearer
credentials, cookies, and unknown SDK headers are accepted at ingress and
dropped. If an embedding host configures `Authorization` as local listener
authentication, it is a singleton consumed only by the trusted principal
resolver and is still never forwarded.
`AnthropicAdapter::new` is an ephemeral, single-request profile and rejects
`x-gaze-session-id`. Session continuity is an explicit builder/configuration
choice and then requires that header with a canonical lowercase UUIDv4 value.
The SDK base URL is the proxy root, while the only direct route is exactly
`POST /v1/messages`.
The listener does not become an access-control boundary merely because provider
credentials pass through it. Bind to loopback unless an explicit trusted
principal resolver protects a non-loopback listener. See the
[strict Anthropic Messages contract](../../docs/explanation/proxy/anthropic-messages-contract.md)
for the complete wire, proof, inspection, migration, and manual SDK-test
contract.
### Configured safety nets at request admission
Surfaced request text in the direct Anthropic and legacy adapter paths first runs
the safety-net Resolve step of `gaze clean --safety-net-fallback strict`: every span
a configured net flags becomes a restorable token. Plain `gaze clean` defaults to
the `redact` fallback, which also tokenizes what the nets' re-run flags and deletes
what is left one way; the proxy refuses that request instead. The result then
passes configured safety-net admission before provider I/O. Nets inspect the
complete transformed surface with a manifest built from the actual session token
ownership and restore boundaries. Token-contained
reflags, including class disagreements, remain allowed; a flagged span Resolve could
not tokenize, a malformed suspect, or a net execution error rejects the request with
`422` and a typed refusal (variant, fallback reason, suspect classes; never text). No
destructive clean fallback is used. See
[Safety nets and refusals](../../docs/explanation/proxy/proxy-runtime.md#safety-nets-and-refusals).
Compatibility: a net-flagged span, including text preserved by primary policy,
is now tokenized and forwarded instead of refused. Requests Resolve cannot protect
are still denied before reaching the provider.
Admission adds inference on surfaced text and codec validation views, bypasses
observer skip optimizations, and runs all registry backends selected across the
locale chain. Registry resolution and inference errors fail closed. This can
increase latency and inference cost. Primary Preserve/Redact actions and public
legacy clean defaults are unchanged; admission does not certify their reversibility.
No installed net, a custom net skipped for locale coverage, or a detector miss
remains a coverage limitation. A model is not required globally. Existing strict
protection still requires its full primary and locale contract. Direct failures
abandon staged mappings before commit/send. Legacy mappings are already live and
remain so on failure; there is no whole-request rollback guarantee. Response
restoration and response residual validation retain their existing contracts.