ChatPanel Privacy Gateway
A localhost server that puts ChatPanel's PII redaction / pseudonymization in the
middle of two CLI agents — so you can use the privacy features outside the
ChatPanel extension. Point opencode / pi at the gateway,
and it drives codex / Claude Code behind your existing subscription login
(via the bridge), redacting on
the way out and restoring on the way back. The model only ever sees opaque
placeholders like [[PERSON_1]] / [[EMAIL_2]] — the real values never leave
your machine.
opencode / pi (configured with a custom provider → the gateway)
│ baseURL → http://127.0.0.1:4320/v1
▼
┌──────────────────────────────────────────────┐
│ ChatPanel Privacy Gateway │
│ 1. detect + redact → [[PERSON_1]] … │
│ 2. drive the agent behind your login │
│ 3. restore placeholders in the reply │
└──────────────────────────────────────────────┘
│ POST /chat (bridge: subscription-authed CLI)
▼
chatpanel-bridge ──spawns──▶ codex / claude (your ChatGPT / enterprise / Claude login)
Two backends (config backend):
bridge(default) — drive the bridge's subscription-authed CLI agents (codex/claude/opencode/pi). No API keys, uses your login. This is the "privacy bridge between two agents" path above.api— forward redacted traffic to a native OpenAI/Anthropic-compatible endpoint (local models, BYO keys). The client's own auth header passes through; the gateway stores no keys.
The redaction engine is the same code the ChatPanel extension runs — the
@chatpanel/pii package is the
single source of truth, so a privacy feature added once is shared everywhere.
Quick start (bridge backend)
You need the ChatPanel bridge running and logged into codex/claude (the same bridge the extension uses).
# Standalone binary — no Node.js required:
curl -fsSL https://dl.chatpanel.net/gateway/install.sh | bash # macOS / Linux
# Windows (PowerShell): irm https://dl.chatpanel.net/gateway/install.ps1 | iex
# Or via npm (needs Node):
npm install -g @chatpanel/gateway
chatpanel-gateway
# → ChatPanel Privacy Gateway on http://127.0.0.1:4320
# backend : bridge (agent: codex, via http://127.0.0.1:4319)
Binary vs. npm — same features, very different local-AI speed. Both run identical redaction/routing. But the standalone binary runs the local models (speech-to-text, diarization, NER) on the WASM runtime — fp32-only, single-threaded, slow. The npm install uses the native runtime with quantized (q8) models — in our tests ~10× faster speech-to-text (real-time even on larger, more accurate models). If you'll use voice/meeting features, install via npm. Don't run both — they can shadow each other on
PATH; checkGET /health→stt.runtime(nativevswasm).
Then point your front-end agent at it. opencode (opencode.json):
{
"provider": {
"chatpanel": {
"npm": "@ai-sdk/openai-compatible",
"options": { "baseURL": "http://127.0.0.1:4320/v1" },
"models": { "codex": {}, "claude": {} } // selects the agent behind the gateway
}
}
}
Now opencode talks to codex through the gateway — every prompt is redacted
before codex sees it, and the reply is restored before opencode renders it. The
request's model (codex/claude/opencode/pi) picks which agent the bridge
drives; otherwise the configured default (codex) is used.
Quick start (api backend — no bridge)
If all you want is the gateway as a redacting proxy in front of an API model,
you do not need the bridge. Set backend: "api" and (optionally) point the
gateway's upstream at your provider — in ~/.chatpanel/gateway.config.json:
{
"backend": "api",
"upstreams": {
"openai": { "baseUrl": "https://api.openai.com" },
"anthropic": { "baseUrl": "https://api.anthropic.com" }
}
}
Then point your client at the gateway and send your own API key — the gateway redacts, forwards to the provider with your key (it stores none), and restores the reply:
# In your CLIENT's environment (NOT the gateway's — see the footgun below):
export OPENAI_BASE_URL=http://127.0.0.1:4320/v1 # OpenAI-compatible: codex / aider / cursor / SDKs
export ANTHROPIC_BASE_URL=http://127.0.0.1:4320 # Claude Code / Anthropic SDK
To target a non-OpenAI provider (a local model, OpenRouter, Azure, …) change the
gateway's upstreams.*.baseUrl in the config above — that's where the gateway
forwards to. Flow: client → gateway (redact) → provider (your key) → gateway (restore) → client.
Footgun:
OPENAI_BASE_URLmeans two different things — for your client it's "where the gateway is", for the gateway it's "where my upstream is". Don't setOPENAI_BASE_URL=…:4320in the gateway's own environment, or it forwards to itself (the loop guard returns 508). Set the gateway's upstream in the config file; use the env var only in the client's shell.
Name/org redaction is built in (in-process NER, no Python)
Deterministic redaction (emails, phones, cards, SSNs, API keys, IPs) needs no
setup. To also blind names, organizations and locations, the gateway runs an
in-process entity detector — an ONNX transformer model via transformers.js —
with ner.autostart on (the default). There's no Python, no second port, no
separate process: the same model runs identically on macOS / Windows / Linux.
The model loads from ~/.chatpanel/models and is downloaded once on first run if
absent (set ner.allowDownload: false to require it be pre-placed). It's
fail-open, so if the model can't load the gateway just runs deterministic-only.
Once the detector is ready, redaction switches to the full tier automatically.
The default model (Xenova/bert-base-NER) matches or beats spaCy's small model on
people/orgs/locations. Larger or alternative models can be installed from the
ChatPanel extension's Gateway settings.
Prefer a local LLM or your own external NER service? Set redaction.detection
yourself and the gateway won't load the bundled one (yours takes precedence).
Configuration
Precedence: defaults < gateway.config.json (or $CHATPANEL_GATEWAY_CONFIG) <
env vars. See gateway.config.example.json.
| Key | Env | Default | Meaning |
|---|---|---|---|
backend |
— | bridge |
bridge (drive CLI agents via login) or api (forward to a provider) |
bridge.url |
— | http://127.0.0.1:4319 |
the ChatPanel bridge |
bridge.agent |
— | codex |
default agent the bridge drives |
bridge.token |
— | (auto) | bridge bearer token; empty = read ~/.chatpanel/bridge-token |
host / port |
CHATPANEL_GATEWAY_HOST / _PORT |
127.0.0.1 / 4320 |
bind address |
upstreams.openai.baseUrl |
OPENAI_BASE_URL |
https://api.openai.com |
api backend only |
upstreams.anthropic.baseUrl |
ANTHROPIC_BASE_URL |
https://api.anthropic.com |
api backend only |
redaction.tier |
CHATPANEL_REDACTION_TIER |
basic |
basic (regex) or full (+ NER + dictionary) |
redaction.detection |
— | (off → bundled engine) | external detector; set to override the bundled in-process one |
redaction.dictionary |
— | [] |
custom { value|pattern, type, alias? } entries |
ner.autostart |
— | true |
load the bundled in-process NER on startup |
ner.model |
— | Xenova/bert-base-NER |
model id under ~/.chatpanel/models |
ner.allowDownload |
— | true |
download the model on first run if absent |
Endpoints
| Route | Behavior |
|---|---|
GET /health |
{ ok, version, backend, tier } |
GET /v1/models |
the agent(s) this gateway exposes |
POST /v1/chat/completions |
OpenAI protocol — redact → backend → restore |
POST /v1/responses |
OpenAI Responses protocol (Codex) |
POST /v1/messages |
Anthropic protocol (Claude Code) |
Streaming (SSE) is supported on all three: placeholders are restored on the fly,
holding back a tail so a token split across chunks ([[PER … SON_1]]) still
restores cleanly.
How it fits with ChatPanel
The extension redacts inside
the browser; the bridge lets the
browser drive local CLI agents. This gateway reuses the bridge to put the same
redaction engine (@chatpanel/pii)
in front of any agent — so non-browser tools get the privacy too, and the
agent's own multi-turn loop is blinded, not just the first prompt.
Caveats
- Reversibility is best-effort: if the model paraphrases a placeholder instead of echoing it, that one reference shows the token. The privacy guarantee (the real value never left the device) always holds.
- A dictionary alias is a permanent pseudonym — the agent sees the alias, not the original, by design.
- Code edits: redacting values that appear inside source can affect round-trip
edits. The default tier touches only structured secrets and (in
full) detected entities — keep your dictionary prose-focused.
License
Source-available under the same license as the ChatPanel extension and bridge — see LICENSE.