Saklam Bridge — Setup Guide
Drop-in LLM proxy with automatic PII masking. On-premises in your own infra, zero-knowledge towards Saklam, GDPR-compliant — sensitive data stays on-prem.
This guide walks you through installing and running the Saklam Bridge container inside your own network. You keep full data sovereignty: plaintext never leaves your infrastructure unmasked.
Architecture at a glance
App servers / Workstations / CI
Claude Code · Cursor · Your own apps
│
│ ANTHROPIC_BASE_URL=http://bridge.internal.example.com
│ ANTHROPIC_API_KEY=sk-bridge-<your-master-key>
▼
┌─ Saklam Bridge (Docker in your infra) ──────────────────────────┐
│ 1. Incoming request with sensitive plaintext │
│ 2. PII masking (300+ patterns, EU-27, 20+ languages) │
│ 3. Forward to Anthropic/OpenAI/Mistral with your API key (BYOK) │
│ 4. Unmask the response │
└──────────────────────────────────────────────────────────────────┘
│
│ Masked tokens, your API key
▼
Anthropic / OpenAI / Mistral
What Saklam sees: nothing. The Saklam server has no connection to your container. What the LLM provider sees: masked tokens, never plaintext.
Requirements
| Component | Detail |
|---|---|
| Host OS | Linux x86_64 or ARM64 (the container is multi-arch). Windows/macOS via Docker Desktop. |
| Docker | Docker Engine ≥ 24 or Docker Desktop ≥ 4.30 with the docker compose v2 plugin |
| CPU/RAM | 1 vCPU + 2 GB RAM recommended (verified to run with 1 GB); 2 vCPU + 2 GB for lower latency / multi-user |
| Storage | ~5 GB disk (download ~3 GB) — the PII model (GLiNER, ONNX) ships pre-installed in the image, no runtime download |
| Network | Outbound HTTPS to api.anthropic.com (or your chosen provider) |
| API key | Pay-as-you-go API key from the LLM provider — not a Max/Pro/Team subscription OAuth token (see Auth note below) |
Quick install (5 minutes)
# 1. Create a directory
mkdir -p /opt/saklam-bridge && cd /opt/saklam-bridge
# 2. Download docker-compose.yml + .env.example
curl -fsSL https://saklam.com/bridge/docker-compose.yml -o docker-compose.yml
curl -fsSL https://saklam.com/bridge/env.example -o .env.example
# 3. Prepare .env
cp .env.example .env
$EDITOR .env
# - SAKLAM_LICENSE_KEY=sk-lic-… (required — from your activation email/dashboard)
# - ANTHROPIC_API_KEY=sk-ant-api03-… (pay-as-you-go key)
# - BRIDGE_MASTER_KEY=$(openssl rand -hex 32) (optional, see auth modes below)
# 4. Start
docker compose pull
docker compose up -d
# 5. Smoke test
sleep 5
curl -fsS http://localhost:4000/health/readiness
Auth modes (master key)
| Setup | Configuration | When |
|---|---|---|
| Single user on a laptop (Bridge used locally only) | BRIDGE_BIND_ADDR=127.0.0.1, BRIDGE_MASTER_KEY= (empty) |
Solo dev, dogfooding. The Bridge accepts any x-api-key. Safe because only host processes can reach it. |
| Multi-user server (central Bridge for the whole team) | BRIDGE_BIND_ADDR=0.0.0.0, BRIDGE_MASTER_KEY=<openssl rand -hex 32>, with a TLS reverse proxy in front |
Production deployment. Master key + TLS are both mandatory. |
Expected response: {"status":"connected"} (HTTP 200).
Tool integration
Claude Code (CLI)
export ANTHROPIC_BASE_URL=http://localhost:4000
export ANTHROPIC_API_KEY=<your BRIDGE_MASTER_KEY from .env>
claude
In the started Claude session, test any prompt containing plaintext PII:
> "Write a payment reminder to Max Mustermann, Musterstr. 12, 80331 Munich, amount due 1,245.50 €."
The Bridge masks Max Mustermann → [PER_a1b2c3d4], Musterstr. 12, 80331 Munich → [LOC_…], sends the masked version to Anthropic, and unmasks the response before returning it.
Cursor
Cursor Settings → Models → API Keys:
- Anthropic API Key:
<BRIDGE_MASTER_KEY> - Anthropic Base URL:
http://localhost:4000(orhttp://bridge.internal.example.com)
Your own scripts (Python anthropic SDK)
from anthropic import Anthropic
client = Anthropic(
base_url="http://localhost:4000",
api_key="<BRIDGE_MASTER_KEY>",
)
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Customer: Müller, case 2025/4711, amount 18k €."}]
)
n8n (self-hosted)
Dedicated guide with chat workflow, credential setup and how this relates to n8n's Guardrails node: Saklam Bridge + n8n. Short version: OpenAI credential with Base URL = http://saklam-bridge:4000/v1 — for Claude too (n8n's Anthropic credential has no base-URL field).
LAN access for team workstations
By default the Bridge binds to 127.0.0.1:4000. For network-wide access:
- In
.env:BRIDGE_BIND_ADDR=0.0.0.0 - Put a reverse proxy (nginx/Caddy/Traefik) with TLS in front — never plain HTTP over the LAN, because sensitive plaintext travels between the workstation and the Bridge.
- Restart with
docker compose up -d.
Custom detection
The Bridge ships with 335+ built-in PII patterns. Organization-specific
sensitive data — project names, case numbers, domain terms — goes into a
./custom folder next to your docker-compose.yml (uncomment the custom
volume in the compose file, restart the container). No code, no training,
no image rebuild. Three file kinds:
| File | Purpose | Example |
|---|---|---|
*.txt |
term list, 1 line = 1 term; filename = category | projects.txt containing "Project Phoenix" → [PRO_…] |
*.yaml |
own regex patterns for fixed formats | case number WUB-\d{6} → [VOR_…] |
labels.yaml |
own AI entities, zero-shot, no training | medication: MEDICATION catches "Pantoprazole" |
# custom/labels.yaml
labels:
medication: MEDICATION
diagnosis: DIAGNOSIS
ner_threshold: 0.5 # optional; higher = stricter
Everything is additive to the built-in patterns and flows through the same mask/unmask roundtrip and audit trail. Note: with AI entities configured, every request runs the full NER analysis (~1–2 s/request on CPU) — term lists and regex patterns don't have that effect. Test zero-shot labels with real sample texts before production; for enumerable terms, a term list is always the more precise choice.
Updates
cd /opt/saklam-bridge
docker compose pull
docker compose up -d
We recommend a weekly maintenance window or one tied to Saklam release notes. Patch releases are compatible; major versions are announced on the release channel.
Auth note (important before setup)
Saklam Bridge works only with classic API keys (pay-as-you-go), not with subscription OAuth.
Does not work:
- Anthropic Max / Pro / Team OAuth subscription tokens
- ChatGPT Plus / Team web sessions
Supported providers (all BYOK = Bring Your Own Key, Saklam does not act as a reseller):
| Provider | When it makes sense | Where to get it |
|---|---|---|
| Anthropic (direct) | Frontier models (Claude Sonnet/Opus/Haiku) | console.anthropic.com → API Keys |
| OpenAI (direct) | GPT models | platform.openai.com → API Keys |
| Azure OpenAI | If you have an M365 enterprise contract — same GPT models with EU data residency + Microsoft DPA | Azure Portal → AI Services → Azure OpenAI |
| Google Gemini (Vertex AI) | Gemini family, EU region Frankfurt available | aistudio.google.com or Vertex AI |
| AWS Bedrock | If you have an AWS contract — Claude / Llama / Mistral through one API with an AWS DPA, EU Frankfurt (eu-central-1) |
AWS IAM → Access Key + Bedrock Model Access |
| Mistral (direct) | EU provider La Plateforme | console.mistral.ai |
| Self-hosted | Your own inference cluster (Ollama / vLLM / TGI) — maximum zero-knowledge | OLLAMA_API_BASE=http://ollama.internal:11434/v1 |
You pick one or more providers and add the corresponding env variables to .env. Only ANTHROPIC_API_KEY is required (the default provider on first start) — everything else is optional. Config routing happens automatically based on the model-name prefix:
claude-*→ Anthropicgpt-*/openai/*→ OpenAI (direct)azure/*→ Azure OpenAIgemini/*→ Google Geminibedrock/*→ AWS Bedrockmistral/*→ Mistralollama/*→ Self-hosted
Hybrid routing: local model + masked frontier (one gateway)
You don't have to choose between "local model" and "frontier quality" — the Bridge routes both:
- Routine tasks → local model (
ollama/…): the request never leaves your host. Summaries, classification, simple drafts. - Complex tasks → frontier, masked (
claude-…,gpt-…): PII is replaced before the request goes out, the response is unmasked locally.
Both paths go through the same masking and the same audit trail — one policy, one record, regardless of model.
Run Ollama inside the same compose stack (optional profile):
# .env: OLLAMA_API_BASE=http://ollama:11434/v1
docker compose --profile local-llm up -d
docker compose exec ollama ollama pull llama3.2
Call it like any other model, just with the ollama/ prefix (OpenAI wire):
curl http://localhost:4000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model": "ollama/llama3.2", "messages": [{"role": "user", "content": "…"}]}'
If Ollama already runs elsewhere on your network, OLLAMA_API_BASE=http://ollama.internal:11434/v1 is all you need — the profile is just the compose convenience variant. Set expectations honestly: local models on CPU servers are noticeably slower and weaker than frontier — the hybrid pattern deliberately reads "routine locally, complex masked to frontier", not "local does everything".
FAQ
Do the [XXX_yyyyyyyy] placeholders confuse the LLM or make it refuse to answer?
No. The Bridge automatically injects a system instruction into every request (wire-aware for both the Anthropic and OpenAI formats) that explains to the LLM: tokens in the format [PER_a1b2c3d4], [EMA_…], [TEL_…], etc. are masked personal data — copy them verbatim, don't change them, don't invent new ones, and treat them "as if they were the real data."
For normal tasks (summarizing, drafting text, email drafts, analysis) output quality is fully preserved — the placeholder stands semantically in place of the real person/IBAN/address. In the response, the placeholders are automatically unmasked before they reach your app/client: you see the real values, the LLM never does.
Intentional edge case: for tasks that need the exact original value of a masked field — e.g. "verify the checksum of this IBAN" or "how many digits does this phone number have?" — the LLM correctly notes that it only sees a placeholder. Such format/validation checks belong on your side (before or after the Bridge), not inside the masked AI request.
Handy side effect for your own testing: if you ask the LLM whether a masked field is "real," it replies "that's a placeholder" — direct, visible proof that plaintext never reaches the provider.
Troubleshooting
unhealthy in docker ps
docker compose logs bridge
Typical causes:
- First ~10–30s after start: the GLiNER model is loaded from the image into RAM (warmup, no download — the model is in the image) —
start_period: 60sin the healthcheck covers this. If it's stillunhealthyafter 90s: check the logs. ANTHROPIC_API_KEYinvalid → 401 from Anthropic → the Bridge reports no error at start, but every request fails. Test:curl …/health/liveness(should be 200 even without Anthropic).
401 Unauthorized on Anthropic calls
Your API key is a Max/Team OAuth token instead of a classic sk-ant-… key. See the Auth note above.
Latency
The model is pre-installed in the image and loaded into RAM on container start (no runtime download, air-gap capable). The first PII request is therefore not delayed by a download; requests land at ~<100 ms (CPU).
PII is not being masked
Check the logs: docker compose logs bridge | grep -i 'mask'. Common causes:
MASK_API_URLis set but the target is unreachable → remove the variable from.env. The default is in-process (no external mask service, no network hop).- The prompt only contains domain-specific abbreviations that aren't in the 300+ patterns → custom patterns on request.
"Disclosure": the Bridge buffers PII while processing a request
Yes, this is architecturally unavoidable (RAM-only, no disk persistence). A malicious container admin could inspect the process. That's exactly why the Bridge belongs in your infra, not at Saklam.
no valid license / requests rejected with 5xx
The Bridge needs a valid license (active subscription). Check:
SAKLAM_LICENSE_KEYset correctly in.env(sk-lic-…from your activation email/dashboard)?- Subscription active? See your dashboard.
- On startup the Bridge automatically fetches a signed license token (only the key leaves the host — never customer data). Logs:
docker compose logs bridge | grep -i licen. - Network is needed once to activate; afterwards a cached token bridges short outages (grace period).
Logs & privacy
- Default logging: structured JSON logs (
json-filedriver), rotated at 50 MB. - PII in logs: the Bridge logs request metadata (model, token counts, latency), not prompt plaintext.
- External log forwarding (Splunk, ELK, Loki): standard Docker logging mechanisms.
- Auditability: audit-trail export (CSV) + tamper-evident hashing on request — coming in Phase 2 (see roadmap).
Pricing & license
| Plan | Price | Includes |
|---|---|---|
| Solo | €99 / month or €990 / year (2 months free), plus VAT | 1 Bridge instance, unlimited volume, BYOK, auto-updates, pattern maintenance, email support, cancel monthly |
Running multiple instances or a team? Write me (stefan@saklam.com) — we'll sort it out individually.
Data processing: Saklam does not act as a data processor, because Saklam never receives personal data — the Bridge runs on-premises in your infrastructure. We provide a site-license agreement + a technical whitepaper for your data-protection documentation.
Support
| Channel | Detail |
|---|---|
| support@saklam.com |
Setup help: hands-on with Stefan over a screen share if you want it — you're live in 15–20 min.