fix(homepage): check allowlisted private service reachability (#1479)

* fix(homepage): check allowlisted private service reachability

* test(css): exercise the configured PostCSS plugin chain
This commit is contained in:
ZacharyZcR authored and GitHub committed 2026-09-27 00:11:41 -05:00
1 parent 3b9a1bacf0
commit 2824b49ba9
11 files changed
+232 -10

No files matched your search

+1
View File
@@ -173,6 +173,7 @@ export function createPluginFetch(deps: Deps): PluginFetch {
url,
{
method: init.method ?? "GET",
redirect: init.redirect,
headers: init.headers,
body: init.body,
signal: controller.signal,
@@ -26,6 +26,7 @@ const state = vi.hoisted(() => ({
fetchCalls: [] as Array<{ url: string; allowlist: readonly string[] }>,
lastTls: null as Record<string, unknown> | null,
lastSignal: null as AbortSignal | null,
lastRedirect: undefined as RequestRedirect | undefined,
}));
vi.mock("../../database/repositories/factory.js", () => ({
@@ -66,6 +67,7 @@ vi.mock("../../utils/safe-outbound-fetch.js", () => ({
tls: Record<string, unknown> = {},
) => {
state.lastTls = tls;
state.lastRedirect = init.redirect;
state.lastSignal = init.signal ?? null;
state.fetchCalls.push({ url, allowlist });
return new Response("ok", { status: 200 });
@@ -256,6 +258,14 @@ describe("ctx.notify", () => {
});
describe("ctx.fetch", () => {
it("forwards manual redirect handling without enabling redirect following", async () => {
const ctx = contextFor(["network:outbound"]);
await ctx.fetch("https://example.com", { redirect: "manual" });
expect(state.lastRedirect).toBe("manual");
await ctx.fetch("https://example.com");
expect(state.lastRedirect).toBeUndefined();
});
it("refuses without network:outbound", async () => {
const ctx = contextFor([]);
await expect(ctx.fetch("https://example.com")).rejects.toThrow(
@@ -281,3 +281,38 @@ describe("safeOutboundFetch", () => {
}
}, 10_000);
});
describe("redirect handling", () => {
it.each([undefined, "error", "follow", "manual"] as const)(
"never follows redirects with mode %s",
async (redirect) => {
const paths: string[] = [];
const server = createServer((req, res) => {
paths.push(req.url!);
res.writeHead(302, { Location: "/private-target" });
res.end();
});
await new Promise<void>((resolve) =>
server.listen(0, "127.0.0.1", resolve),
);
const { port } = server.address() as AddressInfo;
try {
const response = safeOutboundFetch(
`http://127.0.0.1:${port}/`,
{ redirect },
["127.0.0.1"],
);
if (redirect === "manual") {
const result = await response;
expect(result.status).toBe(302);
await result.body?.cancel();
} else {
await expect(response).rejects.toThrow();
}
expect(paths).toEqual(["/"]);
} finally {
await new Promise<void>((resolve) => server.close(() => resolve()));
}
},
);
});
+2 -2
View File
@@ -222,7 +222,7 @@ export async function safeOutboundFetch(
return (await undiciFetch(url.toString(), {
...options,
dispatcher: proxy as unknown as Dispatcher,
redirect: "error",
redirect: options.redirect === "manual" ? "manual" : "error",
} as never)) as unknown as Response;
}
@@ -240,7 +240,7 @@ export async function safeOutboundFetch(
const response = await undiciFetch(url.toString(), {
...options,
dispatcher,
redirect: "error",
redirect: options.redirect === "manual" ? "manual" : "error",
});
// close() waits for the body, which the caller reads after we return.
dispatcher.close().catch(() => {});