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

ghost commented Sep 6, 2026

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

Reviewers

Couldn't load reviewers.

Assignees

Couldn't load assignees.