Skip to content

Redact credentials from requests sent to external backends - #94

Merged
data-angel merged 1 commit into
mainfrom
egress-redaction
Oct 18, 2026
Merged

data-angel merged 1 commit into
mainfrom
egress-redaction

Conversation

@data-angel

Copy link
Copy Markdown
Contributor

Summary

Opt-in egress redaction. The gateway replaces credentials in request text with [secret] before it forwards a request to a backend outside the local network, such as DeepSeek or OpenRouter.

Config

  • security.redactEgress (default false, validated as a boolean): redact for every external backend.
  • backends.<id>.redact: true | false (validated as a boolean): overrides the automatic decision in either direction. true applies even when the global switch is off.
  • External is decided from the baseUrl host. Internal hosts are loopback, RFC 1918, link-local, 100.64.0.0/10, fc00::/7, .ts.net / .local / .internal / .localhost, and unqualified single-label names. Backends with audioProvider or videoProvider always count as external, because those adapters call OpenRouter or Replicate directly. Federated LLooM gateways (isFederatedGatewayBackend) count as internal, since the downstream gateway applies its own policy.

Coverage

  • Applied once, after protocol translation, through a new fetchModelUpstream wrapper. It replaces the duplicated headers and backend lines at all 11 JSON fetchUpstream call sites. The audio and video provider adapter calls also go through the same egressBody.
  • Chat Completions, Responses and Anthropic Messages (both bridged to chat), streaming or not, and JSON image, video, speech and audio-generation prompts. LLooM has no native Responses passthrough and no legacy /v1/completions route. prompt is still handled generically.
  • Scrubbed fields: message content (strings and text parts), tool_calls[].function.arguments, role: tool results, replayed reasoning_content/reasoning, prompt, input, instructions and system. Image, audio and file parts are untouched.
  • Not covered:
    • Embeddings, on purpose: rewriting the input changes the vectors.
    • Transcriptions: the payload is audio.
    • Multipart uploads (image edits, voice-clone speech).
    • Web search queries.

Patterns

  • Ported from Tessera's Steward redactor.
  • Added URL passwords (scheme://user:[secret]@host).
  • The sk- pattern already covers OpenRouter sk-or-v1- keys, and a test checks that.
  • Labelled values keep the label and need at least 6 characters. Unquoted values that read as code (getToken(), process.env.X, $TOKEN, words with no digits or symbols) are skipped.
  • No character class accepts a quote or a backslash, so redaction inside JSON-encoded tool arguments keeps the JSON valid.
  • PEM keys are found with a linear BEGIN/END scan. A lazy regex goes quadratic when there are many unterminated headers.

The caller's object is never mutated. Matched values are never logged; one info line records the replacement count and the backend ID.

Tests

  • node --test test/egress-redaction.test.mjs (new, 18 tests). It covers each pattern, URL credentials, prose and code false positives, JSON validity, body shapes and non-mutation, host classification, policy and overrides, federation, config validation, and gateway integration through a mocked undici dispatcher (chat buffered and streaming, Responses, Anthropic, embeddings and transcriptions passthrough, off or overridden).
  • Regression: openrouter-provider, security, model-failover, responses-codex, video-providers, protocol, protocol-stream, server-resilience, stream-metrics, and node --test for audio-generations, standby, federated-priority and audio-providers.
  • Prettier and eslint pass on the changed files.

🤖 Generated with Claude Code

Add opt-in egress redaction. With security.redactEgress (default false),
the gateway replaces credentials in request text with [secret] before
forwarding to a backend whose baseUrl host is outside the local network.
backends.<id>.redact overrides the decision in either direction.

- Patterns ported from Tessera's Steward redactor, plus URL passwords
  (mongodb+srv://, postgres://, redis://, ...); sk- already covers
  OpenRouter sk-or-v1- keys. Labelled values keep their label, need 6+
  characters, and skip unquoted values that read as code.
- Applied once, after protocol translation, in a fetchModelUpstream
  wrapper used by every JSON model route, and to the OpenRouter/Replicate
  audio and video provider adapters. Chat, Responses and Anthropic
  bridges, streaming or not, share it.
- Internal: loopback, RFC1918, link-local, 100.64/10, fc00::/7, .ts.net,
  .local, .internal and single-label hosts, and federated LLooM gateways.
- Embeddings and transcriptions are never rewritten. The caller's body is
  not mutated, and only a per-request count is logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@data-angel
data-angel merged commit bcce316 into main Oct 18, 2026
2 checks passed
@data-angel
data-angel deleted the egress-redaction branch October 18, 2026 01:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant