Files
nodeagent/tests/unit/acl-v11.test.mjs
T
jkooe 2cb0ed148e 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 全绿。
2026-10-10 00:39:28 +08:00

148 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.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.85:51234'), '192.168.1.85');
assert.equal(normalizeIp('192.168.1.85'), '192.168.1.85');
});
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.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.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.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', () => {
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('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.85'] };
assert.equal(ipAllowed(both, '192.168.1.85').allowed, false, '黑名单优先');
assert.equal(ipAllowed(both, '192.168.1.86').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);
});