来源 PR:#251 #251
严重级别:P1
位置:apps/api/src/common/rate-limit/rate-limit.service.ts:64
风险
在 assertAllowedWithRedis 中,限流计数器使用 client.incr(key) 递增,并且仅在 count === 1 时调用 client.pExpire(key, input.windowMs) 设置过期时间。如果在高并发下或者在 incr 执行成功后、pExpire 执行前发生网络抖动、服务重启或 Redis 故障,该 Key 将永远不会被设置过期时间(TTL 为永久)。由于该 Key 不包含日期或时间戳,一旦丢失 TTL,该用户/IP 将被永久限制访问,除非手动删除 Redis Key。
建议
建议使用 Redis Lua 脚本原子性地执行 INCR 和 EXPIRE,或者无论 count 是否为 1,都确保设置过期时间。例如使用 Lua 脚本:
const lua = `
local current = redis.call('incr', KEYS[1])
if current == 1 then
redis.call('pexpire', KEYS[1], ARGV[1])
end
return current
`;
const count = Number(await client.eval(lua, { keys: [key], arguments: [String(input.windowMs)] }));
Checked at: 2026-06-10T09:47:34.590366Z
来源 PR:#251 #251
严重级别:
P1位置:
apps/api/src/common/rate-limit/rate-limit.service.ts:64风险
在
assertAllowedWithRedis中,限流计数器使用client.incr(key)递增,并且仅在count === 1时调用client.pExpire(key, input.windowMs)设置过期时间。如果在高并发下或者在incr执行成功后、pExpire执行前发生网络抖动、服务重启或 Redis 故障,该 Key 将永远不会被设置过期时间(TTL 为永久)。由于该 Key 不包含日期或时间戳,一旦丢失 TTL,该用户/IP 将被永久限制访问,除非手动删除 Redis Key。建议
建议使用 Redis Lua 脚本原子性地执行
INCR和EXPIRE,或者无论count是否为 1,都确保设置过期时间。例如使用 Lua 脚本:Checked at: 2026-06-10T09:47:34.590366Z