77 lines
2.7 KiB
TypeScript
77 lines
2.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { assertPublicRemoteHttpEndpoint } from "../services/remote-http-endpoint-guard.js";
|
|
|
|
function guardError(message: string, code: string) {
|
|
return Object.assign(new Error(message), { code });
|
|
}
|
|
|
|
describe("remote HTTP endpoint guard", () => {
|
|
it("blocks hostnames that resolve to private network addresses", async () => {
|
|
await expect(assertPublicRemoteHttpEndpoint(
|
|
new URL("https://metadata.example/mcp"),
|
|
{ lookup: async () => [{ address: "10.0.0.12", family: 4 }] },
|
|
guardError,
|
|
)).rejects.toMatchObject({ code: "remote_http_private_endpoint" });
|
|
});
|
|
|
|
it("allows hostnames when every resolved address is public", async () => {
|
|
await expect(assertPublicRemoteHttpEndpoint(
|
|
new URL("https://public.example/mcp"),
|
|
{ lookup: async () => [{ address: "93.184.216.34", family: 4 }] },
|
|
guardError,
|
|
)).resolves.toBeUndefined();
|
|
});
|
|
|
|
it.each([
|
|
"169.254.0.1",
|
|
"169.254.169.254",
|
|
"::ffff:169.254.169.254",
|
|
"::ffff:a9fe:a9fe",
|
|
"fe80::1",
|
|
"febf::1",
|
|
])("always rejects link-local literal %s when private networking is allowed", async (address) => {
|
|
const url = address.includes(":") ? `http://[${address}]/mcp` : `http://${address}/mcp`;
|
|
await expect(assertPublicRemoteHttpEndpoint(
|
|
new URL(url),
|
|
{ allowPrivateNetwork: true },
|
|
guardError,
|
|
)).rejects.toMatchObject({ code: "remote_http_private_endpoint" });
|
|
});
|
|
|
|
it.each(["169.254.42.1", "fe80::1234"])(
|
|
"always rejects link-local DNS answer %s when private networking is allowed",
|
|
async (address) => {
|
|
await expect(assertPublicRemoteHttpEndpoint(
|
|
new URL("https://operator-endpoint.example/mcp"),
|
|
{ allowPrivateNetwork: true, lookup: async () => [{ address, family: address.includes(":") ? 6 : 4 }] },
|
|
guardError,
|
|
)).rejects.toMatchObject({ code: "remote_http_private_endpoint" });
|
|
},
|
|
);
|
|
|
|
it.each(["127.0.0.1", "10.1.2.3", "fd00::1"])(
|
|
"allows intended private address %s when private networking is allowed",
|
|
async (address) => {
|
|
const url = address.includes(":") ? `http://[${address}]/mcp` : `http://${address}/mcp`;
|
|
await expect(assertPublicRemoteHttpEndpoint(
|
|
new URL(url),
|
|
{ allowPrivateNetwork: true },
|
|
guardError,
|
|
)).resolves.toBeUndefined();
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
"http://[2001::1]/mcp",
|
|
"http://[2001:20::1]/mcp",
|
|
"http://[2001:2f::1]/mcp",
|
|
"http://[64:ff9b:1::1]/mcp",
|
|
])("rejects reserved IPv6 endpoint %s", async (url) => {
|
|
await expect(assertPublicRemoteHttpEndpoint(
|
|
new URL(url),
|
|
{},
|
|
guardError,
|
|
)).rejects.toMatchObject({ code: "remote_http_private_endpoint" });
|
|
});
|
|
});
|