按 security-reports/ 的隐私审查台账逐项收口。纯脱敏与表述泛化, 不含任何业务逻辑变更;实测结论与排查步骤一字未动。 P1(可定位到具体设备/个人) - scripts/verify-b2.mjs:NA_URL 去掉硬编码缺省值(原指向一台真实机器),改为必填。 这是全仓库唯一「拿着就能连到某台真机」的常量。 - 测试账户名 pdverify → na-test(docs/VM-VERIFICATION.md 7 处、docs/console-plan.md 1 处) - 虚拟机与宿主的真实网段 → RFC 5737 文档保留段 192.0.2.x(含 CLOSE_WAIT 记录) - 跨项目名与私人称呼 → 中性表述(docs/PRD.md、docs/DEVELOPMENT.md、docs/console-plan.md 等) P2(低风险设备指纹) - 虚拟化产品及其 CLI / 访客工具名 → 中性表述(12 个文件 + 1 个工作流注释) - 测试目录 C:\nodeagent-test → C:\na-test - NAS 设备型号与系统名 → NAS 设备 / NAS 系统(docs/DEPLOY-LINUX.md、scripts/install-linux.sh、README.md) - 真机 CPU 型号、真机物理分辨率、代理软件品牌与监听端口、杀软品牌 → 泛化 - 单元测试示例段去掉真机 IP 尾八位:acl-v11 / net-scripts 的 fixture 与断言同步更新 - .gitignore 补 config.json / agent.json(含真实密钥与节点地址,绝不入库) - 顺带移除混入技术文档的「不构成投资建议」字样(与文档主题无关) 验证:pnpm test:unit 340/340 通过;独立扫描器对 235 个跟踪文件做 17 类模式 全量扫描,命中 0。
166 lines
7.3 KiB
JavaScript
166 lines
7.3 KiB
JavaScript
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import {
|
||
ipMatchesCidr,
|
||
ipAllowed,
|
||
inTimeWindow,
|
||
rateLimitFor,
|
||
authorize,
|
||
normalizeIp,
|
||
} from '../../packages/protocol/dist/index.js';
|
||
|
||
// ---------- B3:对端地址规范化(真机踩坑回归) ----------
|
||
|
||
test('normalizeIp:IPv4-mapped IPv6 归一为 IPv4(真机坑)', () => {
|
||
assert.equal(normalizeIp('::ffff:192.168.1.10'), '192.168.1.10');
|
||
assert.equal(normalizeIp('::FFFF:192.168.1.10'), '192.168.1.10');
|
||
});
|
||
|
||
test('normalizeIp:IPv4:port 去端口;纯 IP 原样', () => {
|
||
assert.equal(normalizeIp('192.168.1.10:51234'), '192.168.1.10');
|
||
assert.equal(normalizeIp('192.168.1.10'), '192.168.1.10');
|
||
});
|
||
|
||
test('normalizeIp:真 IPv6 原样保留(不误当 IPv4)', () => {
|
||
assert.equal(normalizeIp('fe80::1'), 'fe80::1');
|
||
assert.equal(normalizeIp('[fe80::1]:8080'), 'fe80::1');
|
||
assert.equal(normalizeIp(''), '');
|
||
assert.equal(normalizeIp(undefined), '');
|
||
});
|
||
|
||
test('normalizeIp + deny_cidr:mapped 地址也能命中黑名单(端到端复现)', () => {
|
||
const ip = normalizeIp('::ffff:192.168.1.10');
|
||
const client = { client_id: 'mac_01', allow: [], deny_cidr: ['192.168.1.10'] };
|
||
assert.equal(ipAllowed(client, ip).allowed, false);
|
||
});
|
||
|
||
// ---------- B3:CIDR ----------
|
||
|
||
test('CIDR:单 IP 精确匹配', () => {
|
||
assert.equal(ipMatchesCidr('192.168.1.10', '192.168.1.10'), true);
|
||
assert.equal(ipMatchesCidr('192.168.1.11', '192.168.1.10'), false);
|
||
});
|
||
|
||
test('CIDR:/24 网段匹配', () => {
|
||
assert.equal(ipMatchesCidr('192.168.1.10', '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', () => {
|
||
assert.equal(ipMatchesCidr('10.0.1.2', '10.0.0.0/16'), true, '10.0.x.x 在 10.0.0.0/16 内');
|
||
assert.equal(ipMatchesCidr('10.1.2.3', '10.0.0.0/16'), false, '10.1.x.x 不在 10.0.0.0/16 内');
|
||
assert.equal(ipMatchesCidr('10.1.2.3', '10.1.0.0/16'), true);
|
||
assert.equal(ipMatchesCidr('8.8.8.8', '0.0.0.0/0'), true);
|
||
});
|
||
|
||
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.1.1', '192.168.1.0/99'), false);
|
||
assert.equal(ipMatchesCidr('192.168.1.999', '192.168.1.0/24'), false);
|
||
});
|
||
|
||
test('F-01:空的掩码位数必须判非法,不得被当成 /0 而放行全部', () => {
|
||
// 真机复现:原来只做 `Number.isFinite(Number(bitsRaw))`,而 **Number('') === 0**
|
||
// → '10.0.0.0/' 被当成 /0 = 匹配全部,于是 `ipMatchesCidr('8.8.8.8','10.0.0.0/')` 返回 true。
|
||
// 这类「配置写漏了却被读成最宽松」的形态最危险:不报错、且方向正好相反。
|
||
assert.equal(ipMatchesCidr('8.8.8.8', '10.0.0.0/'), false, '空掩码不得被当成 /0');
|
||
assert.equal(ipMatchesCidr('10.0.0.1', '10.0.0.0/'), false);
|
||
assert.equal(ipMatchesCidr('8.8.8.8', '0.0.0.0/'), false);
|
||
// 其它畸形位长同样不得放行
|
||
assert.equal(ipMatchesCidr('8.8.8.8', '0.0.0.0/ '), false);
|
||
assert.equal(ipMatchesCidr('8.8.8.8', '0.0.0.0/+0'), false);
|
||
assert.equal(ipMatchesCidr('8.8.8.8', '0.0.0.0/0x0'), false);
|
||
assert.equal(ipMatchesCidr('8.8.8.8', '0.0.0.0/-1'), false);
|
||
// 对照组(铁律 20:预期逐条推演,别把正确行为误报成缺陷)
|
||
assert.equal(ipMatchesCidr('8.8.8.8', '0.0.0.0/0'), true, '显式的 /0 是合法的「匹配全部」');
|
||
assert.equal(ipMatchesCidr('10.0.0.0', '10.0.0.0/32'), true, '/32 只含这一个地址');
|
||
assert.equal(ipMatchesCidr('10.0.0.1', '10.0.0.0/32'), false, '/32 不含 10.0.0.1');
|
||
});
|
||
|
||
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.1.0/24'] }, '192.168.1.5').allowed,
|
||
true,
|
||
);
|
||
assert.equal(
|
||
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.1.0/24'], deny_cidr: ['192.168.1.10'] };
|
||
assert.equal(ipAllowed(both, '192.168.1.10').allowed, false, '黑名单优先');
|
||
assert.equal(ipAllowed(both, '192.168.1.11').allowed, true);
|
||
});
|
||
|
||
// ---------- B3:生效时段 ----------
|
||
|
||
test('时段:未配置即全天可用', () => {
|
||
assert.equal(inTimeWindow(undefined, new Date('2026-09-29T03:00:00')).allowed, true);
|
||
});
|
||
|
||
test('时段:普通区间与跨零点区间', () => {
|
||
const work = { from: '09:00', to: '18:00' };
|
||
assert.equal(inTimeWindow(work, new Date('2026-09-29T10:30:00')).allowed, true);
|
||
assert.equal(inTimeWindow(work, new Date('2026-09-29T20:00:00')).allowed, false);
|
||
|
||
const night = { from: '22:00', to: '06:00' };
|
||
assert.equal(inTimeWindow(night, new Date('2026-09-29T23:30:00')).allowed, true, '当天深夜');
|
||
assert.equal(inTimeWindow(night, new Date('2026-09-29T02:00:00')).allowed, true, '次日凌晨');
|
||
assert.equal(inTimeWindow(night, new Date('2026-09-29T12:00:00')).allowed, false);
|
||
});
|
||
|
||
test('时段:星期限制(1=周一…7=周日)', () => {
|
||
const weekday = { days: [1, 2, 3, 4, 5] };
|
||
assert.equal(inTimeWindow(weekday, new Date('2026-09-29T10:00:00')).allowed, true, '周二');
|
||
assert.equal(inTimeWindow(weekday, new Date('2026-10-03T10:00:00')).allowed, false, '周六');
|
||
assert.equal(inTimeWindow({ days: [7] }, new Date('2026-10-04T10:00:00')).allowed, true, '周日=7');
|
||
});
|
||
|
||
test('时段:非法格式拒绝(不静默放行)', () => {
|
||
assert.equal(inTimeWindow({ from: '9am', to: '6pm' }, new Date()).allowed, false);
|
||
assert.equal(inTimeWindow({ from: '25:00', to: '26:00' }, new Date()).allowed, false);
|
||
});
|
||
|
||
// ---------- B3:按能力限速 ----------
|
||
|
||
test('限速:精确匹配优先,其次 glob,最后回退全局', () => {
|
||
const client = {
|
||
client_id: 'mac_01',
|
||
allow: ['*'],
|
||
max_calls_per_min: 60,
|
||
rate_limits: { 'fs.read': 10, 'fs.*': 30, 'system.shell.exec': 5 },
|
||
};
|
||
assert.equal(rateLimitFor(client, 'fs.read'), 10, '精确优先于 glob');
|
||
assert.equal(rateLimitFor(client, 'fs.write'), 30, '继承 glob');
|
||
assert.equal(rateLimitFor(client, 'system.shell.exec'), 5);
|
||
assert.equal(rateLimitFor(client, 'system.info'), 60, '回退全局');
|
||
});
|
||
|
||
test('限速:无任何配置即不限速', () => {
|
||
assert.equal(rateLimitFor({ client_id: 'a', allow: [] }, 'system.info'), undefined);
|
||
});
|
||
|
||
// ---------- 回归:CIDR/时段不影响原有授权语义 ----------
|
||
|
||
test('授权语义:deny 优先于 allow(与 v11 新字段共存)', () => {
|
||
const policy = {
|
||
default_effect: 'deny',
|
||
clients: [
|
||
{
|
||
client_id: 'mac_01',
|
||
allow: ['fs.*'],
|
||
deny: ['fs.write'],
|
||
allow_cidr: ['192.168.1.0/24'],
|
||
rate_limits: { 'fs.*': 20 },
|
||
},
|
||
],
|
||
};
|
||
assert.equal(authorize(policy, 'mac_01', 'fs.read').allowed, true);
|
||
assert.equal(authorize(policy, 'mac_01', 'fs.write').allowed, false);
|
||
assert.equal(authorize(policy, 'mac_01', 'system.info').allowed, false);
|
||
});
|