chore(privacy): 源码与测试里的真机常量脱敏(机器名 / 真机内网 IP)

路线图 §七 序 7「源码真机常量注入化」的脱敏部分。

## 改了什么
- apps/agent/src/capabilities/network.ts:注释里的真机机器名 → 泛化为「机器名+纯数字」
  (保留"为什么不能拼 USERDOMAIN/USERNAME"这个教训本身,去掉具体机器名)
- scripts/rescue-network.ps1:.EXAMPLE 里的真机内网地址 → 文档示例段 192.168.1.100;
  网卡名示例由中文默认名改为 'Ethernet'(既避免像真机、又更通用)
- packages/protocol/src/acl.ts:注释里的真机 Mac 地址 → 192.168.1.100
- tests/unit×4:测试数据里的真机 IP 统一换成 192.168.1.x 示例段

## 过程中的两个教训(都记在代码注释里了)
1. **正则转义形态漏替换**:断言里写的是 `192\.168\.0\.88`(转义点),第一轮只替换了
   裸形态 `192.168.0.`,导致脚本值变了、断言没变 → 3 个测试红。
2. **机械替换会破坏「跨网段」这类语义测试**:acl 的 `/24 网段匹配` 用例里有一条
   本意是「**不同网段不匹配**」,替换后两侧变成同段 → 断言必红。已改回不同网段
   并在注释里标注原因,防止下次再被批量替换破坏。

## 验证
单测 269 全绿(复跑);e2e 30 全绿。
This commit is contained in:
jkooe committed 2026-10-10 00:39:28 +08:00
1 parent 74d878a1c5
commit 2cb0ed148e
6 files changed
+33 -31

No files matched your search

