Files
SnowLuma/packages/common/tests/uin.test.ts
DrSmoothl 3932958c39 fix(bridge,common,onebot): reject phantom hook UINs + never crash logging (#162)
注入的竞态窗口里,native hook 偶发上报 13 位垃圾 UIN(时间戳型,读到 QQ 尚未落定
的内存)。TS 层防御缺失,导致两个连锁问题:

1) 幽灵账号:isRealUin 只查「纯数字且 ≥5 位」、无上限,垃圾值通过后建会话并把
   config/onebot_<垃圾>.json 落盘 → OneBot 节点配置里出现随机幽灵账号(跨重启残留)。
2) 进程崩溃:logger 彩色路径 ' '.repeat(SLOT - tagLen),UIN tag(含方括号)超宽 →
   负数 → RangeError: Invalid count value → uncaughtException。

跨平台(Win DLL / Linux .so 同逻辑)+「更新 QQ 后消失」都印证是 native 读到那块内存
的偶发垃圾;根修在 SnowLumaNative,此处补齐 TS 防御:

- 抽出唯一的 @snowluma/common/uin::isRealUin = /^\d{5,10}$/(真实 QQ ≤ uint32 →
  10 位)。删除 bridge/core 两处重复副本(重复正是这次漏加上限的温床)。等价旧逻辑,
  仅新增拒 11+ 位。
- logger.ts 加 Math.max(0, …),日志永不崩进程(独立健壮化)。
- 收紧 WebUI UIN_REGEX /^\d{5,12}$/ → /^\d{5,10}$/ 保持一致。
- 启动时 cleanupInvalidPerUinConfigs() 清掉已落盘的幽灵 onebot_<uin>.json(只删
  UIN 不合法的,绝不碰合法账号/全局配置)。

测试:common +8(uin 6 / logger 2)、onebot +3(cleanup)。typecheck 12 包干净;
common 58 / onebot 519 / core 471 / bridge 73 全绿。对抗性 Agent 逐输入核验等价性、
三层 gate 堵死幽灵入口、cleanup 安全时序,无回归。
2026-07-01 20:37:02 +08:00

41 lines
1.4 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { isRealUin } from '../src/uin';
describe('isRealUin', () => {
it('accepts real QQ UINs (5..10 digits)', () => {
expect(isRealUin('10001')).toBe(true); // 5-digit floor
expect(isRealUin('123456789')).toBe(true);
expect(isRealUin('4294967295')).toBe(true); // uint32 max, 10 digits
});
it('rejects the empty string and "0"', () => {
expect(isRealUin('')).toBe(false);
expect(isRealUin('0')).toBe(false);
});
it('rejects too-short UINs (< 5 digits)', () => {
expect(isRealUin('1')).toBe(false);
expect(isRealUin('1234')).toBe(false);
});
it('rejects the garbage timestamp-shaped UINs from issue #162 (11+ digits)', () => {
expect(isRealUin('1701414379536')).toBe(false); // 13-digit ms timestamp
expect(isRealUin('1614019567632')).toBe(false);
expect(isRealUin('2014024115056')).toBe(false);
expect(isRealUin('12345678901')).toBe(false); // 11 digits — first rejected width
});
it('rejects non-digit strings (path traversal / injection guards)', () => {
expect(isRealUin('12345a')).toBe(false);
expect(isRealUin('12 345')).toBe(false);
expect(isRealUin('../etc')).toBe(false);
expect(isRealUin('123456\n')).toBe(false);
});
it('guarantees a BigInt-safe pure-digit string on accept', () => {
const uin = '123456789';
expect(isRealUin(uin)).toBe(true);
expect(() => BigInt(uin)).not.toThrow();
});
});