Repository navigation
feat(plugin): add policy-driven PII detection for AI traffic - #8105
Conversation
…es and response decorator (apache#8048)
…mantics (apache#8048) - add AutoConfiguration.imports so the starter is assembled in bootstrap - add permission rows and the plugin: perms prefix across the four schemas - request scan hops off the netty event loop and catches detector deadlines - gzip, sse and unscannable responses honour failClosed; error envelopes pass through - responses enforce maxBodySize before buffering - rule compilation falls back on unusable detectors, scope and timeout values - one detector deadline per direction instead of per slot - cleared handles drop the compiled rule; luhn rejects repeated digit cards - document mutual exclusion with aiResponseTransformer
…egation (apache#8048) - scan successful openai-responses completions: "error": null is not an error envelope, only a non null error object bypasses the scan - merge overlapping redaction ranges on the original coordinates before replacing, so a contained detection never leaves a partially redacted island behind - bound the response aggregation: a fail closed rule stops reading at the bound, a fail open rule reads to the end and writes the complete body back unscanned instead of truncating it - pass unsupported payloads through as the original bytes, a gzip body never survives a utf-8 round trip - precheck streaming requests of a strict response scope before the model is reached, even when the request text itself is not scanned - measure the request bound in utf-8 bytes instead of characters - default maxBodySize to 1 MiB (dto and the four schema dialects) - restore the upstream auto configuration verification script - add the plugin README and regression tests for every fix above
… size limits (apache#8048) - a fail open rule whose response bound is exceeded now switches to unscanned streaming instead of aggregating the whole body: the buffered prefix is written, the overflowing chunk and every later chunk follow as they arrive, memory stays at the prefix size and the client receives the prefix while the upstream is still open; the upstream is subscribed once, nothing is lost or duplicated - a fail closed rule still cancels the reading at the bound before any byte was written and answers with the rejection body - the scanned completion path sets the content length of the rewritten body - the declared size bound now applies to the response-only precheck path too - a request that exceeds the codec in memory limit (independent of maxBodySize) is rejected with a clear reason instead of surfacing a codec exception - regression tests: prefix readability before the upstream completes, declared over-limit precheck, codec-limit rejection; README updated
…rs at the switch (apache#8048) - the response-only precheck measures the actual utf-8 length of an undeclared chunked body, so the maxBodySize bound no longer depends on how the request is transported - the fail-open switch point copies the overflowing chunk out of its upstream buffer and releases the buffer immediately: what the switch emits is array backed, a cancellation between the replayed prefix and the chunk cannot leak pooled memory; a doOnDiscard hook covers a chunk that was taken from the upstream but never emitted - regression tests: a real netty pooled buffer proves both chunk buffers reach refCnt 0 when the downstream cancels after the prefix, an undeclared 1 KiB body over a 128 byte rule bound is rejected, and the streaming test now replays a non empty prefix
Validate extraction shapes while preserving textless tool calls, normalize negative body limits, bound custom regex configuration before compilation, reject configured rules without compiled policies, and document fail-open response-only behavior. Add regression tests for protocol and configuration boundaries.
Aias00
left a comment
There was a problem hiding this comment.
Clean, self-contained new plugin module shenyu-plugin-ai-pii. The handle (PiiDetectionHandle) is admin-form friendly (delimited detectors/actions/customPatterns, scope request/response/both, maxBodySize with 0=unlimited default, failClosed default false, and a detectorTimeoutMs deadline). Detection is structured into PiiDetector/RegexPiiDetector/BuiltinPiiDetectors; profiles (OpenAiChatProfile, OpenAiResponsesProfile, PiiProtocolProfiles) describe where PII lives in provider payloads; PiiResponseDecorator applies redaction/block; and PiiScanner is deadline-bounded (DeadlineCharSequence, PiiChecksum). Audit is pluggable (PiiAuditSink/LoggingPiiAuditSink). The PluginEnum.PII_DETECTION(196,...) registration, bootstrap/parent pom wiring, and a starter config are all present.
Minor follow-ups (non-blocking):
- Consider an admin metadata/handle JSON example and a short doc so operators know the expected
detectors/actionsvocabulary andscopevalues. failClosed=falseis a safe default for a first release; worth a note in docs that response scanning + fail-closed is the stricter posture.
Approving.
Fixes #8048
Problem
sensitiveWordonly rejects dictionary hits. It does not provide typed PIIdetection, field-aware redaction, or AI protocol awareness.
This change adds policy-driven PII detection for supported AI JSON payloads
while preserving the existing reject-only behavior of
sensitiveWord.What this changes
A standalone
piiDetectionplugin is added at order196, beforeaiProxyat order
199. The plugin is disabled by default.The first release supports:
Supported actions are
BLOCK,REDACTandAUDIT.Redaction is limited to extracted text slots. Tool definitions, function
arguments and unrelated JSON members are not scanned or rewritten. The
rewritten result preserves JSON tree semantics; JSON escaping, formatting,
key ordering and byte-level representation may be normalized by Gson
serialization.
Detection
Built-in detectors:
EMAIL;PHONE_CN;NATIONAL_ID_CN;CREDIT_CARD.Custom regular expressions are supported through the rule handle. The
configuration is bounded by entry count, per-regex length and total length,
and built-in detector names cannot be overridden.
Detection has a per-direction deadline. Audit records contain only detector
type, match count, direction, action and rule id. Detected values are not
written to logs or audit records.
Overlapping matches use the strictest action:
BLOCK > REDACT > AUDIT.Protocol profiles
The plugin supports:
openai-chat:messages[].contentandchoices[].message.content;openai-responses:inputandoutput[].content[].text.Extraction containers and text member types are validated before scanning.
Malformed shapes follow the failure policy. Valid textless messages, such as
tool calls or image-only content, remain supported.
Successful Responses payloads containing
"error": nullare scanned normally.A non-null upstream error envelope is passed through unchanged.
Bounds and failure policy
maxBodySizeapplies to request and response bodies and is measured in UTF-8bytes. Negative values fall back to the default;
0explicitly meansunlimited.
When
failClosed=true, unsupported content types, malformed payloads,unsupported streaming modes and oversized bodies are rejected before protected
content is written.
When
failClosed=false, unsupported modes are passed through without scanningand a warning is logged. Fail-open means “not detected”; it does not mean that
the content was checked and found clean.
Response inspection is limited to JSON responses in this release. SSE and
compressed responses are not scanned. A strict response scope rejects
stream: truerequests before the upstream is called. When request scanning isalso enabled, fail-open request inspection marks the response to be skipped.
With
scope=responsealone, fail-open does not read the request body to inspectthe stream flag; SSE is skipped by response content type and supported JSON
responses remain eligible for response inspection.
Request aggregation is also bounded by
spring.codec.max-in-memory-size.Configuration
detectorscustomPatternsTYPE=REGEXentriesactionsTYPE=ACTIONoverridesdefaultActionREDACTscoperequestrequest,responseor bothmaxBodySize10485760means unlimitedfailClosedfalsedetectorTimeoutMs500Invalid detector types, actions, scopes, timeout values and negative body
limits fall back to safe defaults with a warning. A custom-pattern size or
count violation rejects rule compilation rather than installing a partial
policy. A configured rule without a compiled policy rejects traffic instead of
silently bypassing inspection. Clearing the handle drops the compiled policy.
Assembly
The change includes:
PiiDetectionHandle;The generic console form can edit the handle because its fields remain
delimiter-separated.
Tests
The tests cover built-in detector boundaries, checksum validation, protocol
slot extraction, escaped text, JSON tree equivalence, false-positive
boundaries, action precedence, overlap merging, request and response bounds,
fail-open prefix replay, pooled buffer release, streaming prechecks,
configuration fallback, malformed protocol handling and starter registration.
Local validation passed:
The local run used JDK 25, Maven 3.9.4,
-Dnet.bytebuddy.experimental=true,-Djacoco.skip=trueand-DfailIfNoTests=false. The Maven Wrapper could notdownload its distribution in this environment because the server returned
HTTP 403; the full JDK 17
./mvnw clean installrun remains for CI.Known limitations
aiResponseTransformermust not be enabled on the same traffic because itsresponse decorator can replace the redacted body.
Checklist
./mvnw clean install -Dmaven.javadoc.skip=truepassed locally.