mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
A Nightscout self-host on host networking with NIGHTSCOUT_PRIVATE_ORIGINS=http://localhost:1337 worked before and must keep working unchanged; an operator who lists an exact loopback origin with its port has made the same deliberate decision as one who lists a LAN origin. The never-grantable floor is now the unspecified address, link-local and the metadata endpoint only. Loopback (127/8, ::1, localhost) is grantable when listed exactly, still resolved and pinned at connect time with redirects forbidden, and still refused when not listed. ::1 is answered before the embedded-IPv4 unwrap, which read it as the IPv4-compatible spelling of 0.0.0.1 and refused it as unspecified. The pinned Nightscout transport test runs the real floor again. Each refused range keeps its own test; loopback moves to the grantable-when-listed set with a test that an unlisted loopback is still refused, and a Nightscout grant of http://localhost:1337 is asserted to parse. Refs #947
161 lines
5.2 KiB
TypeScript
161 lines
5.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import * as nightscoutValidation from "../nightscout";
|
|
|
|
type NightscoutOriginVerdict = {
|
|
allowed: boolean;
|
|
canonicalOrigin: string | null;
|
|
privateOriginApproved: boolean;
|
|
reasonCode: "invalid_origin" | "private_origin_not_approved" | null;
|
|
};
|
|
|
|
type NightscoutPolicyModule = typeof nightscoutValidation & {
|
|
parseNightscoutPrivateOrigins(raw: string | undefined): ReadonlySet<string>;
|
|
evaluateNightscoutOrigin(
|
|
value: string,
|
|
privateOrigins: ReadonlySet<string>,
|
|
): NightscoutOriginVerdict;
|
|
};
|
|
|
|
const { parseNightscoutPrivateOrigins, evaluateNightscoutOrigin } =
|
|
nightscoutValidation as NightscoutPolicyModule;
|
|
|
|
describe("NIGHTSCOUT_PRIVATE_ORIGINS", () => {
|
|
it("parses a comma-separated server setting into canonical exact origins", () => {
|
|
expect(
|
|
parseNightscoutPrivateOrigins(
|
|
" http://10.0.0.4:1337, HTTPS://CGM.LAN:443 ,https://[fd00::4]:8443 ",
|
|
),
|
|
).toEqual(
|
|
new Set([
|
|
"http://10.0.0.4:1337",
|
|
"https://cgm.lan",
|
|
"https://[fd00::4]:8443",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("authorizes no private origin when the operator setting is empty", () => {
|
|
expect(parseNightscoutPrivateOrigins(undefined)).toEqual(new Set());
|
|
expect(parseNightscoutPrivateOrigins(" , ")).toEqual(new Set());
|
|
|
|
expect(
|
|
evaluateNightscoutOrigin(
|
|
"http://10.0.0.4:1337",
|
|
parseNightscoutPrivateOrigins(""),
|
|
),
|
|
).toEqual({
|
|
allowed: false,
|
|
canonicalOrigin: "http://10.0.0.4:1337",
|
|
privateOriginApproved: false,
|
|
reasonCode: "private_origin_not_approved",
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
["credentials", "http://operator:secret@10.0.0.4:1337"],
|
|
["non-root path", "http://10.0.0.4:1337/api/v1"],
|
|
["query", "http://10.0.0.4:1337?token=secret"],
|
|
["fragment", "http://10.0.0.4:1337#admin"],
|
|
["wrong scheme", "ftp://10.0.0.4:1337"],
|
|
["wildcard", "https://*.cgm.lan"],
|
|
["suffix", ".cgm.lan"],
|
|
["malformed", "not an origin"],
|
|
])("rejects a %s operator trust entry", (_label, value) => {
|
|
expect(() => parseNightscoutPrivateOrigins(value)).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("Nightscout exact-origin policy", () => {
|
|
const privateOrigins = new Set([
|
|
"http://10.0.0.4:1337",
|
|
"https://cgm.lan:8443",
|
|
]);
|
|
|
|
it("keeps an ordinary canonical public origin functional", () => {
|
|
expect(
|
|
evaluateNightscoutOrigin("https://NS.EXAMPLE.COM:443", privateOrigins),
|
|
).toEqual({
|
|
allowed: true,
|
|
canonicalOrigin: "https://ns.example.com",
|
|
privateOriginApproved: false,
|
|
reasonCode: null,
|
|
});
|
|
});
|
|
|
|
it("allows only the exact operator-approved private scheme, host, and port", () => {
|
|
expect(
|
|
evaluateNightscoutOrigin("http://10.0.0.4:1337", privateOrigins),
|
|
).toEqual({
|
|
allowed: true,
|
|
canonicalOrigin: "http://10.0.0.4:1337",
|
|
privateOriginApproved: true,
|
|
reasonCode: null,
|
|
});
|
|
|
|
for (const value of [
|
|
"https://10.0.0.4:1337",
|
|
"http://10.0.0.4:8080",
|
|
"http://10.0.0.40:1337",
|
|
"https://sub.cgm.lan:8443",
|
|
]) {
|
|
expect(evaluateNightscoutOrigin(value, privateOrigins)).toMatchObject({
|
|
allowed: false,
|
|
privateOriginApproved: false,
|
|
reasonCode: "private_origin_not_approved",
|
|
});
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
["credentials", "http://operator:secret@10.0.0.4:1337"],
|
|
["path", "http://10.0.0.4:1337/api/v1"],
|
|
["query", "http://10.0.0.4:1337?token=secret"],
|
|
["fragment", "http://10.0.0.4:1337#admin"],
|
|
])("rejects a %s even when its bare origin is approved", (_label, value) => {
|
|
expect(evaluateNightscoutOrigin(value, privateOrigins)).toEqual({
|
|
allowed: false,
|
|
canonicalOrigin: null,
|
|
privateOriginApproved: false,
|
|
reasonCode: "invalid_origin",
|
|
});
|
|
});
|
|
|
|
it("keeps a loopback Nightscout grant working exactly as before", () => {
|
|
// A host-networking self-host lists http://localhost:1337; the shared
|
|
// floor must not turn that into a failed sync on upgrade.
|
|
expect(parseNightscoutPrivateOrigins("http://localhost:1337")).toEqual(
|
|
new Set(["http://localhost:1337"]),
|
|
);
|
|
expect(
|
|
evaluateNightscoutOrigin(
|
|
"http://localhost:1337",
|
|
parseNightscoutPrivateOrigins("http://localhost:1337"),
|
|
),
|
|
).toMatchObject({ allowed: true, privateOriginApproved: true });
|
|
expect(
|
|
parseNightscoutPrivateOrigins("http://127.0.0.1:1337, http://[::1]:1337"),
|
|
).toEqual(new Set(["http://127.0.0.1:1337", "http://[::1]:1337"]));
|
|
});
|
|
|
|
it("names the entry and the reason when a metadata grant is listed, without its token (M2)", () => {
|
|
let caught: unknown;
|
|
try {
|
|
parseNightscoutPrivateOrigins(
|
|
"https://cgm.lan, http://169.254.169.254:1337/?token=SECRET123",
|
|
);
|
|
} catch (err) {
|
|
caught = err;
|
|
}
|
|
expect(caught).toBeInstanceOf(Error);
|
|
const message = (caught as Error).message;
|
|
expect((caught as Error).name).toBe("NightscoutOriginConfigError");
|
|
expect(message).toContain(
|
|
'NIGHTSCOUT_PRIVATE_ORIGINS entry "http://169.254.169.254:1337/"',
|
|
);
|
|
expect(message).toMatch(/link-local.*cannot be granted/);
|
|
expect(message).not.toContain("SECRET123");
|
|
expect(message).not.toContain("token=");
|
|
});
|
|
});
|