test(security): inventory guard for the SSRF requirePublicHost pins

Source-grep guard asserting the openai/anthropic/geo outbound sites keep
the unconditional DNS-rebinding pin and the local AI client keeps its
conditional LAN escape hatch — so a future edit can't silently re-open
the SSRF surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.8 committed 2026-06-04 12:23:43 +02:00
1 parent bd61374351
commit 38ef581e7a
1 file changed
+46
@@ -0,0 +1,46 @@
/**
* v1.11.2 — SSRF DNS-rebinding pin inventory.
*
* Source-text guard (same approach as the queue-registration + coach-gate
* inventory tests): the outbound `safeFetch` sites whose host is user- or
* operator-controlled MUST pass `requirePublicHost: true`, which wires both
* the input-time `isPublicUrl` check and the connect-time DNS-rebinding pin.
* A future edit that drops the pin (re-opening the SSRF surface) fails here
* instead of shipping silently.
*
* The LOCAL AI client is the deliberate exception: it stays CONDITIONAL
* (`requirePublicHost: !allowPrivate`) so an operator can opt into LAN hosts
* via `ALLOW_LOCAL_AI_PRIVATE_HOSTS`. We assert it keeps that conditional
* shape — never an unconditional `true` (which would break LAN local models)
* and never absent (which would re-open it for the default case).
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
const SRC = join(__dirname, "..");
const read = (rel: string) => readFileSync(join(SRC, rel), "utf8");
describe("SSRF requirePublicHost pin inventory", () => {
it("openai-client pins the BYO base-URL outbound unconditionally", () => {
expect(read("ai/openai-client.ts")).toMatch(/requirePublicHost:\s*true/);
});
it("anthropic-client pins the BYO base-URL outbound unconditionally", () => {
expect(read("ai/anthropic-client.ts")).toMatch(/requirePublicHost:\s*true/);
});
it("geo lookup pins the operator IP_GEO_LOOKUP_URL outbound", () => {
expect(read("geo.ts")).toMatch(/requirePublicHost:\s*true/);
});
it("local AI client keeps the pin CONDITIONAL (LAN escape hatch)", () => {
const src = read("ai/local-client.ts");
// Must carry the conditional form …
expect(src).toMatch(/requirePublicHost:\s*!?\s*allowPrivate/);
// … and must NOT be hardened to an unconditional true (that would break
// a deliberately-private LAN local model).
expect(src).not.toMatch(/requirePublicHost:\s*true/);
});
});