Docs / Quickstart
Bridgekeeper is self-hosted: you run the proxy image, with your own database, your own LLM keys, and your own master key. At onboarding you receive a service-account key (for the image pull + signed-library reads) and the exact image reference. The rest is configuration.
1. Authenticate and pull
Log Docker into the registry with the JSON key you were issued, then pull the image reference from your onboarding email.
cat your-key.json | docker login -u _json_key --password-stdin https://us-central1-docker.pkg.dev docker pull <image-ref-from-onboarding>
2. Configure
Bridgekeeper does not manage these — you provide them via environment variables:
| Variable | Purpose |
|---|---|
| LITELLM_MASTER_KEY | Your admin key for minting per-end-user virtual keys (you generate it). |
| DATABASE_URL | Your Postgres for virtual-key storage. |
| ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY | At least one upstream provider key you want to use. |
| OLLAMA_BASE_URL | Optional — point at your Ollama host to use local open-source models. |
| GOOGLE_APPLICATION_CREDENTIALS | Path to the mounted service-account JSON (the signed-library poller's identity). |
| BRIDGEKEEPER_POLL_INTERVAL_SECONDS | How often to check for a new signed protection bundle (default 300). |
3. Run it in front of your proxy
docker run -d --name guardllm-proxy -p 4000:4000 \ -v "$PWD/your-key.json:/secrets/sa-key.json:ro" \ -e GOOGLE_APPLICATION_CREDENTIALS=/secrets/sa-key.json \ -e LITELLM_MASTER_KEY="sk-admin-..." \ -e DATABASE_URL="postgresql://litellm:PW@host:5432/litellm" \ -e ANTHROPIC_API_KEY="sk-ant-..." \ <image-ref-from-onboarding>
Where it attaches
Bridgekeeper attaches at the pre/post-call seam. Today it ships first-class for LiteLLM and OpenRouter (gateways) and any OpenAI-compatible model server you front (Ollama, vLLM, LM Studio, TGI, llama.cpp). Portkey, Kong AI Gateway, Helicone, and framework wrappers (LangChain, LlamaIndex) are rolling out.
4. Smoke-test
curl http://localhost:4000/health/liveliness # -> "I'm alive!"
curl -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
http://localhost:4000/guardllm/library-info | jq . # current signed policy version
curl http://localhost:4000/v1/chat/completions \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":20}'Policy fields
Outbound DLP behavior is governed by the signed policy and overridable via PolicyConfig:
- dlp_sensitive_lcs_min — minimum verbatim overlap (chars) with ingested sensitive content that triggers a block. The hardened proof policy uses 8; the default container ships a more conservative value.
- dlp_verbatim_lcs_min — same, for the non-sensitive verbatim path.
- reasoning_content scanning — when on, the outbound check inspects both content and reasoning_content (chain-of-thought), so reasoning models can't leak via the thinking channel.
Logging schema
The handler emits structured [GuardLLM] log lines. The outbound decision records the channel checked, the verdict, the reason, and signals:
[GuardLLM] inbound sanitized; warnings=[] [GuardLLM] inbound wrapped content: '<untrusted_content source="..." trust="untrusted"> ... </untrusted_content>' [GuardLLM] async_post_call_success_hook fired [GuardLLM] outbound[content] checked; allowed=False; reason=Verbatim overlap (13 chars) with ingested sensitive content; secrets_found=[]; overlap_pct=0.0; provenance_blocked=False [GuardLLM] library v2 loaded (sha=617a817156…); policy hot-swapped
Air-gapped update mode
The protection feed is a signed bundle: the in-process poller fetches it, verifies its SHA + ECDSA signature against the public key baked into the image, and hot-swaps the policy — rules flow in; nothing about your traffic flows out. For fully air-gapped operation, mirror the signed bundle into an internal location the container can reach and point the poller at it; verification is offline against the embedded public key, so no internet egress is required.
Failure modes
- Feed unreachable: the last successfully-loaded, signed policy stays in effect; the poller retries on its interval. Protection does not silently disappear.
- Signature invalid: the bundle is rejected and the current policy is kept — a tampered or unsigned bundle is never loaded.
- Outbound block: a blocked response returns HTTP 500 with a block reason rather than leaking; your application should treat that as “blocked,” not “model error.”
False-positive behavior
Lowering the overlap threshold catches shorter sensitive strings but raises the chance of over-triggering on benign text that happens to share a substring with your system prompt. Tune dlp_sensitive_lcs_min to your content. A measured false-positive rate against a benign corpus is in progress and will be published here when available.