When you enable this plugin on a Route, it acts in two Kong Gateway request-lifecycle phases:
-
access: Runs before the request is proxied upstream.
The plugin reads the request body, extracts the target text spans based on the wire format detected from the body (OpenAI, Anthropic, or MCP) plus any explicit JSONPath selectors, calls Skyflow De-identify (POST /v2/detect/deidentify/string), and rewrites the outbound body so the upstream receives only tokens, for example [NAME_aB3xQ].
A request-scoped token-to-value map is stashed for the response phase.
-
response: Runs after the full upstream response is buffered but before any byte reaches the client.
When reidentify.enabled = true, the plugin restores the original values using either mapping_only (the request-scoped map) or reidentify_text (a vault-authoritative call to POST /v2/detect/reidentify/string), then rewrites the response body.
The end user sees real values; the provider never does.
sequenceDiagram
autonumber
participant C as Client
participant K as Kong Gateway
skyflow-ai-data-control
participant S as Skyflow Detect
participant U as Upstream
LLM / MCP / API
C->>K: Request containing sensitive values
Note over K: access phase
K->>S: POST /v2/detect/deidentify/string
S-->>K: Tokens + token/value map
alt Skyflow unreachable and on_error.skyflow = deny
K-->>C: 502 - request blocked, nothing forwarded
else De-identified
K->>U: Forward request - tokens only
U-->>K: Response referring to tokens
Note over K: response phase
K->>S: POST /v2/detect/reidentify/string
S-->>K: Original values
K-->>C: Response with real values restored
end
Note over K: log phase - entity counts by type, never values
The plugin attaches to Kong Gateway Routes and Services and talks to the Skyflow Detect API over HTTPS using a bearer token minted from the configured credential.
It emits metrics and structured logs of detected-entity counts by type, never the values themselves.
With VAULT_TOKEN, the same input value always maps to the same token, so multi-message and multi-turn conversations stay coherent even though the provider only ever saw opaque tokens.
The Skyflow De-identify plugin can’t share a Route with ai-proxy.
ai-proxy transforms the LLM response in its header_filter, while re-identify must run in the response phase (it calls Skyflow over a cosocket, which Kong Gateway disallows in body_filter).
On one Route, the two plugins contend for the buffered body, and ai-proxy returns 500 "no response body found when transforming response" when the upstream body is gzip-encoded, which real OpenAI always is (Kong #14380).
The fix is a nested-proxy topology with two Routes and two independent buffered cycles.
flowchart TB
C["Client
Email Jane Doe at jane@acme.com"]
F["/ai/chat - front route
skyflow-ai-data-control only
access: de-identify · response: re-identify"]
I["/_ai_upstream - internal route
ai-proxy only"]
P["Provider
OpenAI · Anthropic · …
sees only tokens"]
S[("Skyflow
Detect")]
C -- "sensitive values" --> F
F -- "tokens only
loopback 127.0.0.1:8000" --> I
I --> P
P -- "tokens" --> I
I -- "tokens" --> F
F -- "values restored" --> C
F -. "deidentify · reidentify" .-> S
Each Route runs its own buffered request/response cycle, which keeps the two plugins out of each other’s way.
The internal Route is not externally reachable: an ip-restriction plugin bound to 127.0.0.1/32 refuses all other traffic, because that Route doesn’t carry authentication or de-identification.
Beyond simple JSON bodies, the plugin handles coding-agent and MCP traffic.
It reads large bodies that nginx spools to disk, re-identifies values inside tool_calls[].function.arguments (not just message content), and buffers streaming (stream: true) responses.
Re-identification runs on the full completion, which is then re-emitted as SSE, so a token split across chunks is never leaked.