/** * SSRF DNS Pinning 测试(v0.7.3 P2-1) * * 锁定三个单元: * D1 createPinnedLookup —— 只返回校验阶段锁定的 IP 集合(过滤非法 family), * 空集合返回 ENOTFOUND(防御)。 * D2 resolveRedirectTarget —— 重定向状态识别 + 相对 Location 解析 + * 非法/缺失 Location 返回 null。 * D3 resolvePinnedIps —— IP 直连与私网拒绝(走 ssrf-guard 单一事实来源; * 域名解析路径由 ssrf-guard 表测覆盖,此处不重复触网)。 */ import { describe, it, expect } from 'vitest'; import { createPinnedLookup, resolveRedirectTarget, resolvePinnedIps } from '../ssrf-dispatcher'; import type { LookupCallback } from '../ssrf-dispatcher'; describe('createPinnedLookup', () => { it('D1: 仅返回钉死的 IP 集合(忽略 hostname),family 正确标注', async () => { const lookup = createPinnedLookup(['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946']); const result = await new Promise<{ address: string; family: number }[]>((resolve, reject) => { const cb: LookupCallback = (err, addresses) => (err ? reject(err) : resolve(addresses!)); lookup('attacker.example', {}, cb); }); expect(result).toHaveLength(2); expect(result[0]).toEqual({ address: '93.184.216.34', family: 4 }); expect(result[1].family).toBe(6); }); it('D1: 非法 family(非 IPv4/IPv6 字符串)被过滤', async () => { const lookup = createPinnedLookup(['not-an-ip']); await expect( new Promise((resolve, reject) => { const cb: LookupCallback = (err, addresses) => (err ? reject(err) : resolve(addresses)); lookup('h', {}, cb as never); }), ).rejects.toMatchObject({ code: 'ENOTFOUND' }); }); it('D1: 空集合 → ENOTFOUND(防御:调用方不应构造空 pin dispatcher)', async () => { const lookup = createPinnedLookup([]); await expect( new Promise((resolve, reject) => { const cb: LookupCallback = (err, addresses) => (err ? reject(err) : resolve(addresses)); lookup('h', {}, cb as never); }), ).rejects.toMatchObject({ code: 'ENOTFOUND' }); }); }); describe('resolveRedirectTarget', () => { const makeResponse = (status: number, location?: string) => ({ status, headers: { get: (name: string) => (name.toLowerCase() === 'location' ? (location ?? null) : null), }, }); it('D2: 301/302/303/307/308 识别并解析绝对 Location', () => { for (const status of [301, 302, 303, 307, 308]) { expect( resolveRedirectTarget(makeResponse(status, 'https://cdn.example.com/x'), 'https://a.test/'), ).toBe('https://cdn.example.com/x'); } }); it('D2: 相对 Location 以当前 URL 为基解析(RFC 7231)', () => { expect(resolveRedirectTarget(makeResponse(302, '/next?a=1'), 'https://a.test/dir/page')).toBe( 'https://a.test/next?a=1', ); }); it('D2: 非 3xx 状态 → null(终态)', () => { expect(resolveRedirectTarget(makeResponse(200), 'https://a.test/')).toBeNull(); expect(resolveRedirectTarget(makeResponse(404), 'https://a.test/')).toBeNull(); }); it('D2: 缺失/非法 Location → null', () => { expect(resolveRedirectTarget(makeResponse(302), 'https://a.test/')).toBeNull(); expect(resolveRedirectTarget(makeResponse(302, ''), 'https://a.test/')).toBeNull(); expect(resolveRedirectTarget(makeResponse(302, 'http://[::bad'), 'https://a.test/')).toBeNull(); }); }); describe('resolvePinnedIps', () => { it('D3: IP 直连 URL —— 公网 IP 直接返回', async () => { const ips = await resolvePinnedIps('https://93.184.216.34/x'); expect(ips).toEqual(['93.184.216.34']); }); it('D3: 私有/回环 IP 直连被拒(单一事实来源 ssrf-guard)', async () => { for (const host of ['127.0.0.1', '10.0.0.5', '169.254.169.254', '192.168.1.1', '[::1]']) { await expect(resolvePinnedIps(`http://${host}/latest`)).rejects.toThrow(/Blocked SSRF/); } }); it('D3: 非 http/https 协议被拒', async () => { await expect(resolvePinnedIps('ftp://example.com')).rejects.toThrow(/not allowed/); }); it('D3: 非法 URL 被拒', async () => { await expect(resolvePinnedIps('not a url')).rejects.toThrow(/Invalid URL/); }); });