Skip to content

Secret Guard

Agent tools send your code, configuration, and terminal output into the LLM's context — and the passwords, private keys, tokens, and cookies in them end up on the LLM vendor's servers. Secret Guard stands guard on that channel locally: the LLM only ever sees realistic Mocks, while your tools keep getting the real values.

The local Agent Harness (claude-code / opencode / hermes-agent and friends) does your work by stuffing material wholesale into the prompt — code, configuration, terminal output, along with the passwords, private keys, tokens, and cookies in them, all delivered to the LLM vendor’s servers.

Once that data leaves your machine, it is out of your control: it may end up in LLM vendor logs, be used for model training, or leak in a breach. And “never put Secrets in the context” is simply not viable in Agent workflows — credentials are exactly what an Agent tool needs to act on real systems on your behalf. Secret Guard resolves this tension: your Agent tools keep working with your Secrets, while the LLM never touches a real value.

Secret Guard is an ultra-lightweight local LLM Gateway. Point your Agent tool’s API BASE URL at it — the only integration cost is one line — and traffic flows through it to the LLM vendor:

  • Outbound Redact: Secrets in requests bound for the LLM are automatically replaced with equal-length realistic Mocks.
  • Return Restore: when LLM responses come back (tool-call arguments included), Mocks are restored to the real values.

Seeing Is Believing: A Real Request Round-Trip

Section titled “Seeing Is Believing: A Real Request Round-Trip”

Everything below comes from a real run: the gateway started with a minimal config (default strategy), protecting a fictional GitHub token — the only fiction here. Every other byte is the gateway’s real behavior.

Outbound: what the Agent Harness sends ≠ what the LLM vendor actually receives

Section titled “Outbound: what the Agent Harness sends ≠ what the LLM vendor actually receives”

Debugging a 401 on staging, you pasted your terminal output to your Agent tools — and the GitHub token from your environment ended up in the context. Without Secret Guard the two panels below would be identical (on a direct connection every byte reaches the LLM vendor as-is); with it, the only difference is exactly the part you wanted to protect:

LocalWhat the Agent Harness sendsreal secret
          {
"model": "gpt-5.6",
"messages": [
  {
    "role": "user",
    "content": "staging deploy keeps failing with 401 — is my token expired?\n\n$ ./deploy.sh\nERROR: unauthorized (401)\n\n$ env | grep -i token\nGITHUB_TOKEN=ghp_0123456789abcdefghijklmnopqrstuvwxyzreal secret"
  }
]
}
        
ServerWhat the LLM vendor actually receivesfake secret
          {
"model": "gpt-5.6",
"messages": [
  {
    "role": "user",
    "content": "staging deploy keeps failing with 401 — is my token expired?\n\n$ ./deploy.sh\nERROR: unauthorized (401)\n\n$ env | grep -i token\nGITHUB_TOKEN=2_9rr_61dr1lmv2wf15aotyyt262l3t9_2pt4uihfake secret"
  }
]
}
        

The 40-character real value is replaced by an equal-length, same-charset realistic Mock — the LLM keeps debugging with it, none the wiser.

The gateway log leaves evidence for every forward (redactions=1 is the replacement count):

forward record_id=c36e4984-ffa5-45d7-96f8-10a157de1965 method=POST path=/o/openai-main/v1/chat/completions status=200 elapsed_ms=0 redactions=1 provider=openai-main streamed=false complete=true error=None

On the way back: the LLM’s tool call arrives with real values

Section titled “On the way back: the LLM’s tool call arrives with real values”

The LLM decides to verify the token itself — it grabs “the one it knows” from the context into a command and sends it back to your Agent tools. What the LLM vendor’s servers emit is the Mock; the command landing on your machine carries the real value, and authentication passes as usual:

ServerThe tool call the LLM returnsfake secret
          {
"name": "bash",
"arguments": {
  "command": "curl -s -o /dev/null -w '%{http_code}' -H \"Authorization: Bearer 2_9rr_61dr1lmv2wf15aotyyt262l3t9_2pt4uihfake secret\" https://api.github.com/user"
}
}
        
LocalWhat the Agent Harness receivesreal secret
          {
"name": "bash",
"arguments": {
  "command": "curl -s -o /dev/null -w '%{http_code}' -H \"Authorization: Bearer ghp_0123456789abcdefghijklmnopqrstuvwxyzreal secret\" https://api.github.com/user"
}
}
        

Tool calls travel as stringified JSON on the wire; the name and arguments are extracted and formatted here for readability, with content unchanged. In plain-text replies the LLM likewise quotes the token it “sees” — by the time it reaches your machine, it has been restored too.

Every forward is recorded on the WebUI’s Records page — here is the request above as Records shows it (Secret positions highlighted as Mocks):

Records page: session timeline and request details, Secret positions highlighted as Mocks (LLM’s view)

Figure: WebUI Records page — session timeline and request details (the LLM’s view).

Handing production traffic to a middleman process requires proof that it won’t get in the way. Secret Guard guarantees the following properties by design:

Transparent relay

Apart from the real↔mock substitution, forwarding keeps requests and responses completely unchanged — behaviorally identical to a direct connection.

Strictly reversible

Each Mock maps to exactly one real value; Restore is the exact inverse of Redact — tool calls carrying Secrets are never broken by the swap.

Deterministic Mocks

A given Secret keeps the same Mock across turns, preserving the LLM vendor’s prefix-cache hits — in normal sessions, hit rates and token costs are unaffected.

Property-tested

These properties are not mere claims — they are pinned down by property-based tests and end-to-end integration tests in the upstream repository, run in full on every release.

Unique Mock in Context

A Mock never matches any other content in the request, so responses are never wrongly substituted.

Multi-Provider Routing

Supports multiple Providers (OpenAI / Anthropic / Gemini / Ollama / Responses) with per-request model-pattern routing to different upstreams.