apache / apache/shenyu

[BUG] AI token limiter Redis key ignores rule and selector scope

Open
#6,514 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
8.8k
Forks
3.1k
Avg merge
7d 1h
Merged PRs (30d)
85

Description

### Search before asking

- [x] I had searched in the [issues](https://github.com/apache/shenyu/issues) and found no similar issues.

### Apache ShenYu Component

shenyu-plugin

### What happened

`AiTokenLimiterPlugin` builds the Redis counter key only from a fixed prefix and the configured resolver value:

```java
private static final String REDIS_KEY_PREFIX = "SHENYU:AI:TOKENLIMIT:";
...
String cacheKey = REDIS_KEY_PREFIX + getCacheKey(exchange, tokenLimitType, keyName);
```

`getCacheKey(...)` returns only request-derived data such as IP, URI, header, parameter, cookie, or context path:

```java
case IP -> Objects.requireNonNull(request.getRemoteAddress()).getHostString();
case URI -> request.getURI().getPath();
case HEADER -> request.getHeaders().getFirst(keyName);
case PARAMETER -> request.getQueryParams().getFirst(keyName);
case COOKIE -> { ... }
default -> exchange.getAttribute(Constants.CONTEXT_PATH);
```

The rule id, selector id, plugin id, and namespace are not included. Therefore two different AI token limiter rules can share the same Redis counter whenever their resolver value is the same.

For example, two separate rules using `aiTokenLimitType=uri` for `/v1/chat/completions` but with different `tokenLimit` values will both read and increment:

```text
SHENYU:AI:TOKENLIMIT:/v1/chat/completions
```

Usage from one rule can exhaust the other rule's quota, even if they belong to different selectors/routes.

For comparison, the regular `RateLimiterPlugin` scopes Redis keys with `rule.getId() + resolverKey` before passing them to the rate limiter.

### Expected behavior

AI token limiter counters should be scoped at least by rule id, and likely also by namespace/selector as needed, before appending the request-derived resolver value. Independent limiter rules should not share Redis counters unless explicitly configured to do so.

### How to reproduce

1. Enable `ai-token-limiter`.
2. Configure two different rules for different selectors/routes that both resolve to the same key value, for example `aiTokenLimitType=uri` and request path `/v1/chat/completions`.
3. Set different `tokenLimit` values for the two rules.
4. Send requests through the first rule until it records token usage.
5. Send a request through the second rule.
6. The second rule reads the same Redis key and is affected by the first rule's token usage.

### Debug logs

_No response_

### Environment

Current `master` branch.

### Are you willing to submit a PR?

- [ ] Yes I am willing to submit a PR!

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at AiTokenLimiterPlugin and inspect getCacheKey(...) and the Redis key construction; compare the scoping used by RateLimiterPlugin. Trace how rule and selector context are available, then verify that two rules with the same resolver value maintain independent counters. Done means the relevant limiter tests or reproduction no longer show cross-rule quota sharing.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, redis
Domain
backend, database
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.