Skip to content

fix(captcha): 补上 rAF 别名,并给 getter 探测的 rejected promise 加 sink - #45

Merged
TriDefender merged 1 commit into
TriDefender:masterfrom
LX2000WASD:fix/captcha-raf-and-rejection-noise
Sep 6, 2026
Merged

TriDefender merged 1 commit into
TriDefender:masterfrom
LX2000WASD:fix/captcha-raf-and-rejection-noise

Conversation

@LX2000WASD

Copy link
Copy Markdown
Contributor

背景

排查 v4.5.5 TUI 崩溃(ReferenceError: moveBy is not defined)时顺带发现的两处独立缺口。两者都只表现为静默劣化,不产生可见报错,因此一直没被注意到。

与其余三个 PR 互不依赖,可单独合入。


1. requestAnimationFrame / cancelAnimationFrame 移出 HOST_CRITICAL_GLOBALS

HOST_CRITICAL_GLOBALS 的用意是「别遮蔽宿主依赖的内建」。但 Bun 根本没有原生 rAF:

Bun globalThis.requestAnimationFrame → undefined
happy-dom window.requestAnimationFrame → function

所以跳过别名不是「保护宿主」,而是让它对 guest 完全不可解析。

feilin008 里有 9 处裸引用,只有 1 处带 typeof 保护:

464320  (n=12, requestAnimationFrame(function…      ← 无保护
464404  (e-=1, requestAnimationFrame(i))            ← 无保护
464498  u = typeof requestAnimationFrame            ← 有保护
464725  isNaN(!requestAnimationFrame/!request…      ← 无保护
…共 9 处

这些调用点落在 Promise executor 内,因此不会崩溃,只会打断指纹链,表现为求解成功率下降。实测在解析点装探针,一轮求解命中 24 次,其中来自 feilin008 的调用确认在 Promise 构造器内。

这与 2026-08-29 修 print 的那次是同型问题 —— 同一个跳过表、同样的静默劣化、同样的「Bun 宿主没有该全局,别名不遮蔽任何东西」。注释里已引用那次修复。


2. installNativeToString 探测 getter 时产生的 rejected promise 加 sink

maskObj 会调用 getter,以便给返回的函数也套上 native toString 伪装:

const v = desc.get.call(obj);
if (typeof v === "function") mask(v);

WHATWG stream 的 closed / ready 在接收者是原型而非实例时返回一个已拒绝的 promise。没人 await,于是每次求解稳定产出 4 条 unhandledRejection:

The 'closed' getter can only be used on a ReadableStreamBYOBReader
The 'closed' getter can only be used on a ReadableStreamDefaultReader
The 'closed' getter can only be used on a WritableStreamDefaultWriter
The 'ready'  getter can only be used on a WritableStreamDefaultWriter

探测结果本来就是丢弃的,加一个 .catch(() => {}) 即可。这类噪音会淹没真正需要关注的诊断信息。


验证

别名安装后 bare requestAnimationFrame → function
别名安装后 bare cancelAnimationFrame  → function
完整一轮求解的 unhandledRejection     → 0 条(改动前 4 条 stream getter 噪音)
  • bun x tsc --noEmit 0 错误
  • bun test 733 pass / 0 fail(上游基线 732,本 PR 新增 1 条 rAF 契约测试)

两处独立的求解器缺口,都只表现为静默劣化,不产生可见报错。

1) requestAnimationFrame / cancelAnimationFrame 移出 HOST_CRITICAL_GLOBALS

   这个跳过表的用意是"别遮蔽宿主依赖的内建",但 Bun 根本没有原生 rAF,所以跳过
   别名等于让它对 guest 不可解析。feilin008 里有 9 处裸引用,只有 1 处带 typeof
   保护,其余落在 Promise executor 内——因此它不会崩,只会把指纹链打断,表现为
   求解成功率下降。

   与 2026-08-29 修 `print` 的那次是同型问题(同样的跳过表、同样的静默劣化)。
   Bun 的宿主全局没有 rAF,别名到 window 的实现不遮蔽任何东西。

2) installNativeToString 探测 getter 时产生的 rejected promise 加 sink

   maskObj 会调用 getter 以便给返回的函数也套上 native toString 伪装。WHATWG
   stream 的 `closed`/`ready` 在接收者是原型而非实例时返回一个已拒绝的
   promise,没人 await,于是每次求解稳定产出 4 条 unhandledRejection:

     The 'closed' getter can only be used on a ReadableStreamBYOBReader
     The 'ready' getter can only be used on a WritableStreamDefaultWriter

   探测结果本来就是丢弃的,加一个 .catch(() => {}) 即可。这类噪音会淹没真正需要
   关注的诊断信息。

验证:别名安装后 bare requestAnimationFrame 解析为 function;完整一轮求解的
unhandledRejection 从 4 条 stream getter 噪音降为 0 条。

@TriDefender TriDefender left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已验证并合入:CI 绿 + 本地 worktree 复跑 tsc 0 错 / 733 pass(与声明一致)。bun -e 实证 Bun globalThis 无原生 rAF / cancelAnimationFrame,与 2026-08-29 print 修复同型的保真度缺口成立;getter 探测的 rejection sink 无争议。改动行级,风险≈0。

@TriDefender
TriDefender merged commit 021a50a into TriDefender:master Sep 6, 2026
1 check passed
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.

2 participants