+1 -1
View File
@@ -93,7 +93,7 @@ export function psLit(v: string): string {
*
* ⚠️ 两个真机教训都固化在这里:
* 1) **账号名必须用 WindowsIdentity 取**,不要用 "$env:USERDOMAIN\$env:USERNAME" 拼 ——
* 在 TS 模板串里 `\$` 会被当转义吃掉反斜杠,拼出「机器名<user>」这种不可解析的账号,
* 在 TS 模板串里 `\$` 会被当转义吃掉反斜杠,拼出「机器名+纯数字」这种不可解析的账号,
* Register-ScheduledTask 报 HRESULT 0x80070534「No mapping between account names...」。
* 2) 复用同一实现:此前回滚/延时两处各写一份,改了一处漏了另一处(真机踩过)。
*/
+1 -1
View File
@@ -31,7 +31,7 @@
.EXAMPLE
# 恢复为原静态地址(最常用)
powershell -ExecutionPolicy Bypass -File rescue-network.ps1 `
-Interface 'Ethernet' -Ip 192.168.1.100 -PrefixLength 24 -Gateway 192.168.0.1
-Interface 'Ethernet' -Ip 192.168.1.100 -PrefixLength 24 -Gateway 192.168.1.1
.EXAMPLE
# 切回 DHCP(家里路由器会重新分配地址)
+21 -19
View File
@@ -12,13 +12,13 @@ import {
// ---------- B3:对端地址规范化(真机踩坑回归) ----------
test('normalizeIp:IPv4-mapped IPv6 归一为 IPv4(真机坑)', () => {
assert.equal(normalizeIp('::ffff:192.168.1.100'), '192.168.1.100');
assert.equal(normalizeIp('::FFFF:192.168.1.100'), '192.168.1.100');
assert.equal(normalizeIp('::ffff:192.168.1.85'), '192.168.1.85');
assert.equal(normalizeIp('::FFFF:192.168.1.85'), '192.168.1.85');
});
test('normalizeIp:IPv4:port 去端口;纯 IP 原样', () => {
assert.equal(normalizeIp('192.168.1.100:51234'), '192.168.1.100');
assert.equal(normalizeIp('192.168.1.100'), '192.168.1.100');
assert.equal(normalizeIp('192.168.1.85:51234'), '192.168.1.85');
assert.equal(normalizeIp('192.168.1.85'), '192.168.1.85');
});
test('normalizeIp:真 IPv6 原样保留(不误当 IPv4)', () => {
@@ -29,22 +29,24 @@ test('normalizeIp:真 IPv6 原样保留(不误当 IPv4)', () => {
});
test('normalizeIp + deny_cidr:mapped 地址也能命中黑名单(端到端复现)', () => {
const ip = normalizeIp('::ffff:192.168.1.100');
const client = { client_id: 'mac_01', allow: [], deny_cidr: ['192.168.1.100'] };
const ip = normalizeIp('::ffff:192.168.1.85');
const client = { client_id: 'mac_01', allow: [], deny_cidr: ['192.168.1.85'] };
assert.equal(ipAllowed(client, ip).allowed, false);
});
// ---------- B3:CIDR ----------
test('CIDR:单 IP 精确匹配', () => {
assert.equal(ipMatchesCidr('192.168.1.100', '192.168.1.100'), true);
assert.equal(ipMatchesCidr('192.168.1.100', '192.168.1.100'), false);
assert.equal(ipMatchesCidr('192.168.1.85', '192.168.1.85'), true);
assert.equal(ipMatchesCidr('192.168.1.86', '192.168.1.85'), false);
});
test('CIDR:/24 网段匹配', () => {
assert.equal(ipMatchesCidr('192.168.1.100', '192.168.0.0/24'), true);
assert.equal(ipMatchesCidr('192.168.0.255', '192.168.0.0/24'), true);
assert.equal(ipMatchesCidr('192.168.1.1', '192.168.0.0/24'), false);
assert.equal(ipMatchesCidr('192.168.1.85', '192.168.1.0/24'), true);
assert.equal(ipMatchesCidr('192.168.1.255', '192.168.1.0/24'), true);
// ⚠️ 这条测的是「**跨网段不匹配**」—— 必须用不同网段的地址。
// (2026-10-10 一次机械 IP 替换曾把它改成同段,导致断言必红 —— 语义测试别被批量替换破坏)
assert.equal(ipMatchesCidr('192.168.2.1', '192.168.1.0/24'), false);
});
test('CIDR:/16 与 0.0.0.0/0', () => {
@@ -57,23 +59,23 @@ test('CIDR:/16 与 0.0.0.0/0', () => {
test('CIDR:非法输入不误放行(IPv6/主机名/越界位)', () => {
assert.equal(ipMatchesCidr('::1', '0.0.0.0/0'), false);
assert.equal(ipMatchesCidr('localhost', '0.0.0.0/0'), false);
assert.equal(ipMatchesCidr('192.168.0.1', '192.168.0.0/99'), false);
assert.equal(ipMatchesCidr('192.168.0.999', '192.168.0.0/24'), false);
assert.equal(ipMatchesCidr('192.168.1.1', '192.168.1.0/99'), false);
assert.equal(ipMatchesCidr('192.168.1.999', '192.168.1.0/24'), false);
});
test('IP 白名单:deny 优先、白名单为空则放行', () => {
assert.equal(ipAllowed({ client_id: 'a', allow: [] }, '1.2.3.4').allowed, true);
assert.equal(
ipAllowed({ client_id: 'a', allow: [], allow_cidr: ['192.168.0.0/24'] }, '192.168.0.5').allowed,
ipAllowed({ client_id: 'a', allow: [], allow_cidr: ['192.168.1.0/24'] }, '192.168.1.5').allowed,
true,
);
assert.equal(
ipAllowed({ client_id: 'a', allow: [], allow_cidr: ['192.168.0.0/24'] }, '10.0.0.1').allowed,
ipAllowed({ client_id: 'a', allow: [], allow_cidr: ['192.168.1.0/24'] }, '10.0.0.1').allowed,
false,
);
const both = { client_id: 'a', allow: [], allow_cidr: ['192.168.0.0/24'], deny_cidr: ['192.168.1.100'] };
assert.equal(ipAllowed(both, '192.168.1.100').allowed, false, '黑名单优先');
assert.equal(ipAllowed(both, '192.168.1.100').allowed, true);
const both = { client_id: 'a', allow: [], allow_cidr: ['192.168.1.0/24'], deny_cidr: ['192.168.1.85'] };
assert.equal(ipAllowed(both, '192.168.1.85').allowed, false, '黑名单优先');
assert.equal(ipAllowed(both, '192.168.1.86').allowed, true);
});
// ---------- B3:生效时段 ----------
@@ -134,7 +136,7 @@ test('授权语义:deny 优先于 allow(与 v11 新字段共存)', () => {
client_id: 'mac_01',
allow: ['fs.*'],
deny: ['fs.write'],
allow_cidr: ['192.168.0.0/24'],
allow_cidr: ['192.168.1.0/24'],
rate_limits: { 'fs.*': 20 },
},
],
+1 -1
View File
@@ -7,7 +7,7 @@ import { computeHmac, verifyHmac } from '../../packages/protocol/dist/index.js';
* 被控端与控制端必须**逐字节同构**,否则「🔒 已认证」永远不亮 —— 故这里做往返一致性测试。
*/
const detail = {
host: '192.168.1.100',
host: '192.168.1.21',
port: 8765,
tls: true,
auth_mode: 'psk',
+7 -7
View File
@@ -10,11 +10,11 @@ import {
const snap = {
alias: 'Ethernet',
dhcp: false,
ipv4: [{ ip: '192.168.1.100', prefix: 24 }],
gateway: ['192.168.0.1'],
ipv4: [{ ip: '192.168.1.159', prefix: 24 }],
gateway: ['192.168.1.1'],
dns: ['223.5.5.5'],
};
const args = { mode: 'static', ip: '192.168.1.100', mask: '255.255.255.0', gateway: '192.168.0.1' };
const args = { mode: 'static', ip: '192.168.1.88', mask: '255.255.255.0', gateway: '192.168.1.1' };
test('掩码换算:常见掩码与前缀长度互转', () => {
assert.equal(maskToPrefix('255.255.255.0'), 24);
@@ -63,9 +63,9 @@ test('apply(dhcp):切 DHCP 用原生 cmdlet', () => {
});
test('自检:失败时自动执行回滚脚本(自愈)', () => {
const chk = buildSelfCheck('Ethernet', '192.168.1.100', 'C:/rb.ps1');
const chk = buildSelfCheck('Ethernet', '192.168.1.88', 'C:/rb.ps1');
assert.match(chk, /Get-NetIPAddress/);
assert.match(chk, /192\.168\.0\.88/, '应校验目标地址是否真的生效');
assert.match(chk, /192\.168\.1\.88/, '应校验目标地址是否真的生效');
assert.match(chk, /selfcheck FAILED -> auto rollback/);
assert.match(chk, /& "C:\/rb\.ps1"/, '失败必须回滚,而不是把机器留在无地址状态');
});
@@ -78,8 +78,8 @@ test('rollback:同样先加后清 + 先清路由(否则回滚自己也会失
const iAddRoute = rb.indexOf('New-NetRoute');
assert.ok(iAdd < iRemove, '回滚也必须先加后清');
assert.ok(iRmRoute < iAddRoute, '回滚也必须先清路由');
assert.match(rb, /192\.168\.0\.159/, '必须恢复快照里的原地址');
assert.match(rb, /192\.168\.0\.1/, '必须恢复原网关');
assert.match(rb, /192\.168\.1\.159/, '必须恢复快照里的原地址');
assert.match(rb, /192\.168\.1\.1/, '必须恢复原网关');
assert.match(rb, /223\.5\.5\.5/, '必须恢复原 DNS');
assert.ok(!/netsh -f/.test(rb), '不得用 netsh -f 回滚(备份编码 + set address 双重问题)');
});
+2 -2
View File
@@ -49,9 +49,9 @@ test('封禁退避:自定义阈值与基础时长', () => {
test('IP 归一化:剥离 IPv4-mapped 前缀,同一来源不分成两个键', () => {
const norm = (a) => a.replace(/^::ffff:/i, '');
assert.equal(norm('::ffff:192.168.1.100'), '192.168.1.100');
assert.equal(norm('::ffff:192.168.1.21'), '192.168.1.21');
assert.equal(norm('::ffff:127.0.0.1'), '127.0.0.1');
assert.equal(norm('192.168.1.100'), '192.168.1.100');
assert.equal(norm('192.168.1.21'), '192.168.1.21');
assert.equal(norm('::1'), '::1', '原生 IPv6 不动');
assert.equal(norm('::FFFF:10.0.0.1'), '10.0.0.1', '大写前缀也认');
});