Skip to content
← All projects
2026Founder: designed by me, implemented by directing AI coding agents

Agent Egress Proxy — a fail-closed perimeter for outbound AI traffic

A small Rust service that sits between an agent and any OpenAI-compatible model API. It redacts PII — email addresses, UK National Insurance numbers, UK phone numbers, Luhn-checked card numbers, IP addresses, vendor API keys — refuses requests over a per-request token or cost ceiling, and records every decision as structured JSON. An upstream circuit breaker turns a vendor outage into fast refusals rather than a queue of hanging connections.

RustAxumPII redactionBudget enforcementCircuit breakerObservability
0 bytesof unredacted PII reach the vendor — asserted on the wire by an integration test, not assumed from a unit test

The source is private for now while its history is cleaned up for release. The demo below runs the deployed instance, and everything above is taken from the repository's README and tests.

Architecture

Architecture of Agent Egress Proxy — a fail-closed perimeter for outbound AI traffic, as a pipeline. 1. Parse: reject malformed JSON before anything reads it (400) 2. Redact: replace PII spans with stable placeholders 3. Meter: estimate tokens and cost; refuse over the ceiling (402) 4. Breaker: refuse fast if the upstream is known-unhealthy (503) 5. Forward: send only the redacted body; surface the decision in response headers
  1. 01400

    Parse

    reject malformed JSON before anything reads it

  2. 02

    Redact

    replace PII spans with stable placeholders

  3. 03402

    Meter

    estimate tokens and cost; refuse over the ceiling

  4. 04503

    Breaker

    refuse fast if the upstream is known-unhealthy

  5. 05

    Forward

    send only the redacted body; surface the decision in response headers

The problem

Three properties are easy to claim and hard to demonstrate about an agent platform: that it does not leak, that it cannot run away with spend, and that you can say what a request cost and how long it took. Prompts are assembled from whatever context an agent has to hand — support threads, scanned documents, receipts — so the realistic leak is accidental, not malicious. The only place all three properties can be enforced without trusting the caller is the network boundary.

Approach & decisions

  • The order of operations is the security property: parse → redact → meter → check the breaker → forward the redacted body. Redaction runs before metering on purpose — placeholders are shorter than what they replace, so metering the redacted body is both more accurate and guarantees no unredacted bytes are ever measured or logged.
  • Refusals are not faults. A budget refusal or a malformed request says nothing about upstream health, so the error type carries an is_upstream_fault() distinction and only genuine transport faults count toward opening the breaker. Without that, one caller sending oversized prompts could take the vendor offline for everyone — a trivially exploitable denial of service, closed by the type system and locked in by a test.
  • Card numbers are Luhn-checked before redaction. A bare 13–19 digit pattern also matches order numbers, timestamps and hashes; redacting those corrupts legitimate prompts and trains people to disable the gate.
  • Money is compared in integer cents, never floats. The estimate is computed in floating point because token pricing is fractional, then rounded up to whole cents before any comparison — ties go against the caller, never against the budget.
  • Configuration is validated once, at boot. A bad limit kills the process with a legible message rather than surfacing on the thousandth request; by the time a handler runs, the config is known good.
  • The claim is checkable rather than merely stated: a standard-library-only mock vendor echoes back exactly the body it received, so anyone can send a prompt with a card number through the proxy and read what the “vendor” got. That is what the live demo on this page does.

Results

  • Unit and integration tests. The integration suite starts a real mock upstream on an ephemeral port and asserts on the bytes it actually received — the email, card and NI number never arrive, the placeholders do, and the rest of the request survives intact.
  • Deployed on a Hetzner box via Coolify in front of the echo-only mock upstream. The demo below sends your text through that live instance.
  • Runs identically against a frontier vendor or a fully local model with no external network path at all — the perimeter does not care where the model lives.

Live demo

Try it against the live proxy

Your text goes from this page's server to a deployed instance of the proxy, which forwards it to an echo-only mock vendor. The second panel shows exactly what that vendor received. Nothing is stored; the route is rate-limited.

Live · no real model

What the vendor received

Nothing sent yet.

What this honestly is not

  • Not authentication. The caller's Authorization header is relayed untouched — this governs content, not identity, and deliberately holds nobody's credentials.
  • Not a guarantee. Redaction is pattern-based; it will not catch a name or a postal address. It raises the cost of an accidental leak; it does not prevent a determined one.
  • Not streaming. Responses are buffered, which is fine for chat completions and wrong for server-sent events — the first real gap to close.

Stack

RustAxumTokioreqwestregextracing (JSON logs)DockerCoolify