This is an automated email from the ASF dual-hosted git repository. AlexStocks pushed a commit to branch fix/issue-3566-script-router-timeout in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
commit 5e1ee0b601d0583b12ddba8aa0ad364537e8a21f Author: alexstocks <[email protected]> AuthorDate: Thu Jul 30 13:55:50 2026 +0800 fix(router): enforce script-router execution timeout + activate review docs (#3566) A runaway/looping routing script (e.g. `while (true) {}`) executed via goja had no execution deadline, so it could block the routing goroutine forever — a denial of service. This is the 🔴 blocker called out in CODE_REVIEW_GUIDE.md §4.2.2. Changes: - cluster/router/script/instance/js_instance.go: wrap RunProgram with time.AfterFunc(defaultScriptTimeout = 500ms) that calls rt.Interrupt(...), plus `defer timer.Stop()`. initCallArgs already ClearInterrupt before each run, also clearing any stale interrupt. - cluster/router/script/instance/js_instance_test.go: TestRunScriptTimeout injects `while(true){}` and asserts the script is interrupted and returns an error within the watchdog instead of hanging forever. - CODE_REVIEW_GUIDE.md: add §4.2.2 (mandatory interrupt timeout for any external script execution), referenced by the code comment so the rule is not a dangling reference. - CODEOWNERS: activate review routing. NOTE: the @dubbo-go/* handles are placeholders — replace them with real team/personal handles before this file takes effect (GitHub will not route review until then). Fixes: #3566 Signed-off-by: alexstocks <[email protected]> --- CODEOWNERS | 27 +++ CODE_REVIEW_GUIDE.md | 270 +++++++++++++++++++++ cluster/router/script/instance/js_instance.go | 15 ++ cluster/router/script/instance/js_instance_test.go | 24 ++ 4 files changed, 336 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..495adeea4 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,27 @@ +# dubbo-go CODEOWNERS +# --------------------------------------------------------------------------- +# 作用:PR 触碰对应路径时自动请求以下 owner 审查(见 CODE_REVIEW_GUIDE.md §6.3)。 +# 注意:下方 @dubbo-go/xxx 为「团队/个人 handle 占位符」,请替换为真实的 +# GitHub team 名或 Committer 个人账号,否则 GitHub 不会路由审查。 +# 语法:<path> @owner1 @owner2 +# --------------------------------------------------------------------------- + +# 核心模块 +/cluster/ @dubbo-go/cluster-maintainers +/protocol/ @dubbo-go/protocol-maintainers +/registry/ @dubbo-go/registry-maintainers +/config_center/ @dubbo-go/config-maintainers +/common/ @dubbo-go/core-maintainers +/global/ @dubbo-go/core-maintainers +/metadata/ @dubbo-go/metadata-maintainers +/remoting/ @dubbo-go/remoting-maintainers +/filter/ @dubbo-go/filter-maintainers +/metrics/ @dubbo-go/observability-maintainers +/otel/ @dubbo-go/observability-maintainers + +# 工具与文档 +/tools/ @dubbo-go/tooling-maintainers +/doc/ @dubbo-go/docs-maintainers + +# 兜底:未被上述路径覆盖的所有文件 +* @dubbo-go/committers diff --git a/CODE_REVIEW_GUIDE.md b/CODE_REVIEW_GUIDE.md new file mode 100644 index 000000000..3579d6d77 --- /dev/null +++ b/CODE_REVIEW_GUIDE.md @@ -0,0 +1,270 @@ +# dubbo-go 代码审查指南(Code Review Standard & Process) + +> 适用对象:`dubbo.apache.org/dubbo-go/v3`(本仓库) +> 维护者:Committer / Maintainer 团队 +> 版本:v1.0 · 2026-07-30 +> 配套文档: [`CONTRIBUTING.md`](./CONTRIBUTING.md) · [`security-review-2026-07-30.md`](./security-review-2026-07-30.md) + +--- + +## 0. 为什么需要这份指南 + +dubbo-go 是一个**面向网络的 Go RPC 框架**,被上游业务作为基础库消费。它的代码质量直接影响成百上千个微服务的稳定性、安全性和可维护性。与业务代码不同,这里有两个特殊性: + +1. **并发是默认状态**:连接池(getty)、注册中心监听、配置中心订阅、每请求 goroutine,几乎每一行都在并发环境里跑。 +2. **信任边界复杂**:配置中心(ZK/Nacos/Apollo/File)、反序列化(Hessian2)、脚本路由(goja JS)、metrics/probe 端点、TLS 都是典型的攻击面。 + +我们在 [`security-review-2026-07-30.md`](./security-review-2026-07-30.md) 中已经发现若干真实风险(脚本路由无执行超时、metrics 端点默认无认证、TLS 未设 `MinVersion`、`exec` 拼 `$USER`、File 配置中心路径可穿越)。**自动化工具(golangci / CodeQL)无法替代人对这些语义风险的判断**,因此必须建立一套可执行的人工审查标准。 + +本指南目标:让任何一次 PR 的审查结果**可预期、可对齐、可复盘**,而不是依赖 reviewer 的个人经验。 + +--- + +## 1. 角色与职责 + +| 角色 | 职责 | +|------|------| +| **Author(作者)** | 保证 PR 自测通过、描述清晰、关联 issue、不夹带无关改动;对 reviewer 的疑问及时响应。 | +| **Reviewer(审查者)** | 至少 1 名,对改动的正确性、安全性、可维护性负责;按本指南清单逐项确认;给出明确的 `Approve` / `Request changes`。 | +| **Maintainer / Approver** | 拥有合并权限的 Committer;负责最终把关**兼容性破坏**、跨模块影响、与发行版本(release 分支)的一致性;可要求额外批准。 | +| **CI(自动门禁)** | 跑 fmt / test / lint / CodeQL / codecov,作为**合并的最低门槛**,不等于人工审查。 | + +> ⚠️ 当前仓库**没有 `CODEOWNERS` 文件**。强烈建议建立(见 §6.3),否则 reviewer 分配完全靠手动,容易出现「无人审」或「不懂的人审」。 + +--- + +## 2. 审查流程 + +``` +┌────────────┐ ┌──────────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ +│ 1.作者自检 │ → │ 2.提交 PR(门禁) │ → │ 3.自动 CI │ → │ 4.人工审查 │ → │ 5.合并 │ +└────────────┘ └──────────────────┘ └──────────────┘ └──────────────┘ └──────────┘ + 本地 pre-push 标题/描述/issue/签名 fmt+test+lint ≥1 Approve + squash/merge + 跑全量清单 见 CONTRIBUTING +CodeQL+codecov 无未解决 Blocker 回退策略 +``` + +### 2.1 作者自检(提交前) +- 本地执行(不要等 CI 报错): + ```bash + make check-fmt && make test && make lint + go test -race ./... # ⚠️ 当前 CI 未开启 -race,框架级改动务必本地加 + ``` +- 对照 §4 清单做一遍自我审查,尤其是并发安全与安全性维度。 +- 一个 PR **只解决一件事**(一个 issue);重构与功能修复分开提。 + +### 2.2 提交 PR +遵循现有 [`CONTRIBUTING.md`](./CONTRIBUTING.md): +- 标题:`<type>: 一句话摘要`,backport 加 `[3.0]` 前缀。 +- 描述必须关联 issue:`Fixes: #xxxx`。 +- 提交需 `Signed-off-by`(提交时 `git commit -s`)。 +- PR 描述用本指南 §7 的模板,明确:改动动机、影响面、测试方式、兼容性说明。 + +### 2.3 自动门禁(CI) +满足以下条件**才允许进入人工审查**: +- License header 检查通过(skywalking-eyes)。 +- `make check-fmt` 通过(gofmt / goimports)。 +- `make test` 全绿,codecov 不下降(或下降有明确理由并经 Maintainer 同意)。 +- `make lint`(golangci-lint)零告警。 +- CodeQL 扫描无新增 high/critical。 + +> 现状提示:CI 当前**未跑 `-race`、未启用 `gosec`、未接 `govulncheck`**。这些由人工审查在 §4.2 / §6 中补足。 + +### 2.4 人工审查 +- **最少批准数**:普通 PR ≥ 1 名 Reviewer;涉及 `protocol/`、`cluster/`、`registry/`、`config_center/`、`common/`、`global/` 等核心模块的改动需 ≥ 2 名(其中至少 1 名为对应模块 Maintainer)。 +- **分配**:有 `CODEOWNERS` 时按文件路由;没有时由作者 @ 对应模块 owner。 +- **SLA(建议)**:Reviewer 在 **1 个工作日内**给出首轮反馈;作者 **2 个工作日内**响应;阻塞性问题 24h 内处理。跨时区可放宽,但需在 PR 内说明。 + +### 2.5 评论分级与处理 +所有评论必须带严重级别标记(与本指南 §5 一致): + +| 标记 | 含义 | 作者是否必须处理 | +|------|------|------------------| +| 🔴 Blocker | 合并前必须修复(安全/正确性/数据风险/破坏性变更) | 必须,否则不可合并 | +| 🟡 Suggestion | 应当修复(可维护性/性能/测试缺口) | 应修复;若坚持不改,需在 PR 内说明理由并经 Reviewer 认可 | +| 💭 Nit | 锦上添花(命名/文档/风格细节) | 可选择性处理 | + +### 2.6 合并与回退 +- 优先 **squash merge**,保持 `main` 线性、commit message 干净。 +- 合并前确认所有 🔴 已清零、🟡 已达成一致。 +- 若合并后发现引入了回归,Maintainer 有权 `revert` 并重新开 issue 跟进,不阻塞主干。 + +--- + +## 3. 严重级别定义(总览) + +| 级别 | 典型场景 | 处理要求 | +|------|----------|----------| +| 🔴 **Blocker** | 安全漏洞、数据丢失/污染、竞态导致崩溃、破坏公共 API、缺关键错误分支 | 合并前必须修复 | +| 🟡 **Suggestion** | 缺输入校验、命名误导、无重要路径测试、N+1/多余分配、代码重复 | 应修复或书面豁免 | +| 💭 **Nit** | 风格不一致、次要命名、文档缺失、更优写法 | 可选 | + +--- + +## 4. 代码审查清单(核心) + +> 用法:Reviewer 逐条过,命中项在 PR 评论里引用对应条目编号(如 `§4.1.3`)。 + +### 4.1 正确性与并发安全(Go 框架级重点) + +- **4.1.1 goroutine 泄漏**:新启的 goroutine 必须有明确的退出路径(context 取消 / channel close / `errgroup`)。尤其连接监听、watch、定时任务。 +- **4.1.2 锁的正确性**:`sync.Mutex`/`RWMutex` 不要被值拷贝(`go vet -copylocks` 已覆盖,但注意结构体含锁被 `append`/`return` 拷贝);读多写少用 `RWMutex`;避免锁内做 IO / 远程调用导致长持锁。 +- **4.1.3 map / slice 并发**:非 `sync.Map` 的 map 绝不在多 goroutine 下无锁读写;注意 `getty`、注册表、缓存等共享容器。 +- **4.1.4 channel 安全**:发送端可能无人接收时要有 `select { case ch <- v: default: }` 或超时;**只由发送方 close**,避免重复 close / close 已 close。 +- **4.1.5 context 传递**:网络调用、RPC 调用必须透传 `ctx` 并尊重取消与超时;不要把 `context.Background()` 一路透传到底层网络栈。 +- **4.1.6 错误不吞**:不要 `_ = fn()` 吞掉关键错误;返回错误时用 `fmt.Errorf("...: %w", err)` 保留链路;注意「返回非 nil error 但返回了非 nil 接口+typed nil」的经典坑。 +- **4.1.7 nil 安全**:接口 nil vs 具体类型 nil 的区别;解引用前判空;`defer` 中访问可能已关闭的资源要判空。 +- **4.1.8 panic 边界**:框架对外暴露的入口(协议解码、filter 链、回调)应考虑 `recover`,避免单请求 panic 拖垮整个处理 goroutine;内部逻辑不滥用 recover 掩盖 bug。 +- **4.1.9 初始化顺序 / `sync.Once`**:扩展点(SPI:protocol/router/loadbalance/registry/filter)的注册与懒初始化要幂等;注意 `init()` 副作用与循环依赖。 + +### 4.2 安全性(信任边界) + +> 结合安全评估报告中的真实发现,逐条核对。 + +- **4.2.1 配置中心信任边界** 🔴:来自 ZK/Nacos/Apollo/File 的内容视为**不可完全信任**。路由规则、动态配置、脚本**禁止在运行时从不可信源加载未校验内容**;配置中心侧应启用 ACL / 强认证(见报告 §二.1)。 +- **4.2.2 脚本路由执行超时** 🔴:任何执行外部脚本(goja JS)的地方**必须设置中断超时**(`js_instance.go` 当前 `ClearInterrupt` 却从未设 deadline → `while(true){}` 可永久阻塞 → DoS)。强制: + ```go + timer := time.AfterFunc(500*time.Millisecond, func() { rt.Interrupt("timeout") }) + defer timer.Stop() + ``` +- **4.2.3 反序列化(Hessian2)** 🔴:保持 `dubbo-go-hessian2` 最新;谨慎启用 generic 服务(暴露面最大);关注其 Security Advisory。Go 实现不会自动实例化 Java gadget 类,但仍需版本受控。 +- **4.2.4 metrics / probe 端点暴露面** 🔴:默认监听 `0.0.0.0` 且无认证会泄露拓扑、成为侦察/SSRF 跳板。合并前确认:绑定 `127.0.0.1` 或加 mTLS/Bearer 鉴权或仅内网 + 网络隔离。 +- **4.2.5 TLS 配置** 🟡:显式设置 `MinVersion: tls.VersionTLS12`(建议 TLS1.3);不要出现 `InsecureSkipVerify: true`。 +- **4.2.6 命令注入面** 🟡:禁止 `exec.Command("sh","-c",...)` 拼接环境变量(如 `config_center/file/impl.go` 的 `eval echo ~$USER`),改用 `os.UserHomeDir()` 等原生 API。 +- **4.2.7 路径穿越** 🟡:文件型配置中心 / 静态资源路径拼接用 `filepath.Clean` 并校验结果仍在 root 内,防止 `../` 逃逸。 +- **4.2.8 密钥与凭证** 🔴:禁止硬编码生产口令/Token;密钥只从配置或环境变量读取;测试占位符不得进非 `_test.go`。 +- **4.2.9 依赖漏洞** 🔴:新增/升级依赖前 `govulncheck`(见 §6.2);`go.mod` 间接依赖也要关注。 + +### 4.3 性能 + +- **4.3.1 分配与 GC 压力**:热路径(编解码、filter 链、负载均衡选择)避免不必要的堆分配;复用 buffer / `sync.Pool`;大结构体传指针而非值。 +- **4.3.2 N+1 / 批量**:注册中心、配置拉取、批量调用避免逐条网络往返;支持批量与异步。 +- **4.3.3 锁粒度**:高频读路径考虑 `atomic`(项目已用 `go.uber.org/atomic`)或 `RWMutex`,避免全局大锁成为瓶颈。 +- **4.3.4 context 超时与重试**:网络调用必须有超时;重试用指数退避(`cenkalti/backoff` 已在依赖中),避免重试风暴。 +- **4.3.5 连接/资源池**:连接池上限、空闲回收、泄漏检测;关闭时优雅 `Close()`。 + +### 4.4 可维护性与可读性 + +- **4.4.1 命名**:包级导出标识符自解释;避免缩写歧义;类型名不带 `Impl`/`Manager` 噪音(除非必要)。 +- **4.4.2 函数复杂度**:golangci 已设 `gocyclo=10`;超复杂逻辑拆分,优先早返回。 +- **4.4.3 魔法数字 / 常量**:超时、端口、上限等提取为具名常量。 +- **4.4.4 注释**:导出的类型/函数必须有 godoc;「为什么」比「做什么」重要;复杂的并发/协议逻辑注释说明不变量。 +- **4.4.5 重复代码**:跨模块重复超过 ~100 行(golangci `dupl.threshold=100`)应抽取。 +- **4.4.6 日志**:统一走 `logutils.Log`(golangci `depguard` 已禁止直接用 logrus);日志级别恰当,避免打印敏感字段(PII / 凭证 / attachment)。 + +### 4.5 接口与兼容性(框架级关键) + +- **4.5.1 公共 API 不破坏** 🔴:导出函数/方法签名、接口、结构体字段、配置项 key 的变更视为破坏性。需要破坏时:走 `deprecated` 标记 + 至少一个大版本过渡,并在 PR 描述说明迁移路径。 +- **4.5.2 接口稳定性**:被外部实现的接口(filter / router / loadbalance / registry / protocol)新增方法要评估现有实现是否编译失败。 +- **4.5.3 默认值与行为**:配置默认值的改变会影响所有用户,需显式标注并在 CHANGELOG 记录。 +- **4.5.4 版本分支一致性**:backport(release 分支)改动需确认 `main` 上是否已修复,避免再次引入。 + +### 4.6 测试与可观测性 + +- **4.6.1 关键路径覆盖**:新逻辑 / bug 修复必须有测试;并发修复**必须带 `-race` 测试**。 +- **4.6.2 表驱动测试**:Go 惯例优先表驱动;边界值(空、超长、超时会话)要覆盖。 +- **4.6.3 集成测试**:涉及注册中心 / 协议 / 配置中心的改动,尽量落到 `integrate_test.sh` 能覆盖的路径。 +- **4.6.4 可观测性**:新增关键路径应有 metric / trace / log,便于线上排障;不引入新的明文敏感信息暴露。 +- **4.6.5 Mock 合理性**:外部依赖(zk/nacos/网络)用 mock;不要为通过测试而放松真实不变量。 + +### 4.7 Go 专项 + +- **4.7.1 error wrapping**:用 `%w` 而非 `%v` 以便 `errors.Is/As`。 +- **4.7.2 资源释放**:`defer` 关闭连接 / 文件 / 取消;`defer` 在循环里注意及时释放而非等到函数退出。 +- **4.7.3 泛型 / 接口取舍**:泛型仅在有真实复用收益时使用,避免过度抽象。 +- **4.7.4 不受控的 `init()`**:避免 `init()` 产生副作用或失败导致包加载崩溃。 +- **4.7.5 lint 基线**:遵守 `.golangci.yml`(行宽 140、复杂度 10 等),新增代码不应产生新告警。 + +--- + +## 5. 问题分级速查(Reviewer 决策树) + +``` +是否会导致安全漏洞 / 数据丢失 / 生产崩溃 / 破坏公共 API? + ├─ 是 ───────────────────────► 🔴 Blocker(必须修) + └─ 否 ── 是否影响可维护性/性能/测试/安全加固? + ├─ 是 ────────► 🟡 Suggestion(应修或书面豁免) + └─ 否 ────────► 💭 Nit(可选) +``` + +评论写法示例: +``` +🔴 **Security: 脚本路由无执行超时** — `cluster/router/script/instance/js_instance.go:202` +为何:恶意脚本 `while(true){}` 会永久阻塞处理 goroutine → DoS(见安全评估报告 §二.1)。 +建议:加 `time.AfterFunc` 中断,见 §4.2.2。 +``` + +--- + +## 6. 工具链与自动化(对齐现有,补齐缺口) + +### 6.1 现有(保留) +- CI:`make check-fmt` / `make test` / `make lint` + codecov。 +- `golangci-lint`:govet / ineffassign / misspell / staticcheck / unused / testifylint;`gocyclo=10`、`lll=140`、`depguard`(禁直接 logrus)。 +- CodeQL(Go)安全扫描。 + +### 6.2 工具链落地状态(已补齐) +| 工具 | 作用 | 状态 | 落地方式 | +|------|------|------|----------| +| `go test -race` | 检测数据竞争 | ✅ 已接入 | CI 新增 `make test-race` 步骤;`Makefile` 新增 `test-race` target | +| `gosec` | 安全 lint(注入/明文/弱加密/子进程/路径穿越) | ✅ 已启用 | `.golangci.yml` 加入 `gosec`;排除高噪音 G104 与有意的弱加密(G401),首轮聚焦真实安全问题 | +| `govulncheck` | 依赖已知漏洞 | ✅ 已接入 | CI 新增 `make vulncheck` 步骤;`Makefile` 新增 `vulncheck` target | +| `CODEOWNERS` | reviewer 路由 | ✅ 已建立 | 仓库根 `CODEOWNERS`(按模块分配,**owner 占位符需替换为真实 handle**) | +| `staticcheck` SA/ST 告警 | 已部分排除 `SA1019/ST1001` | 保留 | 仅对 legacy 路径排除,新代码不豁免 | + +> ⚠️ **滚动启用提示**:`gosec` 与 `-race` 在存量代码上可能立即暴露历史问题,导致 CI 首轮变红。这是预期内的「技术债显影」,建议: +> 1. 先本地跑 `make lint` 与 `make test-race` 拿到清单; +> 2. 历史问题单独开 issue 分批清理,新代码严格零新增; +> 3. 若不想一次性阻塞全部 PR,可把 CI 中「Unit Test (race detector)」步骤临时加 `continue-on-error: true`,待清理完再移除。 + +### 6.3 CODEOWNERS 模板(请按实际 owner 填充) +``` +# 模块负责人(示例,需替换为真实 GitHub 账号) +/cluster/ @dubbo-go/cluster-maintainers +/protocol/ @dubbo-go/protocol-maintainers +/registry/ @dubbo-go/registry-maintainers +/config_center/ @dubbo-go/config-maintainers +/common/ @dubbo-go/core-maintainers +/global/ @dubbo-go/core-maintainers +/metrics/ @dubbo-go/observability-maintainers +/filter/ @dubbo-go/filter-maintainers +* @dubbo-go/committers +``` + +--- + +## 7. 附录:PR 描述模板 + +```markdown +## 动机(Why) +<!-- 关联 issue #xxxx;解决什么问题 --> + +## 改动说明(What) +<!-- 一句话 + 关键文件;若是 backport 标注目标分支 --> + +## 影响面 / 兼容性 +<!-- 是否破坏公共 API / 默认值 / 行为;是否需要 CHANGELOG --> + +## 安全性自查 +<!-- 是否触及配置中心/反序列化/脚本/metrics/TLS/密钥(对照 §4.2) --> + +## 测试 +<!-- 新增/修改的测试;是否跑过 -race;集成测试覆盖 --> + +## 自查清单 +- [ ] make check-fmt / test / lint 通过 +- [ ] 无新增 Blocker 级问题(已对照 §4) +- [ ] 文档/godoc 已更新(如适用) +``` + +--- + +## 8. 度量与持续改进 + +- **审查时效**:跟踪首响时间 / 合并周期,目标逐步收敛。 +- **缺陷逃逸率**:合并后由 issue 反推「本应在审查发现却漏掉」的问题,回灌到 §4 清单。 +- **清单迭代**:每季度根据安全评估、线上事故、CodeQL/govulncheck 新发现修订本指南与 §4 清单。 +- **知识沉淀**:典型 bad case(如脚本路由 DoS)写成复盘,链接进对应清单条目。 + +--- + +> 本指南是**活文档**:它服务于「让 dubbo-go 的每次合并都更可信」,而非增加流程负担。当某条规则开始制造无谓摩擦时,优先修订规则,而不是让工程师绕过它。 diff --git a/cluster/router/script/instance/js_instance.go b/cluster/router/script/instance/js_instance.go index 58e51693b..fddde1f05 100644 --- a/cluster/router/script/instance/js_instance.go +++ b/cluster/router/script/instance/js_instance.go @@ -23,6 +23,7 @@ import ( "reflect" "sync" "sync/atomic" + "time" ) import ( @@ -38,6 +39,12 @@ const ( jsScriptPrefix = "\n" + jsScriptResultName + ` = ` ) +// defaultScriptTimeout bounds the execution of a user-provided routing script. +// Without it a runaway/looping script (e.g. `while (true) {}`) would block the +// routing goroutine indefinitely, causing a denial of service. +// See CODE_REVIEW_GUIDE.md §4.2.2. +const defaultScriptTimeout = 500 * time.Millisecond + type jsInstances struct { insPool *sync.Pool // store *goja.runtime pgLock sync.RWMutex @@ -199,6 +206,14 @@ func (j jsInstance) runScript(pg *goja.Program) (res any, err error) { } } }(&res, &err) + // Hardening: interrupt the script if it exceeds the timeout so a runaway + // script cannot block this goroutine forever (DoS). initCallArgs calls + // ClearInterrupt before each run, which also clears any stale interrupt. + timer := time.AfterFunc(defaultScriptTimeout, func() { + j.rt.Interrupt("script execution exceeded timeout") + }) + defer timer.Stop() + res, err = j.rt.RunProgram(pg) return res, err } diff --git a/cluster/router/script/instance/js_instance_test.go b/cluster/router/script/instance/js_instance_test.go index 2ea812fa8..f8ac1c990 100644 --- a/cluster/router/script/instance/js_instance_test.go +++ b/cluster/router/script/instance/js_instance_test.go @@ -23,6 +23,7 @@ import ( "reflect" "sync" "testing" + "time" ) import ( @@ -716,3 +717,26 @@ func TestRunScriptInPanic(t *testing.T) { ` wontPanic(scriptCallWrongArgs3) } + +// TestRunScriptTimeout guards against a runaway/looping routing script blocking +// the routing goroutine indefinitely (denial of service). +// See CODE_REVIEW_GUIDE.md §4.2.2 and apache/dubbo-go#3566. +func TestRunScriptTimeout(t *testing.T) { + j := newJsInstance() + pg, err := goja.Compile("", "while (true) {}", true) + testify_require.NoError(t, err) + + done := make(chan error, 1) + go func() { + _, e := j.runScript(pg) + done <- e + }() + + select { + case e := <-done: + // The interrupt must surface as an error rather than hanging forever. + testify_require.Error(t, e, "runaway script must be interrupted and return an error") + case <-time.After(3 * time.Second): + t.Fatal("runScript blocked indefinitely; script execution timeout is not enforced") + } +}
