mirror of
https://github.com/mudler/LocalAI.git
synced 2026-10-28 09:08:54 -04:00
feat(pii): apply PII middleware to the Open Responses API (#12708)
* feat(pii): apply PII middleware to the Open Responses API
The per-model PII filter ran on /v1/chat/completions, /v1/completions,
/v1/messages and the Ollama routes, but not on POST /v1/responses. A
model whose policy blocks or masks a secret on the chat route served the
same content unfiltered when it was sent through the Responses API.
Add a piiadapter.OpenResponses adapter and wire pii.RequestMiddleware
into the POST /v1/responses and /responses chain, after the model config
is resolved and merged and before the handler converts input items into
prompt messages. The adapter covers instructions, input as a string, and
in an input item array the text of message and reasoning items (string
content or input_text/output_text parts, including typeless
{role, content} items), function_call arguments and function_call_output
output; structured tool output is scanned as the JSON the handler
forwards. Image, file, audio and video parts are left untouched.
The Responses WebSocket mode (GET /v1/responses) is not HTTP middleware
and cannot filter, so a response.create for a model with PII filtering
enabled is now refused with pii_unsupported_transport instead of being
served unfiltered. The docs list the new route and this limit.
Tests: adapter specs for every covered field, each with a clean-text
counter-check; a route spec driving the real middleware chain with an
in-process pattern detector (block on string input, content part,
instructions and tool output; mask; clean input passes unchanged); and
WebSocket specs proving the refusal and that models without a PII
policy are still served.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
* fix(pii): scan complete Responses tool output arrays
The Responses handler forwards non-string tool output as JSON. Scan
that full encoding so string arrays and extra fields cannot bypass PII
filtering. Cover array scans, redaction, clean values and route blocking.
Document that stored history is resolved after request filtering.
Assisted-by: Codex:gpt-6
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
* feat(pii): report unscanned media on the Open Responses route
Extend block_unscanned_media to /v1/responses: input_image, input_file
and input_audio parts inside message items, and the same types as bare
input items, are reported as unscanned content. Tool output stays
allowed, as it is scanned as JSON text.
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
---------
Signed-off-by: Stefan Walcz <stefan.walcz@walcz.de>
Co-authored-by: localai-org-maint-bot <306269227+localai-org-maint-bot@users.noreply.github.com>
This commit is contained in:
1 parent
e97bd0be76
commit
d768f1f973
8 files changed
+710
-21
No files matched your search
@@ -53,11 +53,27 @@ backend default.
|
||||
|
||||
Filtering runs on every text-accepting endpoint that has an adapter wired:
|
||||
`/v1/chat/completions` and `/v1/messages` (chat), `/v1/completions`,
|
||||
`/v1/embeddings`, `/v1/edits`, and the Ollama `/api/chat`, `/api/generate`
|
||||
and `/api/embed` endpoints, plus the [MITM proxy]({{< relref "mitm-proxy.md" >}})
|
||||
request body. Image, audio (TTS/STT), video, rerank, and the realtime
|
||||
WebSocket are not filtered yet (different prompt-PII semantics; realtime is
|
||||
not HTTP middleware).
|
||||
`/v1/embeddings`, `/v1/edits`, the Open Responses `POST /v1/responses`
|
||||
(`instructions`, string `input`, and in input items the message and
|
||||
reasoning text, `function_call` arguments and `function_call_output` output),
|
||||
and the Ollama `/api/chat`, `/api/generate` and `/api/embed` endpoints, plus
|
||||
the [MITM proxy]({{< relref "mitm-proxy.md" >}}) request body. Image, audio
|
||||
(TTS/STT), video, rerank, and the realtime WebSocket are not filtered yet
|
||||
(different prompt-PII semantics; realtime is not HTTP middleware).
|
||||
|
||||
The Responses WebSocket mode (`GET /v1/responses`) is not HTTP middleware
|
||||
either, so it cannot filter: a `response.create` for a model with PII
|
||||
filtering enabled is refused with the error type `pii_unsupported_transport`
|
||||
instead of being served unfiltered. Use `POST /v1/responses` for those models.
|
||||
Requests whose `model` names an agent are handed to the agent pool before
|
||||
model resolution and are not filtered by the per-model policy.
|
||||
|
||||
Stored history loaded through `previous_response_id` or `item_reference`
|
||||
is resolved after filtering and is not scanned against the current model's
|
||||
policy. That history can contain unfiltered text from another model or
|
||||
model-generated output. Tool output supplied directly in the request is
|
||||
scanned as text, or as its complete JSON encoding for non-string values,
|
||||
including arrays.
|
||||
|
||||
A request's messages are scanned **as one document** (joined in order), so
|
||||
the NER detector keeps conversational context: whether `4421` is a PIN or
|
||||
@@ -277,8 +293,9 @@ they reach the backend unchecked. With the option set, a request that carries
|
||||
any such content is rejected with HTTP 400 (`error.type=pii_blocked`, audit
|
||||
event pattern `__unscanned_media__`) before a detector runs. Use it for egress
|
||||
policies where "cannot be inspected" must mean "not sent", e.g. on
|
||||
`cloud-proxy` models. It applies to the OpenAI chat, Anthropic messages and
|
||||
Ollama chat routes, and only while PII is enabled for the model.
|
||||
`cloud-proxy` models. It applies to the OpenAI chat, Open Responses,
|
||||
Anthropic messages and Ollama chat routes, and only while PII is enabled for
|
||||
the model.
|
||||
|
||||
Multiple detectors **union** their detections; overlapping spans resolve to
|
||||
the strongest action (`block` > `mask` > `allow`). A configured detector
|
||||
|
||||
Reference in new issue
Block a user