Raised on #13731 at the maintainers' request, split out from that docs PR.
Current Behavior
When the set of messages selected for checking is empty, ai-prompt-guard returns 200 from _M.access (apisix/plugins/ai-prompt-guard.lua:162-164). plugin.lua:1406-1419 hands any non-nil code to core.response.exit, and core/response.lua:126-134 calls ngx_exit(code) for any code — the >= 400 branch only adds tracing. The client gets an empty 200 and the upstream is never called.
Two ordinary requests reach it:
match_all_roles left at its default false (ai-prompt-guard.lua:38-41) and no user message in the request, e.g. only a system message. Non-user messages are filtered out (:152-154), the list is empty, and the request is answered with an empty 200. No non-default configuration is needed to reach this.
- Embeddings with
input as an array of strings. ai-protocols/openai-embeddings.lua:109-117 only collects a string input, so an array yields {}.
The repo already uses both meanings of the return value: cors.lua:338-339 returns 200 deliberately to end an OPTIONS preflight, and ai-lakera-guard.lua:104-105 uses a bare return for the same "no messages" condition and lets the request continue.
Nothing is logged: the warn at plugin.lua:1408-1409 only fires for codes >= 400.
Expected Behavior
Nothing to check should mean pass through to the upstream, as ai-lakera-guard does. The fix is return instead of return 200.
The tests don't catch this. t/plugin/ai-prompt-guard.t TEST 9 (line 243, "send request with bad word and it will pass for non user") is exactly the first case — its route, from TEST 8, sets only allow_patterns and leaves match_all_roles at the default — and it asserts neither --- response_body nor --- error_code, so it passes whether the request was proxied or cut off. TESTs 3, 6, 14, 20, 23 and 26 have the same gap. Adding --- response_body / hello world (t/lib/server.lua:74-79) would make them discriminate, following t/plugin/ai-lakera-guard.t, which already asserts --- error_code: 200 with --- response_body_like on its allow paths.
Error Logs
None — see the note on logging above.
Steps to Reproduce
- Use the route from TEST 8 in
t/plugin/ai-prompt-guard.t (only allow_patterns set; match_all_roles at its default).
- Add
--- response_body with hello world to TEST 9.
- Run
prove t/plugin/ai-prompt-guard.t — TEST 9 should fail on current master, and pass once return 200 becomes return.
Environment
master at d13105e17 (2026-09-14). Traced from source, not run — I don't have an APISIX test environment set up, so treat the reproduction above as the expected result rather than an observed one.
Happy to open the PR with the one-line fix and the test assertions.
Raised on #13731 at the maintainers' request, split out from that docs PR.
Current Behavior
When the set of messages selected for checking is empty,
ai-prompt-guardreturns200from_M.access(apisix/plugins/ai-prompt-guard.lua:162-164).plugin.lua:1406-1419hands any non-nil code tocore.response.exit, andcore/response.lua:126-134callsngx_exit(code)for any code — the>= 400branch only adds tracing. The client gets an empty 200 and the upstream is never called.Two ordinary requests reach it:
match_all_rolesleft at its defaultfalse(ai-prompt-guard.lua:38-41) and nousermessage in the request, e.g. only asystemmessage. Non-user messages are filtered out (:152-154), the list is empty, and the request is answered with an empty 200. No non-default configuration is needed to reach this.inputas an array of strings.ai-protocols/openai-embeddings.lua:109-117only collects a stringinput, so an array yields{}.The repo already uses both meanings of the return value:
cors.lua:338-339returns200deliberately to end an OPTIONS preflight, andai-lakera-guard.lua:104-105uses a barereturnfor the same "no messages" condition and lets the request continue.Nothing is logged: the warn at
plugin.lua:1408-1409only fires for codes>= 400.Expected Behavior
Nothing to check should mean pass through to the upstream, as
ai-lakera-guarddoes. The fix isreturninstead ofreturn 200.The tests don't catch this.
t/plugin/ai-prompt-guard.tTEST 9 (line 243, "send request with bad word and it will pass for non user") is exactly the first case — its route, from TEST 8, sets onlyallow_patternsand leavesmatch_all_rolesat the default — and it asserts neither--- response_bodynor--- error_code, so it passes whether the request was proxied or cut off. TESTs 3, 6, 14, 20, 23 and 26 have the same gap. Adding--- response_body/hello world(t/lib/server.lua:74-79) would make them discriminate, followingt/plugin/ai-lakera-guard.t, which already asserts--- error_code: 200with--- response_body_likeon its allow paths.Error Logs
None — see the note on logging above.
Steps to Reproduce
t/plugin/ai-prompt-guard.t(onlyallow_patternsset;match_all_rolesat its default).--- response_bodywithhello worldto TEST 9.prove t/plugin/ai-prompt-guard.t— TEST 9 should fail on currentmaster, and pass oncereturn 200becomesreturn.Environment
masteratd13105e17(2026-09-14). Traced from source, not run — I don't have an APISIX test environment set up, so treat the reproduction above as the expected result rather than an observed one.Happy to open the PR with the one-line fix and the test assertions.