mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 17:12:27 +03:00
test(authz): stop skipping the loopback coverage and re-point the Host-spoofing guard
Two follow-ups to the peerContext extraction, both caught by CI. public-policy.test.ts guarded its two loopback cases behind `if (!getMachineTokenSync()) t.skip(...)`. Those are the only tests covering the branch the pack-boot fix added, so a machine-id that failed to resolve would have silenced exactly the coverage that matters and shipped the authorization change untested — which is what the test-masking detector flagged. The sibling management-policy cases in authz/routeGuard.test.ts call the same helper with no guard and pass in CI, so an empty token is a broken environment: assert on it loudly instead of skipping. route-guard-private-lan.test.ts pinned its Host-spoofing regression guard to source text inside management.ts; moving requestPeerAddress into peerContext.ts made the literals disappear from the file it greps, while the property itself was untouched. The guard now follows the implementation: neither module may read the Host header, the owning module must resolve the token-stamped peer, and — new positive anchor — management.ts must still delegate to peerContext, so the guard cannot pass by the policy quietly regrowing a Host-based path.
This commit is contained in:
@@ -47,11 +47,13 @@ function cliCtx(overrides: Partial<PolicyContext["request"]> = {}): PolicyContex
|
||||
};
|
||||
}
|
||||
|
||||
test("publicPolicy stamps the local-CLI subject for a valid loopback machine token", async (t) => {
|
||||
if (!getMachineTokenSync()) {
|
||||
t.skip("machine-id unavailable in this environment");
|
||||
return;
|
||||
}
|
||||
test("publicPolicy stamps the local-CLI subject for a valid loopback machine token", async () => {
|
||||
// No environment guard here on purpose: this and the negative control below are
|
||||
// the only tests covering the loopback branch added for check:pack-boot, and a
|
||||
// conditional skip would silence them exactly where the coverage matters. The
|
||||
// sibling management-policy tests in authz/routeGuard.test.ts call this same
|
||||
// helper unguarded, so an empty token is a broken environment worth failing on.
|
||||
assert.ok(getMachineTokenSync(), "machine token must resolve for this suite to mean anything");
|
||||
const out = await publicPolicy.evaluate(cliCtx());
|
||||
assert.equal(out.allow, true);
|
||||
if (out.allow) {
|
||||
@@ -60,11 +62,8 @@ test("publicPolicy stamps the local-CLI subject for a valid loopback machine tok
|
||||
}
|
||||
});
|
||||
|
||||
test("publicPolicy keeps anonymous for a non-loopback peer carrying the token", async (t) => {
|
||||
if (!getMachineTokenSync()) {
|
||||
t.skip("machine-id unavailable in this environment");
|
||||
return;
|
||||
}
|
||||
test("publicPolicy keeps anonymous for a non-loopback peer carrying the token", async () => {
|
||||
assert.ok(getMachineTokenSync(), "machine token must resolve for this suite to mean anything");
|
||||
const out = await publicPolicy.evaluate(cliCtx({ ip: "203.0.113.7" }));
|
||||
assert.equal(out.allow, true);
|
||||
if (out.allow) assert.equal(out.subject.kind, "anonymous");
|
||||
|
||||
@@ -95,17 +95,38 @@ test("management policy must NOT derive locality from the spoofable Host header"
|
||||
join(import.meta.dirname, "../../src/server/authz/policies/management.ts"),
|
||||
"utf8"
|
||||
);
|
||||
// `requestPeerAddress` and friends moved to authz/peerContext.ts when the PUBLIC
|
||||
// policy had to share the very same verdict (check:pack-boot / #11040 follow-up).
|
||||
// The guard follows the implementation instead of the filename: neither module may
|
||||
// read the Host header, and the module that OWNS peer resolution must resolve the
|
||||
// token-stamped peer IP.
|
||||
const peerSrc = readFileSync(
|
||||
join(import.meta.dirname, "../../src/server/authz/peerContext.ts"),
|
||||
"utf8"
|
||||
);
|
||||
// Regression guard: a prior fix read the client-controlled Host header for the
|
||||
// LOCAL_ONLY decision, letting `Host: 127.0.0.1` bypass the gate. Locality must
|
||||
// come from the token-stamped peer IP instead.
|
||||
for (const [name, text] of [
|
||||
["management.ts", src],
|
||||
["peerContext.ts", peerSrc],
|
||||
] as const) {
|
||||
assert.ok(
|
||||
!text.includes('get?.("host")') && !text.includes('get("host")'),
|
||||
`${name} must NOT read the Host header for locality`
|
||||
);
|
||||
}
|
||||
assert.ok(
|
||||
!src.includes('get?.("host")') && !src.includes('get("host")'),
|
||||
"requestPeerAddress must NOT read the Host header"
|
||||
);
|
||||
assert.ok(
|
||||
src.includes("resolveStampedPeer") && src.includes("PEER_IP_HEADER"),
|
||||
peerSrc.includes("resolveStampedPeer") && peerSrc.includes("PEER_IP_HEADER"),
|
||||
"requestPeerAddress must resolve the trusted token-stamped peer IP"
|
||||
);
|
||||
// Positive anchor: management.ts must still route its locality decision through
|
||||
// the shared helpers, so this guard cannot pass by the policy quietly growing its
|
||||
// own Host-based path again.
|
||||
assert.ok(
|
||||
src.includes("peerContext") && src.includes("isLoopbackRequest"),
|
||||
"management policy must delegate locality to authz/peerContext"
|
||||
);
|
||||
});
|
||||
|
||||
// ── resolveStampedPeer: the auth boundary that replaces Host-header trust ──
|
||||
|
||||
Reference in New Issue
Block a user