Skip to content

jrmdev/privllm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

privllm

privllm is a self-hosted, OpenAI-compatible privacy gateway for AI agents. It transparently redacts sensitive data before it reaches upstream LLM providers, then restores the original values in the response so the client never sees aliases, and the provider never sees secrets.

How It Works (The 2-Way Architecture)

privllm is not a one-way scrubber. It performs a bidirectional rewrite:

Request flow

  • Outbound: Every string in the request is scanned. Detected values are replaced with stable, realistic aliases and stored in an in-memory mapping table.
  • Inbound: Every string in the response is scanned. Known aliases are replaced with their original values before the client sees them.
  • Stable aliases: The same original value always gets the same alias within a namespace/TTL window, multi-turn conversations stay consistent.
  • Namespace isolation: Each client API key gets its own isolated mapping (SHA-256 hash of the bearer token). No cross-tenant leakage.
  • TTL-bounded: Mappings expire after a configurable TTL (default 12 hours).
  • Schema-aware: For chat completions, content in all messages (user/system/developer/tool/assistant) is redacted.

Detected & Redacted Categories

Category What It Matches Example Alias
URLs https?://, ftp://, s3://, postgresql://, redis://, ssh://, git://, ws://, wss://, gs://, file://, mysql://, mongodb:// http://localhost-a1b2c3.example/x/x
Emails Standard email patterns user-a1b2c3@example.local
IP Addresses IPv4 and IPv6 (validated; rewritten to looback-range addresses) 127.42.1.1, ::1:a1b2
Phone Numbers Common formatted phone numbers with optional country code, separators, parentheses, extensions, and spaced 4-3-3 mobile formats phone-a1b2c3
Credit Card Numbers 13-19 digit card numbers that pass Luhn, plus strict grouped 4-4-4-4 card-like sequences with spaces or hyphens card-a1b2c3
Social Security Numbers Hyphen- or space-delimited U.S. SSN patterns with invalid ranges rejected ssn-a1b2c3
Domains Domain names preceded by domain:, host=, hostname:, site: localhost-a1b2c3.example
Paths Unix absolute paths, Windows paths (C:\...), UNC paths (\\server\share) /x42/x42/file, C:\x42\x42
Names Person names via curated wordlists (35K first names, 42K surnames, 463K English words for false-positive suppression) person-a1b2c3, surname-a1b2c3
Addresses Multilingual street addresses (Latin, CJK, Cyrillic, Arabic, Devanagari scripts; 300+ street keywords across EN/DE/FR/ES/PT/IT/NL/SL/TR/ID); PO Box, postal codes, multi-line block detection address-a1b2c3
Headers Header-style text: Authorization:, Cookie:, X-Api-Key:, Api-Key:, X-Auth-Token: (parses Basic/Bearer credentials and cookie pairs) auth-a1b2c3, cookie-a1b2c3
Secrets OpenAI/Anthropic API keys, AWS AKIA/ASIA keys, GitHub tokens (all gh*_ variants), JWT tokens (starts with eyJ), NTLM hashes, inline Bearer/Basic auth, password/secret assignments sk-a1b2c3, ghp-a1b2c3, jwt-a1b2c3
Blacklist Custom words from blacklist.txt (one per line, # comments) blacklist-a1b2c3

Client Configuration

privllm proxies to a single upstream provider defined in the config file. Clients point directly at privllm's address and pass their own real API key — it is forwarded unchanged.

export OPENAI_BASE_URL="http://127.0.0.1:8080/v1"
export OPENAI_API_KEY="sk-your-real-api-key"

This works with any OpenAI-compatible SDK, CLI, or agent framework.

Example: opencode

Configure opencode (~/.config/opencode/opencode.jsonc) to route through privllm:

{
  "$schema": "https://opencode.ai/config.json",
  "model": "privllm/deepseek-v4-flash",
  "provider": {
    "privllm": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "PrivLLM",
      "options": {
        "baseURL": "http://127.0.0.1:8080/v1",
        "apiKey": "{env:OPENAI_API_KEY}"              // your API key for the provider defined in privllm.toml
      },
      "models": {
        "deepseek-v4-flash": {
          "name": "DeepSeek V4 Flash"
        }
      }
    }
  }
}

opencode sends requests → privllm redacts → upstream responds → privllm restores → opencode sees originals. Privllm logs the alias mappings locally — the upstream provider never sees the real values.

Example: Goose CLI

GOOSE_PROVIDER=openai \
GOOSE_OPENAI_BASE_URL=http://127.0.0.1:8080/v1 \
GOOSE_OPENAI_API_KEY=$OPENAI_API_KEY \
goose run

Example: curl

curl http://127.0.0.1:8080/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4-flash",
    "messages": [{"role": "user", "content": "My email is john.doe@acmecorp.com"}]
  }'

The response will contain john.doe@acmecorp.com restored, the provider saw only user-a1b2c3@example.local.

Run

privllm --bind 127.0.0.1:8080 --config privllm.toml

CLI Flags

Flag Description
--bind <ADDR> Bind address (default 127.0.0.1:8080)
--config <PATH> TOML config file
--print-redacted Print redact/restore pairs to stderr (originals visible locally — do not use in shared logs)
--print-rewritten-prompts Print the sanitized request body with aliases highlighted in yellow

Configuration

Default config (OpenAI upstream, 12h TTL, all categories enabled, blacklist.txt):

blacklist_path = "blacklist.txt"

[upstream]
base_url = "https://api.deepseek.com/v1"

[mapping]
ttl_hours = 12

[redaction]
urls      = true
domains   = true
emails    = true
paths     = true
ips       = true
phone_numbers = true
credit_card_numbers = true
social_security_numbers = true
names     = true
addresses = true
headers   = true
secrets   = true
blacklist = true

Disable any category by setting it to false. Use DeepSeek, Anthropic, or any OpenAI-compatible provider by changing upstream.base_url.

Supported Endpoints

  • GET /v1/models
  • GET /v1/models/{model}
  • POST /v1/chat/completions (streaming + non-streaming)
  • POST /v1/responses (streaming + non-streaming)
  • POST /v1/embeddings (non-streaming)

About

A fast AI privacy gateway with 2-way rewrite/restore of PII and secrets, written in Rust.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages