pi-mono/packages/chord/test/next-node/http/admission.test.ts
Mario Zechner 8959ad6e68 feat(chord): Chord v2 slice 10, HTTP and workers
chord/next/http: a Web fetch handler built from the registry Http (routes, wraps,
sockets, hosts, relays, CSP sources) and Authenticators, responses as descriptions,
a security baseline on by default, Chord's own Node listener as a host-lifetime
object, nodeRequest for apps mounting it, links and plugin sockets.
chord/next/workers: a forked worker per address, started on demand, stopped when
idle, never restarted by itself, plugins built by the server and shipped.
Plugins: kinds with platform and host packages, createPluginRuntime, scripts(kind),
rollout results per host, imports of other plugins by name. Kernel: RouteHandle
(retry, suspend), no route backoff, anonymous host.root, origins only from relays.
ws is an optional peer dependency.
2026-10-31 20:40:36 +00:00

154 lines
6.8 KiB
TypeScript

// Tests of admission (slice 10 §2, R4; security baseline (1)-(3), "Relays"): local only on a loopback connection with
// a loopback `Host` and its port and no forwarded headers; allowed hosts as https; duplicate `Host`s refused; relays
// verifying forwarded requests; foreign requests and `Origin` rules.
import { describe, expect, it } from "vitest";
import {
admit,
type Connection,
isForeign,
isNavigation,
originAccepted,
} from "../../../src/next-node/http/admission.ts";
import { type Incoming, webIncoming } from "../../../src/next-node/http/incoming.ts";
import type { HttpRelay } from "../../../src/next-node/http/router.ts";
const LOOPBACK: Connection = { localAddress: "127.0.0.1", localPort: 8788, remoteAddress: "127.0.0.1" };
const allowed = new Set(["notes.example.com"]);
/** A request with `headers` (a list: duplicates kept), as the component reads it. */
function request(headers: [string, string][] | Record<string, string>): Incoming {
return webIncoming(new Request("http://x/", { headers }));
}
/** What admission gives a request with `headers` on `connection`, without relays. */
function admitted(headers: [string, string][], connection = LOOPBACK, relays: readonly HttpRelay[] = []) {
return admit(request(headers), connection, allowed, relays);
}
describe("provenance", () => {
it("is local on a loopback connection with a loopback Host and its port (without it on port 80)", () => {
for (const host of ["127.0.0.1:8788", "LOCALHOST:8788", "[::1]:8788"]) {
expect(admitted([["host", host]])).toEqual({
provenance: { kind: "local" },
origin: `http://${host.toLowerCase()}`,
framer: undefined,
allowed: undefined,
relay: undefined,
});
}
expect(admitted([["host", "localhost"]], { localAddress: "::ffff:127.0.0.1", localPort: 80 })).toMatchObject({
provenance: { kind: "local" },
});
});
// Opus's security audit H0: a loopback `Host` from a LAN address, or forwarded by a local proxy, was local.
it("refuses a loopback Host on another connection, without a connection, or with forwarded headers", () => {
const host: [string, string] = ["host", "127.0.0.1:8788"];
expect(admitted([host], { localAddress: "192.168.1.5", localPort: 8788 })).toEqual({ kind: "refused" });
expect(admitted([host], {})).toEqual({ kind: "refused" });
expect(admitted([host], { localPort: 8788 })).toEqual({ kind: "refused" });
for (const forwarded of ["x-forwarded-for", "x-forwarded-proto", "forwarded"]) {
expect(admitted([host, [forwarded, "1"]])).toEqual({ kind: "refused" });
}
});
it("admits an allowed host as remote https; refuses another, another port, two Hosts, none", () => {
expect(admitted([["host", "Notes.Example.com"]])).toEqual({
provenance: { kind: "remote", host: "notes.example.com" },
origin: "https://notes.example.com",
framer: undefined,
allowed: "notes.example.com",
relay: undefined,
});
for (const headers of [
[["host", "evil.com"]],
[["host", "127.0.0.1:1"]],
[["host", "localhost"]],
[["host", "localhost.:8788"]],
[["host", "notes.example.com:443"]],
[
["host", "127.0.0.1:8788"],
["host", "notes.example.com"],
],
[
["host", "notes.example.com"],
["host", "127.0.0.1:8788"],
],
[],
] as [string, string][][]) {
expect(admitted(headers)).toEqual({ kind: "refused" });
}
});
it("admits a forwarded request on loopback that a relay verifies, with its origin and framer", () => {
const radius: HttpRelay = {
name: "radius",
verify: (request) =>
request.headers.get("x-relay-secret") === "s"
? { origin: "https://me.radius.example", framer: "https://radius.example" }
: undefined,
};
const broken: HttpRelay = {
name: "broken",
verify: () => {
throw new Error("verify");
},
};
const host: [string, string] = ["host", "127.0.0.1:8788"];
const forwarded: [string, string] = ["x-forwarded-for", "203.0.113.9"];
expect(admitted([host, forwarded, ["x-relay-secret", "s"]], LOOPBACK, [radius])).toEqual({
provenance: { kind: "relayed", relay: "radius", origin: "https://me.radius.example" },
origin: "https://me.radius.example",
framer: "https://radius.example",
allowed: undefined,
relay: radius,
});
expect(admitted([host, forwarded, ["x-relay-secret", "wrong"]], LOOPBACK, [radius])).toEqual({ kind: "refused" });
// Fable's recheck N2: a relay's origin reached the CSP unchecked (`; script-src *`).
for (const bad of [
{ origin: "https://a.example; script-src *" },
{ origin: "https://a.example", framer: "javascript:x" },
{ origin: "https://a.example/path" },
]) {
const lying: HttpRelay = { name: "lying", verify: () => bad };
expect(admitted([host, forwarded], LOOPBACK, [lying])).toMatchObject({
kind: "refused",
failed: { relay: "lying" },
});
}
const failed = admitted([host, forwarded], LOOPBACK, [broken]);
expect(failed).toMatchObject({ kind: "refused", failed: { relay: "broken", error: { message: "verify" } } });
});
});
describe("foreign requests and Origin", () => {
it("is foreign for another site by Fetch Metadata, or a frame not from this origin; not without the headers", () => {
const headers = request;
expect(isForeign(headers({ "sec-fetch-site": "cross-site" }))).toBe(true);
expect(isForeign(headers({ "sec-fetch-site": "same-site" }))).toBe(true);
expect(isForeign(headers({ "sec-fetch-site": "same-origin", "sec-fetch-dest": "iframe" }))).toBe(false);
expect(isForeign(headers({ "sec-fetch-site": "none", "sec-fetch-dest": "iframe" }))).toBe(true);
expect(isForeign(headers({ "sec-fetch-dest": "embed" }))).toBe(true);
expect(isForeign(headers({ "sec-fetch-site": "same-origin" }))).toBe(false);
expect(isForeign(headers({ "sec-fetch-site": "none" }))).toBe(false);
expect(isForeign(headers({}))).toBe(false);
expect(isNavigation("GET", headers({ "sec-fetch-mode": "navigate", "sec-fetch-dest": "document" }))).toBe(true);
expect(isNavigation("POST", headers({ "sec-fetch-mode": "navigate", "sec-fetch-dest": "document" }))).toBe(false);
expect(isNavigation("GET", headers({ "sec-fetch-mode": "navigate", "sec-fetch-dest": "empty" }))).toBe(true);
expect(isNavigation("GET", headers({ "sec-fetch-mode": "navigate", "sec-fetch-dest": "iframe" }))).toBe(false);
});
it("accepts an absent or equal Origin; an unsafe method from a browser needs it", () => {
const origin = "http://127.0.0.1:8788";
const accepted = (method: string, entries: Record<string, string>) =>
originAccepted(method, request(entries), origin);
expect(accepted("GET", {})).toBe(true);
expect(accepted("POST", {})).toBe(true);
expect(accepted("POST", { origin })).toBe(true);
expect(accepted("GET", { origin: "http://evil.com" })).toBe(false);
expect(accepted("POST", { origin: "null" })).toBe(false);
expect(accepted("POST", { "sec-fetch-site": "same-origin" })).toBe(false);
expect(accepted("GET", { "sec-fetch-site": "same-origin" })).toBe(true);
});
});