Skip to content

fix(router): CloudEdgeRegister 接入频率限流,复用 AuthRegisterRateLimit 死常量 - #2185

Merged
DeliciousBuding merged 1 commit into
masterfrom
fix/cloud-edge-register-ratelimit
Sep 1, 2026
Merged

fix(router): CloudEdgeRegister 接入频率限流,复用 AuthRegisterRateLimit 死常量#2185
DeliciousBuding merged 1 commit into
masterfrom
fix/cloud-edge-register-ratelimit

Conversation

@DeliciousBuding

Copy link
Copy Markdown
Collaborator

Summary

POST /cloud/edge/register(CloudEdgeRegister)接入 per-IP 频率限流:middleware.RateLimit(cacheClient, config.AuthRegisterRateLimit, config.AuthRateLimitWindow, middleware.IPKey),即每 IP 每 1 分钟最多 3 次,复用此前全仓零引用的死常量 AuthRegisterRateLimit = 3(接线后注释自然成真,常量不再死)。形状与 /client/auth/refresh、OIDC 三条路由既有限流完全一致。

动机(#2154 F16 取证)

  • hub-server/internal/router/router.gocloud.POST("/edge/register", ...) 只挂 authMW.Handler() + RequireHubSession(),无 middleware.RateLimit;对照组为同文件 auth.POST("/refresh", middleware.RateLimit(...), ...)
  • hub-server/internal/config/constants.go:119-120AuthRegisterRateLimit = 3 全仓零引用(死常量)。
  • 该路由虽在鉴权之后,但注册动作可批量创建 cloud_edge 设备记录并签发 edge JWT,缺频率限流即为可放大面。

Scope

  • 只接频率限流(一行接线 + 注释)。每用户 cloud_edge 设备数量上限不在本轮
  • 不改 middleware.RateLimit / isAuthPath / constants 数值。

测试证据

新增路由级测试 TestCloudEdgeRegisterRateLimitedByIPhub-server/internal/router/router_test.go,SetupRoutes 全链路 + miniredis + hub-local JWT + stub DeviceService):

  • 同一 IP 连打 3 次(= AuthRegisterRateLimit)全部 200(穿过 auth→限流→handler 全链);
  • 第 4 次 429 + Retry-After
  • 换 IP 再打仍 200 → 限流键是 IP 维度。

门禁(本地全绿):go vet ./hub-server/...go test ./internal/router/ ./internal/middleware/ ./internal/handler/ ./internal/config/make test(hub+edge 全量)、verify-doc-ssot.pygit diff --checkverify-ci-gates.pyverify-i18n-deadkeys.pyverify-openapi-contract.py(156 routes,0 新增违规)、verify-migration-idempotency.pyverify-test-sleep-ratchet.pyverify-conventions.pyverify-hub-pure-packages.pyverify-hub-layering.pycheck-secrets.sh --rangeverify-commit-messages.sh

注:本地 verify-hub-lint-ratchet.py 报 2 条 jwtutil G101 findings,在未改动的 master(ef7d9ea)上逐字复现(本机 gosec 版本漂移,与本 diff 无关),以 CI 固定工具链为准。

Redis 故障弱失败说明(必读)

isAuthPathinternal/middleware/global_rate_limit.go:58-61)只认 /client/auth/ 前缀,/cloud/edge/register 不在其中 → Redis 故障时该限流器走非鉴权分支:遵循 AGENTHUB_RATE_LIMIT_FAIL_OPEN(默认 fail-open,带 X-Rate-Limit-Degraded: true 与 warn 日志)。这是防滥用限流可接受的弱失败(鉴权本身不受影响,Redis 故障时设备注册仍要过 auth+hub-session 门禁),与 api/conventions.md 限流故障语义一致。

后续项

  • 每用户 cloud_edge 设备数量上限(与频率限流正交)。
  • /edge/devices/register(桌面端注册)同样无频率限流,同类问题登记为后续。

