HY-love-sleep opened a new pull request, #7153:
URL: https://github.com/apache/shenyu/pull/7153
### Motivation
A gateway that fronts an LLM should be able to stop a prompt that carries
forbidden content
before the model ever sees it, and this is a compliance requirement for a
lot of deployments.
ShenYu has no content-based filter today: the `waf` plugin only matches
request conditions
(uri / header / param) and never looks at the body, and the logging plugins
desensitize log
records only.
This PR adds a sensitive word filter for the request body.
### What is added
| | |
| --- | --- |
| `shenyu-plugin-ai/shenyu-plugin-ai-sensitive-word` | the plugin, the
Aho-Corasick automaton and the data handler |
| `shenyu-spring-boot-starter-plugin-ai-sensitive-word` | the starter |
| `shenyu-common` | `SensitiveWordHandle` (rule level) and
`PluginEnum.SENSITIVE_WORD` |
| `shenyu-plugin-ai/pom.xml`, starter pom, `shenyu-bootstrap/pom.xml` |
module registration |
### Design
1. **The dictionary lives in redis.** The rule carries a `redisKey` (default
`shenyu:sensitive:words`) and the plugin reads the set with `SMEMBERS`,
so the dictionary is
maintained by operations without redeploying shenyu, and every rule can
point at its own set.
2. **Aho-Corasick.** The dictionary is compiled into an automaton, so a body
is scanned in a
single pass and every matching word is reported, nested and overlapping
ones included (both
`中国` and `中国银行` for `中国银行`).
3. **The automaton is cached per redis key**, not per plugin: rules with
different dictionaries
never share an automaton. A cached dictionary is read from redis again
after
`refreshIntervalSeconds` (default 300), and it is dropped immediately
when the rule is
configured again, so a dictionary change does not need a gateway restart.
4. **The event loop is never blocked.** The body is read with the shared
`ServerWebExchangeUtils#rewriteRequestBody`, and the automaton is
compiled on a bounded elastic
thread, never inside the request thread.
5. **Fail open.** If redis is unreachable the request is passed through and
the failure is logged.
A dictionary that cannot be read must not take the traffic down.
6. **Request body only.** Only the request body is inspected, so the plugin
is not limited to AI
routes; response-side (including SSE streaming) detection is a possible
follow-up.
### Configuration
Plugin level, the redis client used to read the dictionaries:
```json
{"url": "127.0.0.1:6379", "password": "", "database": 0, "mode":
"standalone"}
```
Rule level:
| field | type | default | description |
| --- | --- | --- | --- |
| `redisKey` | string | `shenyu:sensitive:words` | the redis set holding the
dictionary of this rule |
| `refreshIntervalSeconds` | long | `300` | how long a compiled dictionary
is reused, `0` reads it on every request |
### Why not extend the `waf` plugin
`WafHandle` only carries `permission` and `statusCode`, and `waf` runs at
order 50, before the
body is available: its contract is "reject on the matched conditions", not
"inspect the content".
A dictionary, a refresh policy and a matched-word report do not fit that
handle, and the body has
to be read at a later order anyway.
### Testing
```
./mvnw -pl shenyu-common -Dtest=SensitiveWordHandleTest test
./mvnw -pl
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-sensitive-word,<starter> -am
test
./mvnw -pl shenyu-bootstrap -am -DskipTests package
```
All green: checkstyle 0 violations, RAT ok, and 34 tests
(`AhoCorasickTest` 14, `SensitiveWordPluginTest` 7,
`SensitiveWordPluginDataHandlerTest` 9,
`SensitiveWordHandleTest` 2, starter 2). The automaton tests cover nested,
overlapping and suffix
words, blank entries, empty dictionaries and long texts; the plugin tests
cover the reject path
(response body contains the matched words) and both fail-open paths.
Manual check:
```
redis-cli SADD shenyu:sensitive:words "bad word"
curl -X POST http://localhost:9195/ai/chat -d 'a bad word here'
```
### Not included in this PR (happy to add according to your preference)
- **the `db/init` and `db/upgrade` scripts that register the plugin** (the
`plugin` row and the
`resource` rows for the console). I would like to agree on the plugin
name, its order, the module
placement and the rule handle fields first — if the design is fine I will
add them right away,
in this PR or as a follow-up;
- **the console rule form** (`shenyu-dashboard` project);
- **the dictionary itself is deliberately not bundled.** A word list depends
on the country, the
business and the applicable compliance rules, so it must not be shipped
inside a gateway:
deployments provide their own redis set (see the module README).
--
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]