mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-24 01:41:42 +02:00
Apply the reviewed change for Keep proven external connection failures out of Sentry. Validation: local typecheck/build and focused regression tests, passing exact-head CI, Greptile 5/5, and independent source review. The PR records full-suite evidence and any local environment limitations. Co-Authored-By: Paperclip <noreply@paperclip.ing>
99 lines
4.1 KiB
TypeScript
99 lines
4.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { assertPublicRemoteHttpEndpoint } from "../services/remote-http-endpoint-guard.js";
|
|
import { readRemoteConnectionFailure } from "../services/remote-connection-failure.js";
|
|
|
|
function guardError(message: string, code: string) {
|
|
return Object.assign(new Error(message), { code });
|
|
}
|
|
|
|
describe("remote HTTP endpoint guard", () => {
|
|
it.each(["ENOTFOUND", "ENODATA", "EAI_AGAIN"])("retains proven DNS failure %s across the public wrapper", async (code) => {
|
|
const failure = await assertPublicRemoteHttpEndpoint(new URL("https://missing.example.test/mcp"), {
|
|
lookup: async () => { throw Object.assign(new Error("lookup failed"), { code }); },
|
|
}, guardError).catch((error) => error);
|
|
expect(failure).toMatchObject({ code: "remote_http_dns_failed" });
|
|
expect(readRemoteConnectionFailure(failure)).toBe("dns_failure");
|
|
});
|
|
|
|
it("marks its exact DNS deadline without trusting unknown lookup exceptions", async () => {
|
|
const timedOut = await assertPublicRemoteHttpEndpoint(new URL("https://silent.example.test/mcp"), {
|
|
lookup: async () => new Promise(() => {}), dnsTimeoutMs: 1,
|
|
}, guardError).catch((error) => error);
|
|
expect(timedOut).toMatchObject({ code: "remote_http_dns_failed" });
|
|
expect(readRemoteConnectionFailure(timedOut)).toBe("connection_timeout");
|
|
const unknown = await assertPublicRemoteHttpEndpoint(new URL("https://broken.example.test/mcp"), {
|
|
lookup: async () => { throw new TypeError("internal lookup bug: ENOTFOUND"); },
|
|
}, guardError).catch((error) => error);
|
|
expect(unknown).toMatchObject({ code: "remote_http_dns_failed" });
|
|
expect(readRemoteConnectionFailure(unknown)).toBeNull();
|
|
});
|
|
|
|
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" });
|
|
});
|
|
});
|