102 lines
3.1 KiB
JavaScript
102 lines
3.1 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
isPublicAddress,
|
|
resolvePublicUrl,
|
|
fetchPageText,
|
|
} from "../server/fetch-page.js";
|
|
import { setup, signup } from "./helpers.js";
|
|
test("prefill rejects loopback, private, mapped IPv6 and reserved IP destinations", async () => {
|
|
for (const ip of [
|
|
"127.0.0.1",
|
|
"10.0.0.1",
|
|
"192.168.86.2",
|
|
"169.254.169.254",
|
|
"100.64.1.1",
|
|
"0.0.0.0",
|
|
"::1",
|
|
"fc00::1",
|
|
"fe80::1",
|
|
"::ffff:127.0.0.1",
|
|
"2002:7f00:1::",
|
|
"2001:db8::1",
|
|
])
|
|
assert.equal(isPublicAddress(ip), false, ip);
|
|
for (const ip of ["8.8.8.8", "1.1.1.1", "2606:4700:4700::1111"])
|
|
assert.equal(isPublicAddress(ip), true, ip);
|
|
for (const url of [
|
|
"https://127.1/",
|
|
"https://2130706433/",
|
|
"https://[::1]/",
|
|
"https://user:[email protected]/",
|
|
"https://example.com:8443/",
|
|
])
|
|
await assert.rejects(fetchPageText(url), { code: "destination_rejected" });
|
|
});
|
|
test("all DNS answers are validated and selected public address is returned for pinning", async () => {
|
|
const url = new URL("https://coffee.example/");
|
|
await assert.rejects(
|
|
resolvePublicUrl(url, async () => [
|
|
{ address: "8.8.8.8", family: 4 },
|
|
{ address: "10.0.0.1", family: 4 },
|
|
]),
|
|
{ code: "destination_rejected" },
|
|
);
|
|
assert.deepEqual(
|
|
await resolvePublicUrl(url, async () => [
|
|
{ address: "8.8.8.8", family: 4 },
|
|
]),
|
|
{ address: "8.8.8.8", family: 4 },
|
|
);
|
|
});
|
|
test("authenticated prefill route rejects private URL before model invocation", async () => {
|
|
const { agent } = await setup();
|
|
const { csrf } = await signup(agent, "[email protected]");
|
|
const res = await agent
|
|
.post("/api/prefill")
|
|
.set("x-csrf-token", csrf)
|
|
.send({ url: "https://192.168.86.2/" });
|
|
assert.equal(res.status, 422);
|
|
assert.equal(res.body.code, "destination_rejected");
|
|
});
|
|
test("redirects to private destinations are rejected before a second network request", async () => {
|
|
let requests = 0;
|
|
await assert.rejects(
|
|
fetchPageText("https://coffee.example/", {
|
|
resolver: async () => [{ address: "8.8.8.8", family: 4 }],
|
|
requestPage: async () => {
|
|
requests++;
|
|
return new Response(null, {
|
|
status: 302,
|
|
headers: { location: "https://127.0.0.1/" },
|
|
});
|
|
},
|
|
}),
|
|
{ code: "destination_rejected" },
|
|
);
|
|
assert.equal(requests, 1);
|
|
});
|
|
test("deadline stays active while reading a stalled response body", async () => {
|
|
await assert.rejects(
|
|
fetchPageText("https://coffee.example/", {
|
|
timeoutMs: 20,
|
|
resolver: async () => [{ address: "8.8.8.8", family: 4 }],
|
|
requestPage: async (_url, signal) =>
|
|
new Response(
|
|
new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(new TextEncoder().encode("partial"));
|
|
signal.addEventListener(
|
|
"abort",
|
|
() => controller.error(new Error("body deadline")),
|
|
{ once: true },
|
|
);
|
|
},
|
|
}),
|
|
{ headers: { "content-type": "text/plain" } },
|
|
),
|
|
}),
|
|
/body deadline/,
|
|
);
|
|
});
|