summaryrefslogtreecommitdiffhomepage
path: root/scripts/network-policy.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/network-policy.test.ts')
-rw-r--r--scripts/network-policy.test.ts135
1 files changed, 135 insertions, 0 deletions
diff --git a/scripts/network-policy.test.ts b/scripts/network-policy.test.ts
new file mode 100644
index 0000000..af7d870
--- /dev/null
+++ b/scripts/network-policy.test.ts
@@ -0,0 +1,135 @@
+import { describe, expect, it } from "vitest";
+import {
+ buildAllowedHosts,
+ checkDevRequest,
+ checkHost,
+ isAllowedLocalAddress,
+ normalizeHost,
+ normalizeHostHeader,
+ tailscaleSelf,
+} from "./network-policy";
+
+const tailscaleStatusJson = `{"Self":{"HostName":"laptop",
+ "DNSName":"PII_REDACTED_TAILSCALE_HOST_01.",
+ "TailscaleIPs":["PII_REDACTED_DEV_IPV4_01","PII_REDACTED_DEV_IPV6_01"]}}`;
+const allowedHosts = buildAllowedHosts({ tailscaleStatusJson, env: {} });
+const allowedAddresses = new Set(["127.0.0.1", "::1", ...tailscaleSelf(tailscaleStatusJson).ips]);
+const policy = { allowedHosts, allowedAddresses };
+const request = (localAddress = "::1", host: string | undefined = "localhost", origin?: string) =>
+ checkDevRequest({ localAddress, host, origin }, policy);
+
+describe("backend-compatible hosts", () => {
+ it.each([
+ ["LOCALHOST.:34204", "localhost"],
+ ["127.0.0.1:34204", "127.0.0.1"],
+ ["[0:0:0:0:0:0:0:1]:34204", "::1"],
+ ["PII_REDACTED_DEVICE_01", "PII_REDACTED_DEVICE_01"],
+ ["PII_REDACTED_TAILSCALE_HOST_01.:34204", "PII_REDACTED_TAILSCALE_HOST_01"],
+ ["PII_REDACTED_DEV_IPV4_01:34204", "PII_REDACTED_DEV_IPV4_01"],
+ ["[PII_REDACTED_DEV_IPV6_01]:34204", "PII_REDACTED_DEV_IPV6_01"],
+ ])("normalizes and allows %s", (raw, expected) => {
+ expect(normalizeHost(raw)).toBe(expected);
+ expect(checkHost(raw, allowedHosts)).toBe("ok");
+ });
+
+ it.each(
+ (
+ "evil.com|PII_REDACTED_DEVICE_01.evil.com|PII_REDACTED_DEV_IPV4_01.nip.io|laptop|localhost.evil.com|" +
+ "sub.localhost|127.0.0.2|100.1.2.3|172.17.0.1|[email protected]|localhost/path|" +
+ "localhost:bad|localhost:65536|[::1]suffix|localhost\n|localhost..|%6cocalhost|"
+ ).split("|"),
+ )("rejects %j even where Vite would allow it", (host) => {
+ expect(checkHost(host, allowedHosts)).toMatchObject({ status: 421 });
+ });
+
+ const missingStatuses = [undefined, "invalid", "null", "[]", "{}", '{"Self":null}'];
+ it.each(missingStatuses)("fails closed for status %j, retains env overrides", (json) => {
+ const input = {
+ ...(json !== undefined ? { tailscaleStatusJson: json } : {}),
+ env: {
+ DISPATCH_ALLOWED_HOSTS: " EXTRA.example.:8080, https://evil.com",
+ DISPATCH_TAILSCALE_NAME: "PII_REDACTED_DEVICE_01",
+ },
+ };
+ const expected = "localhost,127.0.0.1,::1,extra.example,PII_REDACTED_DEVICE_01".split(",");
+ expect([...buildAllowedHosts(input)]).toEqual(expected);
+ expect(tailscaleSelf(json)).toEqual({ ips: [] });
+ });
+
+ it("keeps auto-detection when env hosts are supplied, without trusting OS hostname", () => {
+ const hosts = buildAllowedHosts({
+ tailscaleStatusJson,
+ env: { DISPATCH_ALLOWED_HOSTS: "extra.example" },
+ });
+ expect(hosts.has("PII_REDACTED_TAILSCALE_HOST_01")).toBe(true);
+ expect(hosts.has("extra.example")).toBe(true);
+ expect(hosts.has("laptop")).toBe(false);
+ expect(tailscaleSelf(tailscaleStatusJson).hostName).toBe("laptop");
+ expect(
+ tailscaleSelf('{"Self":{"TailscaleIPs":[null,"evil.com","PII_REDACTED_DEV_IPV4_01"]}}').ips,
+ ).toEqual(["PII_REDACTED_DEV_IPV4_01"]);
+ });
+
+ it.each([
+ ["LOCALHOST.:34204", "localhost:34204"],
+ ["[0:0:0:0:0:0:0:1]:34204", "[::1]:34204"],
+ ["[::1]", "[::1]"],
+ ["PII_REDACTED_DEVICE_01.", "PII_REDACTED_DEVICE_01"],
+ ])("preserves valid authorities for Vite: %s", (raw, expected) => {
+ expect(normalizeHostHeader(raw)).toBe(expected);
+ });
+});
+
+describe("destination interfaces (not remote peer addresses)", () => {
+ it.each(
+ (
+ "127.0.0.1|::1|0:0:0:0:0:0:0:1|::ffff:127.0.0.1|::ffff:7f00:1|" +
+ "PII_REDACTED_DEV_IPV4_01|::ffff:PII_REDACTED_DEV_IPV4_01|PII_REDACTED_DEV_IPV6_01"
+ ).split("|"),
+ )("allows local address %s", (address) => {
+ expect(isAllowedLocalAddress(address, allowedAddresses)).toBe(true);
+ });
+
+ it.each([
+ undefined,
+ ..."|172.17.0.1|::ffff:172.17.0.1|192.168.1.2|100.1.2.3|0.0.0.0|::|127.0.0.2".split("|"),
+ ])("rejects non-selected interface %j", (address) => {
+ expect(isAllowedLocalAddress(address, allowedAddresses)).toBe(false);
+ // undefined must not use the helper's default loopback address.
+ expect(
+ checkDevRequest({ localAddress: address, host: "localhost", origin: undefined }, policy),
+ ).toMatchObject({ status: 403 });
+ });
+
+ it("does not treat an env host extra as an allowed interface", () => {
+ const hosts = buildAllowedHosts({ env: { DISPATCH_ALLOWED_HOSTS: "172.17.0.1" } });
+ expect(
+ checkDevRequest(
+ { localAddress: "172.17.0.1", host: "172.17.0.1", origin: undefined },
+ { allowedHosts: hosts, allowedAddresses },
+ ),
+ ).toMatchObject({ status: 403 });
+ });
+});
+
+describe("shared HTTP / HMR request decision", () => {
+ it.each([
+ undefined,
+ "http://PII_REDACTED_DEVICE_01.:34204",
+ "https://[PII_REDACTED_DEV_IPV6_01]:443",
+ ])("allows trusted or absent Origin %j", (origin) => {
+ expect(request("::ffff:PII_REDACTED_DEV_IPV4_01", "PII_REDACTED_DEVICE_01", origin)).toBe("ok");
+ });
+ const badOrigins =
+ "null|file://localhost|https://evil.com|http://localhost/|http://user@localhost";
+ it.each(badOrigins.split("|"))("rejects Origin %j", (origin) => {
+ expect(request("::1", "localhost", origin)).toMatchObject({ status: 403 });
+ });
+ it("requires Host and checks it before Origin", () => {
+ for (const host of [undefined, "evil.com"]) {
+ expect(
+ checkDevRequest({ localAddress: "127.0.0.1", host, origin: "null" }, policy),
+ ).toMatchObject({ status: 421 });
+ }
+ });
+});