TL;DR: AI tools during pentests send client data (IPs, domains, emails) to third-party LLM providers. LiteLLM Proxy intercepts and masks that data before it leaves the machine. This post walks through setting it up with Claude Code.
The Problem - Client Data Is Leaking
During a pentest under NDA, while using AI tools to analyze findings, a question came up: what happens to client data in those prompts? Server IPs, internal domains, credentials - all flowing directly to external LLM providers. Under NDA terms, that is a problem.
The fix: a local proxy that masks sensitive patterns before they reach the provider. LiteLLM does this with built-in pattern matching.
What Is an LLM Gateway?
An LLM gateway is a proxy between AI tools and LLM providers.

LLM Gateway flow
Every request flows through the gateway. Two capabilities relevant to pentest:
Inspect and control all data flowing to and from LLM providers
Apply content rules: mask or block specific patterns before data leaves the network
What Is Content Filtering?
Content filtering inspects and modifies data as it passes through the gateway.
What this means for pentests:
Mask client infrastructure - IPs, domains, hostnames get replaced with
[IP_REDACTED]before reaching the providerBlock sensitive patterns - credentials, API keys, internal URLs never leave the machine
Why LiteLLM?
LiteLLM is an open-source LLM gateway. It does the pattern matching locally, no extra services needed.
Two reasons it fits this use case:
Built-in
litellm_content_filter- regex + keyword matching, no external dependencies, runs locally40+ integrations available - Presidio for PII, Lakera for prompt injection detection, and others when requirements grow
Works with 100+ LLM providers. One API format (OpenAI-compatible).
Setup
This tutorial assumes familiarity with Docker and basic command line.
Step 1: Install LiteLLM via Docker
Create a
docker-compose.yml:
services:
litellm:
build:
context: .
args:
target: runtime
image: docker.litellm.ai/berriai/litellm:main-stable
volumes:
- ./config.yaml:/app/config.yaml
command:
- "--config=/app/config.yaml"
ports:
- "4000:4000"
environment:
DATABASE_URL: "postgresql://llmproxy:dbpassword9090@db:5432/litellm"
STORE_MODEL_IN_DB: "True"
env_file:
- .env
depends_on:
- db
healthcheck:
test:
- CMD-SHELL
- python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:4000/health/liveliness')"
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres:16
restart: always
container_name: litellm_db
environment:
POSTGRES_DB: litellm
POSTGRES_USER: llmproxy
POSTGRES_PASSWORD: dbpassword9090
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -d litellm -U llmproxy"]
interval: 1s
timeout: 5s
retries: 10
volumes:
postgres_data:
name: litellm_postgres_dataStep 2: Configure the LLM Provider
Create
config.yaml. This example uses models from z.ai Coding plan:
model_list:
- model_name: zai-org/glm-4.5-flash
litellm_params:
model: anthropic/glm-4.5-flash
api_base: "https://api.z.ai/api/anthropic"
api_key: os.environ/ZAI_API_KEY
- model_name: zai-org/glm-4.7
litellm_params:
model: anthropic/glm-4.7
api_base: "https://api.z.ai/api/anthropic"
api_key: os.environ/ZAI_API_KEY
- model_name: zai-org/glm-5
litellm_params:
model: anthropic/glm-5
api_base: "https://api.z.ai/api/anthropic"
api_key: os.environ/ZAI_API_KEY
- model_name: zai-org/glm-5.1
litellm_params:
model: anthropic/glm-5.1
api_base: "https://api.z.ai/api/anthropic"
api_key: os.environ/ZAI_API_KEY
litellm_settings:
drop_params: true
general_settings:
master_key: sk-1234 # Replace with a long random key in production, must start with `sk-`
database_url: "postgresql://llmproxy:dbpassword9090@db:5432/litellm"
store_model_in_db: true
store_prompts_in_spend_logs: true# Replace sk-1234 with a long random key in production. For other providers, check the LiteLLM providers docs.
Step 3: Run LiteLLM
Command: Send below command to terminal
docker compose up -dExpected output:
[+] Running 3/3
✔ Network litellm_default Created
✔ Container litellm_db Started
✔ Container litellm-1 StartedStep 4: Test the Connection
Command: Send below command to terminal
curl -X POST 'http://127.0.0.1:4000/chat/completions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer sk-1234' \
-d '{
"model": "zai-org/glm-5",
"messages": [
{
"role": "user",
"content": "what is your name?"
}
]
}'Expected output (truncated):
{
"id": "chatcmpl-abc123",
"choices": [
{
"message": {
"role": "assistant",
"content": "I'm Claude, made by Anthropic."
}
}
],
"model": "zai-org/glm-5"
}Step 5: Access the Admin UI
Open http://127.0.0.1:4000/ui in a browser.

How Data Masking Works
Supported Modes
Mode | When it runs | Use case |
|---|---|---|
| Before LLM call | Mask input before it leaves the machine |
| After LLM response | Mask output before returning to client |
| Each streaming chunk | Real-time filtering of streamed responses |
For this setup: pre_call - mask sensitive data before it reaches the provider.

Actions
Action | What happens |
|---|---|
| Reject the request (HTTP 400) |
| Replace matched text with a redaction tag like |
Configure Data Masking via LiteLLM UI
Set up pattern matching for pentest data (IP addresses, domains, emails).
Open the admin UI at
http://127.0.0.1:4000/ui/Click Guardrails in the sidebar
Search for "Pattern Matching", click it
Click Create guardrail
Set mode →
pre_callSet "Always On" → yes

Creating pattern matching, pict src: official docs of litellm
Click through until Patterns and Keywords - select the patterns to mask:
IP Address
Email
Domain
etc..

Creating pattern matching, pict src: official docs of litellm
Set the action to
MASKfor each patternClick through to Create Guardrail
Verify the rule is active

Creating pattern matching, pict src: official docs of litellm
→ Full configuration details: LiteLLM Content Filter docs
Demo: LiteLLM + Claude Code
Integrate Claude Code to LiteLLM
Command: Send below command to terminal
# set the LLM provider pointing to LiteLLM
export ANTHROPIC_AUTH_TOKEN=sk-1234
export ANTHROPIC_BASE_URL=http://127.0.0.1:4000
# start claude code
claudeNow Claude Code routes all requests through LiteLLM.
Send a Prompt with PII
Prompt: Send below prompt to Claude Code
My information
Server IP: 172.12.10.1
Server Domain: mycompany.com
My Email: [email protected]
Location: IndonesiaInspecting the pre LLM Request
Use mitmproxy to inspect what actually reaches the LLM provider:

Sensitive data are masked before request is make to LLM provider
The sensitive data was masked before reaching the LLM provider. The provider never saw the real IP, domain, or email.
Result: 1 proxy setup replaces manual data scrubbing - client IPs, domains, and emails never reach the LLM provider.

Sensitive data are redacted
Future Improvements
Custom regex patterns - build your own patterns for specific client data formats (internal ticket IDs, custom PII formats, etc)
Keyword filtering - block specific client names, project codenames, or sensitive terms, etc
Custom code filters - write Python functions for complex masking logic. See Custom Code Content Filter docs
Further Reading
MCP Servers:
Skills & Agents:
Config Management:
Claude Samurai - Visual configuration manager for Claude Code and MCP
Disclaimer
This content reflects personal views, experiments, and use cases in AI and security engineering. It does not represent any employer's positions, policies, or practices.
Ready to apply AI to your Security Engineering ?
Subscribe to Secengai Newsletter for weekly actionable content on AI for security engineers.

