privacy-proxy is privacy tests for logs: a Rust CLI, HTTP proxy and redaction engine that finds emails, tokens, secrets, cookies, cards, IBANs, IPs and sensitive URL parameters before logs reach Sentry, Datadog, Loki, Elastic, Honeycomb, or OpenTelemetry pipelines.
Use it in CI to fail fast when test logs leak secrets, or inline to redact JSONL streams before shipping them to your observability stack.
privacy-proxy assert --input test-logs.jsonl
privacy-proxy redact --input logs.jsonl --output clean.jsonl
privacy-proxy serve --target http://127.0.0.1:9000This MVP reduces the chance of sensitive data leaving your process, but it does not guarantee legal compliance or complete removal of all personal data.
Most teams test code, API contracts and security headers. Very few test whether their logs accidentally contain user emails, bearer tokens, cookies, API keys, reset links, payment cards, or bank identifiers.
privacy-proxy makes that check boring and automatable:
assertfails CI when supported sensitive data is detected.scanreports aggregate counts only, never samples or values.redactstreams JSONL line by line without loading the full file.serveproxies UTF-8 JSON, JSONL and text bodies with payload limits and safe metrics.- The core crate is pure Rust and reusable in other tooling.
cargo install --path crates/privacy_proxy_cliOr run from the workspace:
cargo run -p privacy-proxy -- init
cargo run -p privacy-proxy -- redact --input examples/logs.jsonl --output clean.jsonlAfter crates.io publication, the intended install command is:
cargo install privacy-proxyGenerate shell completions:
privacy-proxy completions bash > privacy-proxy.bash
privacy-proxy completions zsh > _privacy-proxy
privacy-proxy completions fish > privacy-proxy.fish
privacy-proxy completions powershell > privacy-proxy.ps1Install the generated file using your shell's normal completion directory. The
command also supports elvish.
Try the built-in demo first:
privacy-proxy demoIt prints synthetic log lines, their redacted form, and aggregate scan statistics. No config file is needed.
Create a starter config:
privacy-proxy initRedact a JSONL file:
privacy-proxy redact --input logs.jsonl --output clean.jsonlRedact from stdin to stdout:
privacy-proxy redact < logs.jsonl > clean.jsonlScan for aggregate counts only:
privacy-proxy scan --input logs.jsonlscan prints statistics like detector counts and line counts. It never prints matching values or samples.
Fail CI if logs contain sensitive values:
privacy-proxy assert --input test-logs.jsonlassert is the "privacy tests for logs" mode: it exits successfully when no supported sensitive values are found and fails when detections are present. Output is still aggregate-only.
Run the HTTP proxy:
privacy-proxy --config examples/privacy-proxy.toml serve \
--target http://127.0.0.1:9000 \
--listen 127.0.0.1:8080Then point a log sender at http://127.0.0.1:8080. Requests are forwarded to the target with:
- UTF-8 JSON objects redacted recursively
- JSONL/NDJSON bodies redacted line by line
- text bodies redacted as strings
- gzip request bodies decoded before redaction and forwarded uncompressed
- sensitive headers such as
authorization,cookie,set-cookie, API key/token/secret/password/session headers replaced before forwarding - request bodies capped by
max_body_bytesbefore and after gzip decompression - aggregate JSON metrics at
GET /metrics - Prometheus metrics at
GET /metrics/prometheus - liveness at
GET /healthz
The proxy intentionally does not print request bodies, headers, matched values or URL query strings in logs.
Example before:
{"level":"error","email":"alice@example.test","authorization":"Bearer example-token-value-123456","url":"https://app.example.test/reset?token=reset-token","message":"card 4111 1111 1111 1111 from 203.0.113.10"}Example after mask:
{"authorization":"[REDACTED:bearer_token]","email":"[REDACTED:email]","level":"error","message":"card [REDACTED:credit_card] from [REDACTED:ip]","url":"https://app.example.test/reset?token=[REDACTED:url_sensitive_params]"}The CLI reads privacy-proxy.toml by default. Use --config FILE to choose another path.
mode = "mask"
redact = [
"email",
"ip",
"jwt",
"bearer_token",
"api_key",
"cookie",
"credit_card",
"iban",
"url_sensitive_params"
]
fields_deny = [
"password",
"token",
"secret",
"apiKey",
"authorization",
"cookie",
"set-cookie"
]
fields_allow = [
"trace_id",
"span_id",
"request_id"
]
hash_env = "PRIVACY_PROXY_HASH_KEY"
sample_limit = 10000
max_line_bytes = 1048576
max_body_bytes = 10485760Modes:
mask: replaces values with[REDACTED:type].hash: replaces values with[HASHED:type:hmac_sha256]; set the environment variable named byhash_env.drop: removes sensitive JSON fields. Inline string matches become[DROPPED:type]because arbitrary text has no field boundary to remove safely.
The MVP supports:
- email addresses
- IPv4 and IPv6 addresses, validated with Rust's standard IP parsers
- JWT-like tokens
- Bearer tokens
- cookie header values and cookie fields
- API keys and secrets by JSON field name
- credit card candidates validated with Luhn
- IBAN candidates validated with mod-97
- sensitive URL query parameters:
token,key,secret,password,code, andsession
JSON objects and arrays are walked recursively. Non-JSON lines are treated as plain text. Processing is streaming and line-oriented, so the CLI does not load the whole log file into memory.
max_line_bytes limits each input line before redaction/scan work continues.
max_body_bytes limits each HTTP proxy request body before redaction work
starts. For gzip request bodies, the same max_body_bytes limit applies to
both compressed bytes received from the client and decompressed bytes before
redaction.
use privacy_proxy_core::{redact_value, scan_value, Config};
use serde_json::json;
let config = Config::default();
let input = json!({"email": "alice@example.com"});
let result = redact_value(input, &config)?;
println!("{}", result.value);
let report = scan_value(&result.value, &config)?;
println!("{}", report.total);
# Ok::<(), privacy_proxy_core::Error>(())For high-throughput paths, construct privacy_proxy_core::Engine once and reuse it across lines.
The CLI processes input line by line and does not load the full file into memory. For real pipelines, benchmark with representative logs because detector mix and log shape matter a lot.
Core benchmarks are included:
cargo bench -p privacy_proxy_core --bench redactionSee docs/performance.md for what is measured, JSONL small/medium/large benchmark cases, mask vs hash vs drop, memory measurement, end-to-end CLI timing, and optimization notes.
Redaction is best-effort. False positives and false negatives are possible, especially in unstructured text, custom token formats, nested encodings, unsupported compression formats, encrypted blobs, and application-specific identifiers.
Run scan in CI or staging to understand what the configured detectors see before enabling redaction in production pipelines. Keep configs reviewed like security-sensitive code.
- Dockerfile included for container builds.
- GitHub Action metadata included for
privacy-proxy assertin CI. - HTTP proxy mode included for JSON, JSONL/NDJSON and text log ingestion paths.
- See docs/integrations.md for Sentry, Loki, Datadog, Elastic and OpenTelemetry-oriented examples.
- See docs/releasing.md for the planned binary/crates.io release path.
Near-term improvements:
- Add corpus-based benchmarks with realistic Sentry, Datadog, Loki and OpenTelemetry JSON shapes.
- Add proxy streaming for very large bodies instead of buffering up to
max_body_bytes. - Add zstd request body support and streaming decompression for large gzip bodies.
- Add middleware helpers for common web frameworks.
- Add deeper OTLP/HTTP support for OpenTelemetry collector topologies.
- Publish crates.io packages and container images from protected release workflows.
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo bench -p privacy_proxy_core --bench redactionOptional fuzzing:
cargo install cargo-fuzz
cargo fuzz run redact_str
cargo fuzz run redact_jsonLicensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.