You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by "Revolyssup (via GitHub)" <gi...@apache.org> on 2025/02/28 04:39:09 UTC
[PR] feat: add ai-prompt-guard plugin [apisix]
Revolyssup opened a new pull request, #12008:
URL: https://github.com/apache/apisix/pull/12008
### Description
<!-- Please include a summary of the change and which issue is fixed. -->
<!-- Please also include relevant motivation and context. -->
Fixes # (issue)
### Checklist
- [ ] I have explained the need for this PR and the problem it solves
- [ ] I have explained the changes or the new features added to this PR
- [ ] I have added tests corresponding to this change
- [ ] I have updated the documentation to reflect this change
- [ ] I have verified that this change is backward compatible (If not, please discuss on the [APISIX mailing list](https://github.com/apache/apisix/tree/master#community) first)
<!--
Note
1. Mark the PR as draft until it's ready to be reviewed.
2. Always add/update tests for any changes unless you have a good reason.
3. Always update the documentation to reflect the changes made in the PR.
4. Make a new commit to resolve conversations instead of `push -f`.
5. To resolve merge conflicts, merge master instead of rebasing.
6. Use "request review" to notify the reviewer after making changes.
7. Only a reviewer can mark a conversation as resolved.
-->
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "membphis (via GitHub)" <gi...@apache.org>.
membphis commented on code in PR #12008:
URL: https://github.com/apache/apisix/pull/12008#discussion_r1975093640
##########
apisix/plugins/ai-prompt-guard.lua:
##########
@@ -0,0 +1,119 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local ipairs = ipairs
+local table = table
+
+local plugin_name = "ai-prompt-guard"
+
+local schema = {
+ type = "object",
+ properties = {
+ match_all_roles = {
+ type = "boolean",
+ default = false,
+ },
+ match_all_conversation_history = {
+ type = "boolean",
+ default = false,
+ },
+ allow_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ deny_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ },
+}
+
+local _M = {
+ version = 0.1,
+ priority = 1072,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
Review Comment:
need to confirm the items in `allow_patterns[]` and `deny_patterns[]`, they should be a valid regex
you can call this function: https://github.com/openresty/lua-resty-core/blob/master/lib/resty/core/regex.lua#L639
##########
apisix/plugins/ai-prompt-guard.lua:
##########
@@ -0,0 +1,119 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local ipairs = ipairs
+local table = table
+
+local plugin_name = "ai-prompt-guard"
+
+local schema = {
+ type = "object",
+ properties = {
+ match_all_roles = {
+ type = "boolean",
+ default = false,
+ },
+ match_all_conversation_history = {
+ type = "boolean",
+ default = false,
+ },
+ allow_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ deny_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ },
+}
+
+local _M = {
+ version = 0.1,
+ priority = 1072,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+local function get_content_to_check(conf, messages)
+ local contents = {}
+ if conf.match_all_conversation_history then
+ for _, msg in ipairs(messages) do
+ if msg.content then
+ core.table.insert(contents, msg.content)
+ end
+ end
+ else
+ if #messages > 0 then
+ local last_msg = messages[#messages]
+ if last_msg.content then
+ core.table.insert(contents, last_msg.content)
+ end
+ end
+ end
+ return table.concat(contents, " ")
+end
+
+function _M.access(conf, ctx)
+ local body = core.request.get_body()
+ if not body then
+ core.log.error("Empty request body")
+ return 400, {message = "Empty request body"}
+ end
+
+ local json_body, err = core.json.decode(body)
+ if err then
+ return 400, {message = err}
+ end
+
+
+ local messages = json_body.messages or {}
+ if not conf.match_all_roles and messages and messages[#messages].role ~= "user" then
+ return
+ end
+ local content_to_check = get_content_to_check(conf, messages)
+
+ -- Allow patterns check
+ if #conf.allow_patterns > 0 then
+ local any_allowed = false
+ for _, pattern in ipairs(conf.allow_patterns) do
+ if ngx.re.find(content_to_check, pattern, "jou") then
Review Comment:
the `pattern` is not a plain text, it is a regex
the current way is wrong
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "membphis (via GitHub)" <gi...@apache.org>.
membphis commented on code in PR #12008:
URL: https://github.com/apache/apisix/pull/12008#discussion_r1982656640
##########
docs/en/latest/plugins/ai-prompt-guard.md:
##########
@@ -0,0 +1,89 @@
+---
+title: ai-prompt-guard
+keywords:
+ - Apache APISIX
+ - API Gateway
+ - Plugin
+ - ai-prompt-guard
+description: This document contains information about the Apache APISIX ai-prompt-guard Plugin.
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+## Description
+
+The `ai-prompt-guard` plugin safeguards your AI endpoints by inspecting and validating incoming prompt messages. It checks the content of requests against user-defined allowed and denied patterns to ensure that only approved inputs are processed. Based on its configuration, the plugin can either examine just the latest message or the entire conversation history, and it can be set to check prompts from all roles or only from end users.
+
+When both **allow** and **deny** patterns are configured, the plugin first ensures that at least one allowed pattern is matched. If none match, the request is rejected with a _"Request doesn't match allow patterns"_ error. If an allowed pattern is found, it then checks for any occurrences of denied patterns—rejecting the request with a _"Request contains prohibited content"_ error if any are detected.
+
+## Plugin Attributes
+
+| **Field** | **Required** | **Type** | **Description** |
+| ------------------------------ | ------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| match_all_roles | No | boolean | If set to `true`, the plugin will check prompt messages from all roles. Otherwise, it only validates when its role is `"user"`. Default is `false`. |
+| match_all_conversation_history | No | boolean | When enabled, all messages in the conversation history are concatenated and checked. If `false`, only the content of the last message is examined. Default is `false`. |
+| allow_patterns | No | array | A list of regex patterns. When provided, the prompt must match **at least one** pattern to be considered valid. |
+| deny_patterns | No | array | A list of regex patterns. If any of these patterns match the prompt content, the request is rejected. |
+
+## Example usage
+
+Create a route with the `ai-prompt-guard` plugin like so:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \
+ -H "X-API-KEY: ${ADMIN_API_KEY}" \
+ -d '{
+ "uri": "/v1/chat/completions",
+ "plugins": {
+ "ai-prompt-guard": {
+ "match_all_roles": true,
+ "allow_patterns": [
Review Comment:
bad indentation
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "Revolyssup (via GitHub)" <gi...@apache.org>.
Revolyssup commented on PR #12008:
URL: https://github.com/apache/apisix/pull/12008#issuecomment-2690315529
@membphis Made changes
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "nic-6443 (via GitHub)" <gi...@apache.org>.
nic-6443 commented on code in PR #12008:
URL: https://github.com/apache/apisix/pull/12008#discussion_r1980940354
##########
apisix/plugins/ai-prompt-guard.lua:
##########
@@ -0,0 +1,142 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local ipairs = ipairs
+local table = table
+local re_compile = require("resty.core.regex").re_match_compile
+local re_find = ngx.re.find
+
+local plugin_name = "ai-prompt-guard"
+
+local schema = {
+ type = "object",
+ properties = {
+ match_all_roles = {
+ type = "boolean",
+ default = false,
+ },
+ match_all_conversation_history = {
+ type = "boolean",
+ default = false,
+ },
+ allow_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ deny_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ },
+}
+
+local _M = {
+ version = 0.1,
+ priority = 1072,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ local ok, err = core.schema.check(schema, conf)
+ if not ok then
+ return false, err
+ end
+
+ -- Validate allow_patterns
+ for _, pattern in ipairs(conf.allow_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid allow_pattern: " .. pattern
+ end
+ end
+
+ -- Validate deny_patterns
+ for _, pattern in ipairs(conf.deny_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid deny_pattern: " .. pattern
+ end
+ end
+
+ return true
+end
+
+local function get_content_to_check(conf, messages)
+ local contents = {}
+ if conf.match_all_conversation_history then
+ for _, msg in ipairs(messages) do
+ if msg.content then
+ core.table.insert(contents, msg.content)
+ end
+ end
+ else
+ if #messages > 0 then
+ local last_msg = messages[#messages]
+ if last_msg.content then
+ core.table.insert(contents, last_msg.content)
+ end
+ end
+ end
+ return table.concat(contents, " ")
+end
+
+function _M.access(conf, ctx)
+ local body = core.request.get_body()
+ if not body then
+ core.log.error("Empty request body")
+ return 400, {message = "Empty request body"}
+ end
+
+ local json_body, err = core.json.decode(body)
+ if err then
+ return 400, {message = err}
+ end
+
+ local messages = json_body.messages or {}
+ if not conf.match_all_roles and #messages > 0 and messages[#messages].role ~= "user" then
+ return
+ end
Review Comment:
I can't understand this check? should we check all messages that's role is user when `match_all_roles` is false? why only check last message in array?
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "shreemaan-abhishek (via GitHub)" <gi...@apache.org>.
shreemaan-abhishek merged PR #12008:
URL: https://github.com/apache/apisix/pull/12008
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "Revolyssup (via GitHub)" <gi...@apache.org>.
Revolyssup commented on code in PR #12008:
URL: https://github.com/apache/apisix/pull/12008#discussion_r1981256343
##########
apisix/plugins/ai-prompt-guard.lua:
##########
@@ -0,0 +1,159 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local ipairs = ipairs
+local table = table
+local re_compile = require("resty.core.regex").re_match_compile
+local re_find = ngx.re.find
+
+local plugin_name = "ai-prompt-guard"
+
+local schema = {
+ type = "object",
+ properties = {
+ match_all_roles = {
+ type = "boolean",
+ default = false,
+ },
+ match_all_conversation_history = {
+ type = "boolean",
+ default = false,
+ },
+ allow_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ deny_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ },
+}
+
+local _M = {
+ version = 0.1,
+ priority = 1072,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ local ok, err = core.schema.check(schema, conf)
+ if not ok then
+ return false, err
+ end
+
+ -- Validate allow_patterns
+ for _, pattern in ipairs(conf.allow_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid allow_pattern: " .. pattern
+ end
+ end
+
+ -- Validate deny_patterns
+ for _, pattern in ipairs(conf.deny_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid deny_pattern: " .. pattern
+ end
+ end
+
+ return true
+end
+
+local function get_content_to_check(conf, messages)
+ if conf.match_all_conversation_history then
+ return messages
+ end
+ local contents = {}
+ if #messages > 0 then
+ local last_msg = messages[#messages]
+ if last_msg then
+ core.table.insert(contents, last_msg)
+ end
+ end
+ return contents
+end
+
+function _M.access(conf, ctx)
+ local body = core.request.get_body()
+ if not body then
+ core.log.error("Empty request body")
+ return 400, {message = "Empty request body"}
+ end
+
+ local json_body, err = core.json.decode(body)
+ if err then
+ return 400, {message = err}
+ end
+
+ local messages = json_body.messages or {}
+ messages = get_content_to_check(conf, messages)
+ if not conf.match_all_roles then
+ -- filter to only user messages
+ local new_messages = {}
+ for _, msg in ipairs(messages) do
+ if not msg then
Review Comment:
removed check
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "nic-6443 (via GitHub)" <gi...@apache.org>.
nic-6443 commented on code in PR #12008:
URL: https://github.com/apache/apisix/pull/12008#discussion_r1980933019
##########
apisix/plugins/ai-prompt-guard.lua:
##########
@@ -0,0 +1,142 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local ipairs = ipairs
+local table = table
+local re_compile = require("resty.core.regex").re_match_compile
+local re_find = ngx.re.find
+
+local plugin_name = "ai-prompt-guard"
+
+local schema = {
+ type = "object",
+ properties = {
+ match_all_roles = {
+ type = "boolean",
+ default = false,
+ },
+ match_all_conversation_history = {
+ type = "boolean",
+ default = false,
+ },
+ allow_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ deny_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ },
+}
+
+local _M = {
+ version = 0.1,
+ priority = 1072,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ local ok, err = core.schema.check(schema, conf)
+ if not ok then
+ return false, err
+ end
+
+ -- Validate allow_patterns
+ for _, pattern in ipairs(conf.allow_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid allow_pattern: " .. pattern
+ end
+ end
+
+ -- Validate deny_patterns
+ for _, pattern in ipairs(conf.deny_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid deny_pattern: " .. pattern
+ end
+ end
+
+ return true
+end
+
+local function get_content_to_check(conf, messages)
+ local contents = {}
+ if conf.match_all_conversation_history then
+ for _, msg in ipairs(messages) do
+ if msg.content then
+ core.table.insert(contents, msg.content)
+ end
+ end
+ else
+ if #messages > 0 then
+ local last_msg = messages[#messages]
+ if last_msg.content then
+ core.table.insert(contents, last_msg.content)
+ end
+ end
+ end
+ return table.concat(contents, " ")
+end
+
+function _M.access(conf, ctx)
+ local body = core.request.get_body()
+ if not body then
+ core.log.error("Empty request body")
+ return 400, {message = "Empty request body"}
Review Comment:
```suggestion
core.log.error("empty request body")
return 400, {message = "empty request body"}
```
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "Revolyssup (via GitHub)" <gi...@apache.org>.
Revolyssup commented on PR #12008:
URL: https://github.com/apache/apisix/pull/12008#issuecomment-2700093788
> Please add a description of this feature to the PR, otherwise I don't know why it should exist. The project is in the public domain and I think there will be others who would like to know the details as well.
>
> 
Added description
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "nic-6443 (via GitHub)" <gi...@apache.org>.
nic-6443 commented on code in PR #12008:
URL: https://github.com/apache/apisix/pull/12008#discussion_r1980943199
##########
apisix/plugins/ai-prompt-guard.lua:
##########
@@ -0,0 +1,142 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local ipairs = ipairs
+local table = table
+local re_compile = require("resty.core.regex").re_match_compile
+local re_find = ngx.re.find
+
+local plugin_name = "ai-prompt-guard"
+
+local schema = {
+ type = "object",
+ properties = {
+ match_all_roles = {
+ type = "boolean",
+ default = false,
+ },
+ match_all_conversation_history = {
+ type = "boolean",
+ default = false,
+ },
+ allow_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ deny_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ },
+}
+
+local _M = {
+ version = 0.1,
+ priority = 1072,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ local ok, err = core.schema.check(schema, conf)
+ if not ok then
+ return false, err
+ end
+
+ -- Validate allow_patterns
+ for _, pattern in ipairs(conf.allow_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid allow_pattern: " .. pattern
+ end
+ end
+
+ -- Validate deny_patterns
+ for _, pattern in ipairs(conf.deny_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid deny_pattern: " .. pattern
+ end
+ end
+
+ return true
+end
+
+local function get_content_to_check(conf, messages)
+ local contents = {}
+ if conf.match_all_conversation_history then
+ for _, msg in ipairs(messages) do
+ if msg.content then
+ core.table.insert(contents, msg.content)
+ end
+ end
+ else
+ if #messages > 0 then
+ local last_msg = messages[#messages]
+ if last_msg.content then
+ core.table.insert(contents, last_msg.content)
+ end
+ end
+ end
+ return table.concat(contents, " ")
+end
+
+function _M.access(conf, ctx)
+ local body = core.request.get_body()
+ if not body then
+ core.log.error("Empty request body")
+ return 400, {message = "Empty request body"}
+ end
+
+ local json_body, err = core.json.decode(body)
+ if err then
+ return 400, {message = err}
+ end
+
+ local messages = json_body.messages or {}
+ if not conf.match_all_roles and #messages > 0 and messages[#messages].role ~= "user" then
+ return
+ end
Review Comment:
Combined with the `match_all_conversation_history` configuration, we should first obtain the list of messages to be judged based on `match_all_conversation_history`, and then decide whether to perform pattern judgment according to each message's role and `match_all_roles`.
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "nic-6443 (via GitHub)" <gi...@apache.org>.
nic-6443 commented on code in PR #12008:
URL: https://github.com/apache/apisix/pull/12008#discussion_r1981152715
##########
apisix/plugins/ai-prompt-guard.lua:
##########
@@ -0,0 +1,159 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local ipairs = ipairs
+local table = table
+local re_compile = require("resty.core.regex").re_match_compile
+local re_find = ngx.re.find
+
+local plugin_name = "ai-prompt-guard"
+
+local schema = {
+ type = "object",
+ properties = {
+ match_all_roles = {
+ type = "boolean",
+ default = false,
+ },
+ match_all_conversation_history = {
+ type = "boolean",
+ default = false,
+ },
+ allow_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ deny_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ },
+}
+
+local _M = {
+ version = 0.1,
+ priority = 1072,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ local ok, err = core.schema.check(schema, conf)
+ if not ok then
+ return false, err
+ end
+
+ -- Validate allow_patterns
+ for _, pattern in ipairs(conf.allow_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid allow_pattern: " .. pattern
+ end
+ end
+
+ -- Validate deny_patterns
+ for _, pattern in ipairs(conf.deny_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid deny_pattern: " .. pattern
+ end
+ end
+
+ return true
+end
+
+local function get_content_to_check(conf, messages)
+ if conf.match_all_conversation_history then
+ return messages
+ end
+ local contents = {}
+ if #messages > 0 then
+ local last_msg = messages[#messages]
+ if last_msg then
+ core.table.insert(contents, last_msg)
+ end
+ end
+ return contents
+end
+
+function _M.access(conf, ctx)
+ local body = core.request.get_body()
+ if not body then
+ core.log.error("Empty request body")
+ return 400, {message = "Empty request body"}
+ end
+
+ local json_body, err = core.json.decode(body)
+ if err then
+ return 400, {message = err}
+ end
+
+ local messages = json_body.messages or {}
+ messages = get_content_to_check(conf, messages)
+ if not conf.match_all_roles then
+ -- filter to only user messages
+ local new_messages = {}
+ for _, msg in ipairs(messages) do
+ if not msg then
Review Comment:
look like this condition always true
##########
apisix/plugins/ai-prompt-guard.lua:
##########
@@ -0,0 +1,159 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local ipairs = ipairs
+local table = table
+local re_compile = require("resty.core.regex").re_match_compile
+local re_find = ngx.re.find
+
+local plugin_name = "ai-prompt-guard"
+
+local schema = {
+ type = "object",
+ properties = {
+ match_all_roles = {
+ type = "boolean",
+ default = false,
+ },
+ match_all_conversation_history = {
+ type = "boolean",
+ default = false,
+ },
+ allow_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ deny_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ },
+}
+
+local _M = {
+ version = 0.1,
+ priority = 1072,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ local ok, err = core.schema.check(schema, conf)
+ if not ok then
+ return false, err
+ end
+
+ -- Validate allow_patterns
+ for _, pattern in ipairs(conf.allow_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid allow_pattern: " .. pattern
+ end
+ end
+
+ -- Validate deny_patterns
+ for _, pattern in ipairs(conf.deny_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid deny_pattern: " .. pattern
+ end
+ end
+
+ return true
+end
+
+local function get_content_to_check(conf, messages)
+ if conf.match_all_conversation_history then
+ return messages
+ end
+ local contents = {}
+ if #messages > 0 then
+ local last_msg = messages[#messages]
+ if last_msg then
+ core.table.insert(contents, last_msg)
+ end
+ end
+ return contents
+end
+
+function _M.access(conf, ctx)
+ local body = core.request.get_body()
+ if not body then
+ core.log.error("Empty request body")
+ return 400, {message = "Empty request body"}
+ end
+
+ local json_body, err = core.json.decode(body)
+ if err then
+ return 400, {message = err}
+ end
+
+ local messages = json_body.messages or {}
+ messages = get_content_to_check(conf, messages)
+ if not conf.match_all_roles then
+ -- filter to only user messages
+ local new_messages = {}
+ for _, msg in ipairs(messages) do
+ if not msg then
+ return 400, {message = "request doesn't contain messages"}
+ end
+ if msg.role == "user" then
+ core.table.insert(new_messages, msg)
+ end
+ end
+ messages = new_messages
+ end
+ if #messages == 0 then --nothing to check
+ return 200
+ end
+ -- extract only messages
+ local content = {}
+ for _, msg in ipairs(messages) do
+ if not msg then
+ return 400, {message = "request doesn't contain messages"}
+ end
+ if msg.content then
+ core.table.insert(content, msg.content)
+ end
+ end
+ local content_to_check = table.concat(content, " ")
Review Comment:
I think we should match messages one by one instead of concatenate all messages as one.
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "nic-6443 (via GitHub)" <gi...@apache.org>.
nic-6443 commented on code in PR #12008:
URL: https://github.com/apache/apisix/pull/12008#discussion_r1980933019
##########
apisix/plugins/ai-prompt-guard.lua:
##########
@@ -0,0 +1,142 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local ngx = ngx
+local ipairs = ipairs
+local table = table
+local re_compile = require("resty.core.regex").re_match_compile
+local re_find = ngx.re.find
+
+local plugin_name = "ai-prompt-guard"
+
+local schema = {
+ type = "object",
+ properties = {
+ match_all_roles = {
+ type = "boolean",
+ default = false,
+ },
+ match_all_conversation_history = {
+ type = "boolean",
+ default = false,
+ },
+ allow_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ deny_patterns = {
+ type = "array",
+ items = {type = "string"},
+ default = {},
+ },
+ },
+}
+
+local _M = {
+ version = 0.1,
+ priority = 1072,
+ name = plugin_name,
+ schema = schema,
+}
+
+function _M.check_schema(conf)
+ local ok, err = core.schema.check(schema, conf)
+ if not ok then
+ return false, err
+ end
+
+ -- Validate allow_patterns
+ for _, pattern in ipairs(conf.allow_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid allow_pattern: " .. pattern
+ end
+ end
+
+ -- Validate deny_patterns
+ for _, pattern in ipairs(conf.deny_patterns) do
+ local compiled = re_compile(pattern, "jou")
+ if not compiled then
+ return false, "invalid deny_pattern: " .. pattern
+ end
+ end
+
+ return true
+end
+
+local function get_content_to_check(conf, messages)
+ local contents = {}
+ if conf.match_all_conversation_history then
+ for _, msg in ipairs(messages) do
+ if msg.content then
+ core.table.insert(contents, msg.content)
+ end
+ end
+ else
+ if #messages > 0 then
+ local last_msg = messages[#messages]
+ if last_msg.content then
+ core.table.insert(contents, last_msg.content)
+ end
+ end
+ end
+ return table.concat(contents, " ")
+end
+
+function _M.access(conf, ctx)
+ local body = core.request.get_body()
+ if not body then
+ core.log.error("Empty request body")
+ return 400, {message = "Empty request body"}
Review Comment:
```suggestion
core.log.error("empty request body")
return 400, {message = "empty request body"}
```
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] feat: add ai-prompt-guard plugin [apisix]
Posted by "membphis (via GitHub)" <gi...@apache.org>.
membphis commented on code in PR #12008:
URL: https://github.com/apache/apisix/pull/12008#discussion_r1982842887
##########
docs/en/latest/plugins/ai-prompt-guard.md:
##########
@@ -0,0 +1,89 @@
+---
+title: ai-prompt-guard
+keywords:
+ - Apache APISIX
+ - API Gateway
+ - Plugin
+ - ai-prompt-guard
+description: This document contains information about the Apache APISIX ai-prompt-guard Plugin.
+---
+
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+## Description
+
+The `ai-prompt-guard` plugin safeguards your AI endpoints by inspecting and validating incoming prompt messages. It checks the content of requests against user-defined allowed and denied patterns to ensure that only approved inputs are processed. Based on its configuration, the plugin can either examine just the latest message or the entire conversation history, and it can be set to check prompts from all roles or only from end users.
+
+When both **allow** and **deny** patterns are configured, the plugin first ensures that at least one allowed pattern is matched. If none match, the request is rejected with a _"Request doesn't match allow patterns"_ error. If an allowed pattern is found, it then checks for any occurrences of denied patterns—rejecting the request with a _"Request contains prohibited content"_ error if any are detected.
+
+## Plugin Attributes
+
+| **Field** | **Required** | **Type** | **Description** |
+| ------------------------------ | ------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| match_all_roles | No | boolean | If set to `true`, the plugin will check prompt messages from all roles. Otherwise, it only validates when its role is `"user"`. Default is `false`. |
+| match_all_conversation_history | No | boolean | When enabled, all messages in the conversation history are concatenated and checked. If `false`, only the content of the last message is examined. Default is `false`. |
+| allow_patterns | No | array | A list of regex patterns. When provided, the prompt must match **at least one** pattern to be considered valid. |
+| deny_patterns | No | array | A list of regex patterns. If any of these patterns match the prompt content, the request is rejected. |
+
+## Example usage
+
+Create a route with the `ai-prompt-guard` plugin like so:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \
+ -H "X-API-KEY: ${ADMIN_API_KEY}" \
+ -d '{
+ "uri": "/v1/chat/completions",
+ "plugins": {
+ "ai-prompt-guard": {
+ "match_all_roles": true,
+ "allow_patterns": [
Review Comment:
bad indentation too
--
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: notifications-unsubscribe@apisix.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org