From 6d60db19510896559214094cecfcc262b76e8a33 Mon Sep 17 00:00:00 2001 From: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Date: Mon, 2 Nov 2026 02:52:20 +0800 Subject: [PATCH] fix(test): restrict storage fixture to loopback (#8582) --- scripts/lib/browser-scenario-storage-e2e.mjs | 2 +- .../browserStorageListenerScope.test.ts | 36 +++++++++++++++++++ tests/fixtures/browserFixtureHttpTargets.mjs | 22 ++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/boundary/browser/browserStorageListenerScope.test.ts diff --git a/scripts/lib/browser-scenario-storage-e2e.mjs b/scripts/lib/browser-scenario-storage-e2e.mjs index 11065159d6..ef352f16ac 100644 --- a/scripts/lib/browser-scenario-storage-e2e.mjs +++ b/scripts/lib/browser-scenario-storage-e2e.mjs @@ -171,7 +171,7 @@ export const startStorageSite = async () => { response.end(storagePage(kind, port)); }, ); - server.listen(0, "::"); + server.listen(0, "127.0.0.1"); await once(server, "listening"); port = server.address().port; return { diff --git a/tests/boundary/browser/browserStorageListenerScope.test.ts b/tests/boundary/browser/browserStorageListenerScope.test.ts new file mode 100644 index 0000000000..8149ac3cf0 --- /dev/null +++ b/tests/boundary/browser/browserStorageListenerScope.test.ts @@ -0,0 +1,36 @@ +import { networkInterfaces } from "node:os"; +import { fileURLToPath } from "node:url"; +import { expect } from "vitest"; + +import { processTest } from "../../support/process/processFixture.js"; + +const hasNonLoopbackIpv4 = Object.values(networkInterfaces()) + .flatMap((entries) => entries ?? []) + .some((entry) => entry.family === "IPv4" && !entry.internal); + +processTest.skipIf(!hasNonLoopbackIpv4)( + "storage fixture serves localhost and refuses this host's non-loopback interface", + async ({ processes }) => { + const result = await processes.run( + process.execPath, + [ + fileURLToPath( + new URL( + "../../fixtures/browserFixtureHttpTargets.mjs", + import.meta.url, + ), + ), + "storage", + "listener-scope", + ], + { timeoutMs: 15_000 }, + ); + expect(result.signal, result.stderr).toBeNull(); + expect(result.exitCode, result.stderr).toBe(0); + expect(result.stderr).toBe(""); + expect(result.stdout).toBe( + "storage: localhost served; non-loopback connection refused\n" + + "storage: target routing, invalid-target recovery and physical close passed\n", + ); + }, +); diff --git a/tests/fixtures/browserFixtureHttpTargets.mjs b/tests/fixtures/browserFixtureHttpTargets.mjs index 530665a150..022e904f56 100644 --- a/tests/fixtures/browserFixtureHttpTargets.mjs +++ b/tests/fixtures/browserFixtureHttpTargets.mjs @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import { once } from "node:events"; import { createServer, request } from "node:http"; +import { networkInterfaces } from "node:os"; import { closeVerifierResources, withVerifierCleanup, @@ -106,6 +107,27 @@ await withVerifierCleanup(async () => { else assert(reply.body.includes('id="main-report"')); }; await checkOrdinary(); + if (process.argv[3] === "listener-scope") { + assert.equal(kind, "storage"); + const named = new URL(origin); + named.hostname = "localhost"; + const localhost = await send(named.href, ordinary); + assert.equal(localhost.status, 200); + assert(localhost.body.includes('id="main-report"')); + const address = Object.values(networkInterfaces()) + .flatMap((entries) => entries ?? []) + .find((entry) => entry.family === "IPv4" && !entry.internal); + assert( + address, + "Listener scope probe requires an owned non-loopback IPv4 interface", + ); + const nonLoopback = new URL(origin); + nonLoopback.hostname = address.address; + await assert.rejects(send(nonLoopback.href, ordinary), { + code: "ECONNREFUSED", + }); + console.log("storage: localhost served; non-loopback connection refused"); + } for (const target of [ "http://[", "ftp://127.0.0.1/probe",