Closes 关联:#2154 F16 项(单片安全加固 round-40)。

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 4e8ccbfe-e9c2-4a79-853f-3b8105c669a5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@DeliciousBuding
DeliciousBuding force-pushed the fix/cloud-edge-register-ratelimit branch from 45a7d50 to 618f8ac Compare September 1, 2026 16:47
@DeliciousBuding
DeliciousBuding force-pushed the fix/cloud-edge-register-ratelimit branch from 618f8ac to da20c63 Compare September 1, 2026 16:58
@DeliciousBuding
DeliciousBuding merged commit 958d662 into master Sep 1, 2026
38 checks passed
@DeliciousBuding
DeliciousBuding deleted the fix/cloud-edge-register-ratelimit branch September 1, 2026 17:06
DeliciousBuding added a commit that referenced this pull request Sep 1, 2026
#2185 只给 /cloud/edge/register 接了路由级限流,/edge/devices/register
(edge 组,已认证 + desktop 设备型检查)仍无频率门禁。按同一形状补齐:
middleware.RateLimit(cacheClient, AuthRegisterRateLimit, AuthRateLimitWindow,
IPKey)。Redis 故障走非认证路径语义(/edge/* 不在 /client/auth/ 下,
AGENTHUB_RATE_LIMIT_FAIL_OPEN 默认 fail-open)。

测试:TestEdgeDeviceRegisterRateLimitedByIP(miniredis + 真 desktop JWT,
device_id claim 与请求体一致以过 handler 交叉校验),连打第 4 次 429 +
Retry-After,异 IP 放行。openapi 同步 /edge/devices/register 429 响应。

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
DeliciousBuding added a commit that referenced this pull request Sep 1, 2026
现状:device.Register 无条件 UpsertDevice,device_id 由调用方自选 UUID,
认证用户可无限注册 cloud_edge 设备(#2154 F16 登记项,#2185 路由限流只
管频率不管存量)。

实现:
- config:DefaultMaxCloudEdgeDevicesPerUser=50(constants.go)+
  AGENTHUB_MAX_CLOUD_EDGE_DEVICES env 覆盖(config_env.go 函数式,同
  RateLimitFailOpen 模式);未设/非法回默认(非法带 warn 日志),<= 0 关闭。
- repository:CountDevicesByUserAndType + DeviceExistsForUser(COUNT 现有
  devices 表,无迁移/无 schema 变更)+ sqlmock 测试。
- service:Register 在 UpsertDevice 前仅对 cloud_edge 判额——已存在同
  device_id 的同用户同型设备(upsert 命中自己)视为刷新不受限;新
  device_id 达上限返回新错误码。
- 错误码语义选 403 而非 429:这是「凭证有效但策略拒绝」的存量配额
  (codes.go 状态码原则 403 类别),不是时间窗限流,重试无意义,
  conventions.md 的 429 专属 rate_limited 且必须带 Retry-After。

测试:service(达上限拒绝/刷新不受限/用户间独立/env 覆盖与禁用/
desktop 不受影响)、handler(403 + envelope code 透传两条注册路由)、
config(默认/合法/非法回退)。同步:openapi 403/429 响应 + 配额描述,
05-deployment.md 配置面索引加 env 行。

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
DeliciousBuding added a commit that referenced this pull request Sep 1, 2026
* fix(router): /edge/devices/register 接入 IP 频率限流,与 CloudEdgeRegister 同型

#2185 只给 /cloud/edge/register 接了路由级限流,/edge/devices/register
(edge 组,已认证 + desktop 设备型检查)仍无频率门禁。按同一形状补齐:
middleware.RateLimit(cacheClient, AuthRegisterRateLimit, AuthRateLimitWindow,
IPKey)。Redis 故障走非认证路径语义(/edge/* 不在 /client/auth/ 下,
AGENTHUB_RATE_LIMIT_FAIL_OPEN 默认 fail-open)。

测试:TestEdgeDeviceRegisterRateLimitedByIP(miniredis + 真 desktop JWT,
device_id claim 与请求体一致以过 handler 交叉校验),连打第 4 次 429 +
Retry-After,异 IP 放行。openapi 同步 /edge/devices/register 429 响应。

Co-authored-by: Cursor <cursor@vectorcontrol.tech>

* feat(device): cloud_edge 设备每用户数量上限(新错误码 device_limit_exceeded)

现状:device.Register 无条件 UpsertDevice,device_id 由调用方自选 UUID,
认证用户可无限注册 cloud_edge 设备(#2154 F16 登记项,#2185 路由限流只
管频率不管存量)。

实现:
- config:DefaultMaxCloudEdgeDevicesPerUser=50(constants.go)+
  AGENTHUB_MAX_CLOUD_EDGE_DEVICES env 覆盖(config_env.go 函数式,同
  RateLimitFailOpen 模式);未设/非法回默认(非法带 warn 日志),<= 0 关闭。
- repository:CountDevicesByUserAndType + DeviceExistsForUser(COUNT 现有
  devices 表,无迁移/无 schema 变更)+ sqlmock 测试。
- service:Register 在 UpsertDevice 前仅对 cloud_edge 判额——已存在同
  device_id 的同用户同型设备(upsert 命中自己)视为刷新不受限;新
  device_id 达上限返回新错误码。
- 错误码语义选 403 而非 429:这是「凭证有效但策略拒绝」的存量配额
  (codes.go 状态码原则 403 类别),不是时间窗限流,重试无意义,
  conventions.md 的 429 专属 rate_limited 且必须带 Retry-After。

测试:service(达上限拒绝/刷新不受限/用户间独立/env 覆盖与禁用/
desktop 不受影响)、handler(403 + envelope code 透传两条注册路由)、
config(默认/合法/非法回退)。同步:openapi 403/429 响应 + 配额描述,
05-deployment.md 配置面索引加 env 行。

Co-authored-by: Cursor <cursor@vectorcontrol.tech>

---------

Co-authored-by: DeliciousBuding <DeliciousBuding@users.noreply.github.com>
Co-authored-by: Cursor <cursor@vectorcontrol.tech>
DeliciousBuding added a commit that referenced this pull request Sep 3, 2026
…und-NN 8 处删除但保留 issue 与日期锚点、1 处畸形 Windows 路径夹具修正(16 文件净 0 行,历史暴露面已量化未清洗) (#2284)

* test(edge): 公开面脱敏:测试夹具中的个人名 Ding/ding 改为中性 Example/example

Co-authored-by: Cursor <cursor@vectorcontrol.tech>

* chore(edge): 清理测试注释中的 round-NN 内部编号(保留 issue 锚点)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>

* chore(hub): 注释去除 round-NN 内部编号(保留 #2185 锚点)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>

* docs(public): 文档移除 round-NN 内部编号(保留 issue/日期锚点)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>

* test(app): 公开面脱敏:夹具显示名 Ding 改 Alice,修正畸形 Windows 路径

Co-authored-by: Cursor <cursor@vectorcontrol.tech>

---------

Co-authored-by: DeliciousBuding <DeliciousBuding@users.noreply.github.com>
Co-authored-by: Cursor <cursor@vectorcontrol.tech>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant