janiussyafiq commented on issue #13619:
URL: https://github.com/apache/apisix/issues/13619#issuecomment-4828691000
### Reproduced on `master` (PR #13606, merged at `3ca5ddf3`) with Test::Nginx
**Setup:** `ai-proxy` (openai-compatible) + `ai-lakera-guard` with
`direction=output`, `action=alert`, `fail_open=false`. A mock Lakera endpoint
returns HTTP 500 (error) when it scans the response. Same route config
exercised twice: one non-streaming request, one streaming request.
**Result:**
- **Non-streaming** alert + Lakera error → correctly **fails closed**:
client receives the deny body `Response blocked by Lakera Guard`.
- **Streaming** alert + Lakera error → **fails open (the bug)**: the client
receives the full assistant stream, even though the plugin logs the fail-closed
decision:
```
ai-lakera-guard.lua:133: moderate_response(): ai-lakera-guard: Lakera Guard
returned status 500; fail_open=false, blocking response
```
Leaked streamed body the client actually received (tokens delivered despite
the "blocking response" log):
```
data: {... "delta":{"content":"trigger "} ...}
data: {... "delta":{"content":"lakera-error here"} ...}
data: {... "finish_reason":"stop","usage":{...} ...}
data: [DONE]
```
So `moderate()` returns the deny `(code, body)` on the Lakera error (and
logs "blocking response"), but the streaming `action=alert` branch discards
that return and the content streams through. Confirms the root cause and the
fail-closed contract violation; the non-streaming path proves the same config
blocks correctly.
<details>
<summary>Repro test (<code>t/plugin/repro-p1-alert-failclosed.t</code>) + 2
fixtures</summary>
`t/fixtures/openai/chat-streaming-lakera-error.sse` — assistant text
contains `lakera-error` so the mock Lakera returns 500:
```
data:
{"id":"chatcmpl-err123","object":"chat.completion.chunk","created":1700000000,"model":"gpt-4o-2024-05-13","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data:
{"id":"chatcmpl-err123","object":"chat.completion.chunk","created":1700000000,"model":"gpt-4o-2024-05-13","choices":[{"index":0,"delta":{"content":"trigger
"},"finish_reason":null}]}
data:
{"id":"chatcmpl-err123","object":"chat.completion.chunk","created":1700000000,"model":"gpt-4o-2024-05-13","choices":[{"index":0,"delta":{"content":"lakera-error
here"},"finish_reason":null}]}
data:
{"id":"chatcmpl-err123","object":"chat.completion.chunk","created":1700000000,"model":"gpt-4o-2024-05-13","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":8,"total_tokens":18}}
data: [DONE]
```
`t/fixtures/openai/chat-lakera-error.json` (non-streaming counterpart): a
normal completion whose `message.content` is `"trigger lakera-error here"`.
```perl
use t::APISIX 'no_plan';
log_level("info");
repeat_each(1);
no_long_string();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!defined $block->request) { $block->set_value("request", "GET /t"); }
my $http_config = $block->http_config // <<_EOC_;
server {
listen 6724;
default_type 'application/json';
location /v2/guard {
content_by_lua_block {
local core = require("apisix.core")
local fixture_loader = require("lib.fixture_loader")
ngx.req.read_body()
local body = ngx.req.get_body_data() or ""
if core.string.find(body, "lakera-error") then
ngx.status = 500
ngx.say([[{"error":"simulated lakera error"}]])
return
end
ngx.status = 200
ngx.print(fixture_loader.load("lakera/scan-clean.json"))
}
}
}
_EOC_
$block->set_value("http_config", $http_config);
});
run_tests();
__DATA__
=== TEST 1: BASELINE (non-streaming) alert + fail_open=false + Lakera error
=> fails CLOSED
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, [[{
"uri": "/anything",
"plugins": {
"ai-proxy": { "provider": "openai-compatible", "auth": {
"header": { "Authorization": "Bearer token" } }, "options": { "model": "gpt-4"
}, "override": { "endpoint": "http://127.0.0.1:1980/v1/chat/completions" },
"ssl_verify": false },
"ai-lakera-guard": { "api_key": "test-key",
"lakera_endpoint": "http://127.0.0.1:6724/v2/guard", "direction": "output",
"action": "alert", "fail_open": false }
}
}]])
if code >= 300 then ngx.status = code end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: non-streaming alert + Lakera error is BLOCKED (deny body,
content not leaked)
--- request
POST /anything
{ "messages": [ { "role": "user", "content": "say hi" } ] }
--- more_headers
X-AI-Fixture: openai/chat-lakera-error.json
--- response_body_like eval
qr/Response blocked by Lakera Guard/
--- error_log
fail_open=false, blocking response
=== TEST 3: SAME route, streaming request (direction=output, alert,
fail_open=false)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, [[{
"uri": "/anything",
"plugins": {
"ai-proxy": { "provider": "openai-compatible", "auth": {
"header": { "Authorization": "Bearer token" } }, "options": { "model": "gpt-4"
}, "override": { "endpoint": "http://127.0.0.1:1980/v1/chat/completions" },
"ssl_verify": false },
"ai-lakera-guard": { "api_key": "test-key",
"lakera_endpoint": "http://127.0.0.1:6724/v2/guard", "direction": "output",
"action": "alert", "fail_open": false }
}
}]])
if code >= 300 then ngx.status = code end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: BUG - streaming alert + Lakera error + fail_open=false LEAKS the
response
--- request
POST /anything
{ "messages": [ { "role": "user", "content": "say hi" } ], "stream": true }
--- more_headers
X-AI-Fixture: openai/chat-streaming-lakera-error.sse
--- error_code: 200
--- response_body_like eval
qr/lakera-error here/
--- error_log
fail_open=false, blocking response
```
All four tests pass (the streaming case asserts the *current, buggy*
behavior).
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]