Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changeset/control-byte-gate-scans-del.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
---

chore(scripts): `check:nul-bytes` 的扫描面纳入 DEL(0x7f),并转义 login.ts / register.ts 各一枚裸字节 (#5460)

#5157 把这道门禁的扫描面从 NUL 扩到「C0 控制字符集去掉 tab/LF/CR」。**DEL(0x7f)不在那个区间里** —— 它不是 C0 控制符,而是单独坐在 ASCII 表末尾、印刷字符区之后的一枚控制字符,所以一个用连续区间表达的集合根本够不到它。

这个缺口是偶然而非深思熟虑,证据是剩下那两枚裸字节被发现的位置:#5157 转义了 CLI 密码输入里的一枚裸 0x03,而在 `login.ts` 和 `register.ts` **同一个 switch 里、往下九行**,各躺着一枚当 Backspace 键值的裸 0x7f,原封不动。转义后的 case 与仍是裸字节的 case 并排,前者读得出是个按键,后者渲染为空、读起来是 `case '':` —— 一个空串 case;区分二者的只有 ASCII 当年把这个字节放在了 0x1f 的哪一侧。

#5157 论证 C0 的三条危害,逐条原样落在 0x7f 上:同样渲染为空、同样两种拼写都搜不到(既 grep 不到 `\u007f` 这段文本,也没法把那个字节敲进搜索框)、同样出自「作者正在写关于这个字节的内容时,编辑工具把转义落成真字节」这一事故源 —— 而**该事故源不挑字节值**。本单 issue 正文的第一版自己就踩了这个坑(写着写着落进两枚真的 0x03),实施本 PR 的过程中又复现两次:一次落进临时脚本,一次落进本脚本头部那段正在描述该事故的注释里。多数语言对「控制字符」的定义(C 的 `iscntrl`、正则的 `\p{Cc}` 类)也都把 0x7f 算进去,「C0」是个更窄的读法。

扫描面现在是 `[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]`。

**二进制判据同步扩面,而且这一步同样是承重的。** 判据是「剔除全部扫描字节后整文件 UTF-8 严格解码」,现在 0x7f 也在剔除之列。理由与 #5157 对 0x01 的论证同构:`E4 B8 7F AD` 是「中」被塞进一枚 0x7f,只剔 C0 的话它解码失败 → 文件被判二进制 → 跳过 → **那枚 0x7f 成了自己的不在场证明**。自测里把这条钉住了:回退扩面后,该样本文件立刻从「文本」掉进「二进制」(断言 `exactly the 2 binary assets skip` 报 `got 3`)。反方向不会出错:扫描集全部 `<= 0x7f`,而合法 UTF-8 多字节序列只由 `>= 0x80` 的字节构成。

**实测全部 5456 个受追踪常规文件:扩面前后文本/二进制判定零变化**(仍是 4 个 PNG + 1 个 ICO 跳过)。那 4 个 PNG 和 1 个 ICO 里本就含大量裸 0x7f(其中一个 1317 枚),不受影响 —— 它们是整文件解码失败才判的二进制,与任何单个字节无关。

**行为不变,不发版。** `'\u007f'` 与那枚裸字节在运行时逐字节相同,两个 switch 的 Backspace 分支照旧命中。

`--self-test` 断言数 34 → 48。新增断言把两个方向都钉在代码旁边:正向(裸 0x7f 判红、报错报出 0x7f 与 `\u007f` 处方、多字节序列里的 0x7f 判红),反向(两个新样本**整个文件都不含 C0 字节**,所以前一版 C0-only 的扫描确实无事可做),外加一条「转义后的拼写保持绿」—— 门禁教作者写转义,那这条处方本身必须可测,否则只证明了什么会被拒、没证明照做会被接受。原先那条**故意**把 DEL 钉在扫描面之外的断言(`'tab / CR / LF / DEL are outside the scanned set and stay green'`)按裁定反转:它当年是用来说明这条边界是选出来的而不是漏掉的,这次边界被重新选择,选到了另一边。

脚本名与 `pnpm check:nul-bytes` 命令名依旧不变(理由见脚本头);语义变化仍写在脚本头、报错文案和 CI 步骤三处。工具链改动,不发版。
14 changes: 11 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@ jobs:
- name: Slot-lookup ratchet
run: pnpm check:slot-lookup

# Raw control-byte guard (#3127 / #4890 / #5157). Scans every tracked TEXT
# file for a raw C0 control byte — 0x00-0x08, 0x0b, 0x0c, 0x0e-0x1f, i.e.
# everything except tab/LF/CR. Two distinct harms, one gate:
# Raw control-byte guard (#3127 / #4890 / #5157 / #5460). Scans every
# tracked TEXT file for a raw ASCII control byte — 0x00-0x08, 0x0b, 0x0c,
# 0x0e-0x1f and 0x7f, i.e. everything except tab/LF/CR. Two distinct
# harms, one gate:
# • A literal U+0000 makes grep/ripgrep treat the whole file as binary and
# silently return ZERO matches — the file drops out of code search and
# out of every grep-based lint, with no error saying so. Nothing else
Expand All @@ -92,6 +93,13 @@ jobs:
# Four tracked source files carried those past the NUL-only gate until
# #5157 widened the scan surface; PR #5140 is the case that found it,
# when a 0x01 sitting 14 bytes from a caught NUL went unfixed.
# • DEL (0x7f) is in the set for the same reason, added by #5460. It is
# not a C0 control — it sits alone past the printable range — so the
# C0-shaped set could not reach it, and two raw specimens survived in
# the CLI's password prompts nine lines below a 0x03 #5157 had just
# escaped, reading as `case ''`. The set is drawn by the accident
# source (a tool materialising an escape into its byte), and that
# source does not pick byte values.
# The command name stays `check:nul-bytes` for continuity — see the script's
# header for why. Authors must write the unicode escape instead of the byte.
- name: Raw control-byte guard
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async function promptPassword(promptText: string): Promise<string> {
cleanup();
resolve(chars.join(''));
break;
case '': // Backspace
case '\u007f': // Backspace
if (chars.length > 0) {
chars.pop();
process.stdout.clearLine(0);
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async function promptPassword(promptText: string): Promise<string> {
cleanup();
resolve(chars.join(''));
break;
case '': // Backspace
case '\u007f': // Backspace
if (chars.length > 0) {
chars.pop();
process.stdout.clearLine(0);
Expand Down
Loading
Loading