mirror of
https://github.com/YuzuZensai/Termix.git
synced 2026-11-12 19:16:35 +00:00
108 lines
3.7 KiB
TypeScript
108 lines
3.7 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|||
|
|
import type { LookupAddress, LookupAllOptions } from "dns";
|
||
|
|
import {
|
||
|
|
createDnsLookupHook,
|
||
|
|
isBlockedAddress,
|
||
|
|
} from "../../utils/safe-outbound-fetch.js";
|
||
|
|
|
||
|
|
describe("isBlockedAddress", () => {
|
||
|
|
it("allows public IPv4 addresses", () => {
|
||
|
|
expect(isBlockedAddress("8.8.8.8")).toBe(false);
|
||
|
|
expect(isBlockedAddress("104.21.52.150")).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("blocks private/reserved IPv4 ranges", () => {
|
||
|
|
expect(isBlockedAddress("10.0.0.1")).toBe(true);
|
||
|
|
expect(isBlockedAddress("172.16.0.1")).toBe(true);
|
||
|
|
expect(isBlockedAddress("192.168.1.1")).toBe(true);
|
||
|
|
expect(isBlockedAddress("127.0.0.1")).toBe(true);
|
||
|
|
expect(isBlockedAddress("169.254.1.1")).toBe(true);
|
||
|
|
expect(isBlockedAddress("100.64.0.1")).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("allows public IPv6 addresses", () => {
|
||
|
|
expect(isBlockedAddress("2606:4700:3034::ac43:c88d")).toBe(false);
|
||
|
|
expect(isBlockedAddress("2001:4860:4860::8888")).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("blocks private/reserved IPv6 ranges", () => {
|
||
|
|
expect(isBlockedAddress("::1")).toBe(true);
|
||
|
|
expect(isBlockedAddress("fc00::1")).toBe(true);
|
||
|
|
expect(isBlockedAddress("fe80::1")).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("blocks IPv4-mapped-IPv6 spoofing of private addresses", () => {
|
||
|
|
expect(isBlockedAddress("::ffff:127.0.0.1")).toBe(true);
|
||
|
|
expect(isBlockedAddress("::ffff:192.168.1.1")).toBe(true);
|
||
|
|
expect(isBlockedAddress("::ffff:10.0.0.1")).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("does not block IPv4-mapped-IPv6 form of public addresses", () => {
|
||
|
|
expect(isBlockedAddress("::ffff:104.21.52.150")).toBe(false);
|
||
|
|
expect(isBlockedAddress("::ffff:8.8.8.8")).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("blocks unparseable input", () => {
|
||
|
|
expect(isBlockedAddress("not-an-ip")).toBe(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// These exercise createDnsLookupHook directly against a fake resolver,
|
||
|
|
// bypassing fetch()/undici entirely. That's the actual code path the
|
||
|
|
// original bug lived in — a public IPv4 address getting misclassified as
|
||
|
|
// private — and testing it through a real Agent/fetch call would only
|
||
|
|
// add flakiness (real TCP connects, undici's own quirks) without adding
|
||
|
|
// coverage of the logic that actually broke.
|
||
|
|
function runHook(
|
||
|
|
addresses: LookupAddress[],
|
||
|
|
error: NodeJS.ErrnoException | null = null,
|
||
|
|
) {
|
||
|
|
const fakeLookup = (
|
||
|
|
_host: string,
|
||
|
|
_opts: LookupAllOptions,
|
||
|
|
cb: (err: NodeJS.ErrnoException | null, addrs: LookupAddress[]) => void,
|
||
|
|
) => cb(error, addresses);
|
||
|
|
|
||
|
|
const hook = createDnsLookupHook(fakeLookup);
|
||
|
|
const callback = vi.fn();
|
||
|
|
hook("example.invalid", { all: true }, callback);
|
||
|
|
return callback;
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("createDnsLookupHook", () => {
|
||
|
|
it("allows a public IPv4 address through", () => {
|
||
|
|
const callback = runHook([{ address: "104.21.52.150", family: 4 }]);
|
||
|
|
expect(callback).toHaveBeenCalledWith(null, "104.21.52.150", 4);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("rejects a private address with the private-destination error", () => {
|
||
|
|
const callback = runHook([{ address: "192.168.1.1", family: 4 }]);
|
||
|
|
expect(callback).toHaveBeenCalledWith(
|
||
|
|
expect.objectContaining({
|
||
|
|
message: "Private destinations are not allowed",
|
||
|
|
}),
|
||
|
|
"",
|
||
|
|
0,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("rejects with a distinct error when DNS returns no addresses", () => {
|
||
|
|
const callback = runHook([]);
|
||
|
|
expect(callback).toHaveBeenCalledWith(
|
||
|
|
expect.objectContaining({
|
||
|
|
message: "DNS resolution returned no addresses",
|
||
|
|
}),
|
||
|
|
"",
|
||
|
|
0,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("propagates a real DNS lookup error untouched", () => {
|
||
|
|
const dnsError = Object.assign(new Error("getaddrinfo ENOTFOUND"), {
|
||
|
|
code: "ENOTFOUND",
|
||
|
|
});
|
||
|
|
const callback = runHook([], dnsError);
|
||
|
|
expect(callback).toHaveBeenCalledWith(dnsError, "", 0);
|
||
|
|
});
|
||
|
|
});
|