Kong Gateway plugin: redact PII before upstream LLM calls, restore it on the response — transparently.
git clone https://github.com/wan9yu/kong-plugin-argus-redact && cd kong-plugin-argus-redact
docker compose -f docker/docker-compose.yml up -d --build
curl -s localhost:18000/v1/chat/completions -H 'Content-Type: application/json' \
-d '{"model":"gpt-4","messages":[{"role":"user","content":"我叫王五,手机13812345678"}]}' | jqsequenceDiagram
autonumber
participant C as Client
participant K as Kong + argus-redact-bridge
participant A as argus-redact sidecar
participant L as Upstream LLM
C->>K: POST /v1/chat/completions (real PII)
K->>A: POST /redact (per messages[i])
A-->>K: {redacted, key}
Note over K: stash merged key in kong.ctx.plugin
K->>L: forward request (pseudonymized body)
L-->>K: response (still pseudonymized)
Note over K: body_filter — local restore via key dict
K-->>C: response (real PII restored)
The honest gap, before you adopt:
- No streaming.
stream: trueis rejected with HTTP 400. Token-level SSE doesn't honor logical-unit boundaries; correct streaming support is on the v1 roadmap. - OpenAI Chat Completions only. The Anthropic Messages API, Google Vertex, and other vendor shapes are not understood. Only
{messages: [{role, content: <string>}]}in and{choices: [{message: {content}}]}out. - No cross-language alias restore. If the LLM rewrites
张三asZhang San, the response carriesZhang Santhrough unchanged — local restore catches verbatim placeholders only. (OpenResty forbids HTTP calls from thebody_filterphase, so we can't ask argus-redact's/restoreto do the smarter alias-aware match.) - No batched redact. N messages = N sequential
POST /redactcalls. v1 wishlist depends on a batch endpoint on the argus-redact side. - Multimodal content arrays are skipped. A
content: [{type:"text", text:...}]array is not redacted (only stringcontentfields are). The request still flows through unmodified. - Single-form output. argus-redact's three-form output (
audit_text/downstream_text/display_text) is not exposed via HTTP — only the realistic single form is used.
mode=fast, profile=pseudonym-llm (defaults):
- access phase — extract every
messages[].content, send each to argus-redact's/redact, replace with the realistic pseudonym, stash the merged{placeholder → original}key map inkong.ctx.plugin, forward the modified body upstream. - body_filter phase — buffer the upstream response until EOF, parse
choices[].message.content, restore the original PII via local string substitution against the stashed key, write back.
Failure mode: on_error: closed (default) — if argus-redact is unreachable during access, the request is rejected with HTTP 503; unredacted PII never reaches the LLM. Set on_error: open to fail open with a warning log.
luarocks install argus-redact-bridge
export KONG_PLUGINS=bundled,argus-redact-bridgeDB-less example:
# kong.yml
plugins:
- name: argus-redact-bridge
config:
argus_url: http://argus-redact:8000
argus_api_key: "{vault://env/ARGUS_API_KEY}"
lang: zh
mode: fast
profile: pseudonym-llm
on_error: closedFor production, run argus-redact serve with ARGUS_API_KEY set (drop the --insecure flag the demo uses) and reference the same key via Kong's vault syntax. The shipped docker/docker-compose.yml runs --insecure for self-contained reproducibility only.
| Field | Type | Default | Description |
|---|---|---|---|
argus_url |
string (required) | http://argus-redact:8000 |
Base URL of the argus-redact HTTP server. |
argus_api_key |
string (referenceable) | — | Bearer token. Must match ARGUS_API_KEY on the argus-redact server. Use Kong vault references in production. |
lang |
string | zh |
argus-redact language pack (zh, en, ja, ko, de, uk, in, br). |
mode |
enum | fast |
argus-redact detection mode (fast, ner, auto). Only fast is suited to the request path; ner and auto are exposed for completeness but their latency profile makes them better suited to async / sidecar deployments than to inline gateway use. |
profile |
string | pseudonym-llm |
argus-redact compliance profile. pseudonym-llm emits realistic-looking faked values that preserve LLM-reasoning quality. |
timeout_ms |
integer | 2000 |
Per-call timeout for /redact. Bounded to [100, 60000]. |
on_error |
enum | closed |
Behavior when argus-redact is unreachable during the access phase. closed: return 503. open: log a warning and pass the original request through unmodified. |
- Kong Gateway 3.x, DB-less or DB-mode. CI builds and the demo stack pin
kong:3.7.1-ubuntu. The plugin uses only stable PDK 1.x surfaces (kong.request.get_raw_body,kong.service.request.set_raw_body,kong.ctx.plugin,kong.response.exit/clear_header,kong.log.*, schematypedefs.protocols_http) so 3.x family compatibility is expected, but only 3.7.1 is in CI. - OpenResty 1.21+ (Kong 3.x runtime).
- argus-redact 0.6.4+ as the sidecar (
pip install 'argus-redact[serve]').
End-to-end latency on the demo stack, mode=fast, single 25-character Chinese PII payload, N=100 with 5-request warmup. Plugin steady-state overhead alone is ~0.5–0.8 ms at p50; the rows below are full-path numbers including the request-body parse, POST /redact to the sidecar, body inject, upstream mock-LLM call, response body buffer, and local restore.
| Host | p50 | p95 | p99 | throughput |
|---|---|---|---|---|
| Linux aarch64 (Parallels VM, native Docker) | 1.8 ms | 2.6 ms | 42.4 ms | 196 req/s |
| macOS (Apple M1 Max, Docker Desktop) | 6.6 ms | 10.8 ms | 17.0 ms | 42 req/s |
Reproduce with bash scripts/bench.sh after docker compose -f docker/docker-compose.yml up -d --build. Numbers vary with payload size, message count, network distance to the sidecar, and host hardware. Docker Desktop on macOS adds 3–5× hypervisor + bind-mount overhead vs. native Linux; production deployments on Linux nodes should expect the upper row, not the lower one.
Cold-start tax. Without warmup, the first few /redact calls after the argus-redact container starts can take 1–4 seconds (Python imports and regex compilation). Production deployments should warm the sidecar — a single dummy /redact call, or an initialization healthcheck that exercises the path — before serving traffic. v0.1 ships without an automated warmup hook. See benchmarks/vm-test-2026-05-07.md for the measured cold-start profile.
Three patterns dominate gateway-layer PII handling today:
- Block-and-reject — detect sensitive content, refuse the request. Strong privacy guarantee, no usability for legitimate text the regex flags.
- Detect-and-strip — replace PII with placeholders before the upstream call, never restore. Usable, but the client sees pseudonyms and has to map them back manually.
- Reversible redact-and-restore — replace PII with realistic pseudonyms upstream, restore the original on the response. Upstream sees no raw PII; the client sees a normal message. This plugin implements pattern 3.
See benchmarks/prvl/ for the privacy × reversibility × language methodology used to measure each pattern.
- LuaRocks —
luarocks install argus-redact-bridge(package page). - GitHub Releases — tagged release artifacts (signed via
tag → CI → luarocksworkflow). - Kong Plugin Hub — Per the maintainer response on Kong/developer.konghq.com#5157 (opened 2026-05-07, closed 2026-05-20), Kong's Plugin Hub currently accepts third-party plugins only via Kong's partner certification process, after which a Kong-staff-authored PR lands the docs. The PR is held closed until certification is in place. The PR conversation has the contact path for that process.
v0.1 preview. APIs may change. Production users should pin to a tagged commit.