From 54836d208cbdf2d06053757872c933300ca4061c Mon Sep 17 00:00:00 2001 From: Jun Penn Date: Tue, 8 Sep 2026 20:56:34 +0800 Subject: [PATCH 1/3] test: verify authorized live VLESS egress --- docs/live-vless-verification.md | 40 ++++++++++++ docs/live-vless-verification.test.mjs | 37 +++++++++++ package.json | 3 +- scripts/live-vless.mjs | 90 +++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 docs/live-vless-verification.md create mode 100644 docs/live-vless-verification.test.mjs create mode 100644 scripts/live-vless.mjs diff --git a/docs/live-vless-verification.md b/docs/live-vless-verification.md new file mode 100644 index 0000000..c761882 --- /dev/null +++ b/docs/live-vless-verification.md @@ -0,0 +1,40 @@ +# Authorized live VLESS verification + +This test is deliberately separate from secretless PR CI. `pnpm verify` never invokes it, and the +release workflow never receives its credentials. Never print or record the subscription URL, node +address, UUID, authentication material, or observed IP address. + +## Reproduce + +Install the repository-pinned official Mihomo asset with `egresskit runtime install`, then provide +the resulting executable path, an authorized HTTPS subscription, and an HTTPS JSON target whose +response has an `ip` field: + +```sh +export EGRESSKIT_MIHOMO_BINARY=/private/path/to/mihomo +export EGRESSKIT_LIVE_SUBSCRIPTION_URL='set-in-your-secret-store' +export EGRESSKIT_LIVE_TARGET_URL='https://your-authorized-ip-echo.example/json' +pnpm test:live:vless +``` + +The runner uses an isolated temporary state directory, generated one-run admin and proxy tokens, +the real `egressd` and `egresskit` entry points, and the EgressKit proxy as the single egress +endpoint. It imports the remote subscription, lets official Mihomo check and apply it, makes a +direct target request, then makes the same request through an authenticated CONNECT tunnel. It +reports only four independent outcomes: local implementation, Mihomo acceptance, target +reachability, and real exit verification. Real exit verification passes only when the target +returns a valid proxy-observed IP distinct from the direct IP. Temporary state is removed. + +## Evidence: 2026-09-08 + +- EgressKit commit under test: `4e97483` (the merged Issue #22 baseline). +- Platform: Darwin 25.6.0 arm64; Node.js v26.8.1. +- Official runtime: Mihomo Meta v1.19.30, Darwin arm64; repository-pinned SHA-256 verified. +- Configuration category: remote Mihomo YAML containing 170 VLESS nodes using TCP transport. +- Local implementation: passed; daemon started and the subscription operation completed. +- Mihomo acceptance: passed; the official runtime checked and applied the generated configuration. +- Target reachability: passed through the authenticated EgressKit CONNECT endpoint. +- Real exit verification: passed; the target observed a valid IP distinct from the direct exit. + +No subscription URL, node secret, node endpoint, UUID, token, direct IP, or observed exit IP is +stored in this evidence. diff --git a/docs/live-vless-verification.test.mjs b/docs/live-vless-verification.test.mjs new file mode 100644 index 0000000..9214698 --- /dev/null +++ b/docs/live-vless-verification.test.mjs @@ -0,0 +1,37 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; + +const root = new URL("../", import.meta.url); + +test("authorized VLESS verification is documented and isolated from secretless CI", async () => { + const [documentation, packageJson, workflow] = await Promise.all([ + readFile(new URL("live-vless-verification.md", import.meta.url), "utf8"), + readFile(new URL("package.json", root), "utf8").then(JSON.parse), + readFile(new URL(".github/workflows/release.yml", root), "utf8"), + ]); + + assert.match(documentation, /EGRESSKIT_LIVE_SUBSCRIPTION_URL/); + assert.match(documentation, /local implementation/i); + assert.match(documentation, /Mihomo acceptance/i); + assert.match(documentation, /target reachability/i); + assert.match(documentation, /real exit verification/i); + assert.match(documentation, /never (?:print|record).*subscription/i); + assert.equal(packageJson.scripts["test:live:vless"], "node scripts/live-vless.mjs"); + assert.doesNotMatch(packageJson.scripts.verify, /test:live:vless/); + assert.doesNotMatch(workflow, /EGRESSKIT_LIVE_SUBSCRIPTION_URL|test:live:vless/); +}); + +test("the checked-in evidence is reproducible metadata without node secrets", async () => { + const documentation = await readFile( + new URL("live-vless-verification.md", import.meta.url), + "utf8", + ); + + assert.match(documentation, /2026-09-08/); + assert.match(documentation, /Mihomo Meta v1\.19\.30/); + assert.match(documentation, /Darwin 25\.6\.0 arm64/); + assert.match(documentation, /170 VLESS nodes/); + assert.doesNotMatch(documentation, /\/sub\/[a-z0-9]+/i); + assert.doesNotMatch(documentation, /uuid\s*[:=]/i); +}); diff --git a/package.json b/package.json index 33ddcfb..322121d 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,10 @@ "check": "pnpm typecheck", "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", - "lint:root": "biome check package.json pnpm-workspace.yaml turbo.json biome.json tsconfig.base.json .github/workflows/release.test.mjs docker/Dockerfile.test.mjs docs/deployment.test.mjs release", + "lint:root": "biome check package.json pnpm-workspace.yaml turbo.json biome.json tsconfig.base.json .github/workflows/release.test.mjs docker/Dockerfile.test.mjs docs/*.test.mjs release scripts", "prepare": "husky", "test": "pnpm --filter @egresskit/egressd test && node --test .github/workflows/*.test.mjs apps/egressd/*.test.mjs docker/*.test.mjs docs/*.test.mjs release/*.test.mjs", + "test:live:vless": "node scripts/live-vless.mjs", "typecheck": "turbo run typecheck", "verify": "pnpm lint && pnpm typecheck && pnpm test" }, diff --git a/scripts/live-vless.mjs b/scripts/live-vless.mjs new file mode 100644 index 0000000..b21c3b8 --- /dev/null +++ b/scripts/live-vless.mjs @@ -0,0 +1,90 @@ +import { spawn, spawnSync } from "node:child_process"; +import { randomBytes } from "node:crypto"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const required = (name) => { + const value = process.env[name]; + if (!value) throw new Error(`${name} is required for the authorized live test`); + return value; +}; + +const run = (command, args, options = {}) => { + const result = spawnSync(command, args, { encoding: "utf8", ...options }); + if (result.status !== 0) throw new Error(`${command} failed during the live test`); + return result.stdout; +}; + +const waitForStart = (child) => + new Promise((resolve, reject) => { + let output = ""; + const timeout = setTimeout(() => reject(new Error("egressd startup timed out")), 10_000); + child.stdout.on("data", (chunk) => { + output += chunk; + const line = output.split("\n").find((entry) => entry.includes('"egressd.started"')); + if (!line) return; + clearTimeout(timeout); + resolve(JSON.parse(line)); + }); + child.once("exit", () => reject(new Error("egressd exited before startup"))); + }); + +const subscriptionUrl = required("EGRESSKIT_LIVE_SUBSCRIPTION_URL"); +const targetUrl = required("EGRESSKIT_LIVE_TARGET_URL"); +const mihomoBinary = required("EGRESSKIT_MIHOMO_BINARY"); +const stateDirectory = await mkdtemp(join(tmpdir(), "egresskit-live-vless-")); +const adminToken = randomBytes(24).toString("hex"); +const proxyToken = randomBytes(24).toString("hex"); +let daemon; + +try { + run("pnpm", ["--filter", "@egresskit/egressd", "build"]); + daemon = spawn("node", ["apps/egressd/dist/cli.js"], { + env: { + ...process.env, + EGRESSKIT_ADMIN_TOKEN: adminToken, + EGRESSKIT_MIHOMO_BINARY: mihomoBinary, + EGRESSKIT_PORT: "0", + EGRESSKIT_PROXY_TOKEN: proxyToken, + EGRESSKIT_STATE_DIRECTORY: stateDirectory, + }, + stdio: ["ignore", "pipe", "pipe"], + }); + const address = await waitForStart(daemon); + run("node", ["apps/egressd/dist/control-cli-bin.js", "subscription", "add", "--redact"], { + env: { + ...process.env, + EGRESSKIT_ADMIN_TOKEN: adminToken, + EGRESSKIT_STATE_DIRECTORY: stateDirectory, + }, + input: subscriptionUrl, + }); + const direct = JSON.parse(run("curl", ["--fail", "--silent", "--show-error", targetUrl])).ip; + const observed = JSON.parse( + run("curl", [ + "--fail", + "--silent", + "--show-error", + "--max-time", + "30", + "--proxy", + `http://127.0.0.1:${address.port}`, + "--proxy-user", + `rotate:${proxyToken}`, + targetUrl, + ]), + ).ip; + if (typeof direct !== "string" || typeof observed !== "string" || direct === observed) { + throw new Error("the target did not observe a distinct valid proxy exit"); + } + process.stdout.write( + `${JSON.stringify({ localImplementation: "passed", mihomoAcceptance: "passed", targetReachability: "passed", realExitVerification: "passed" })}\n`, + ); +} finally { + if (daemon && daemon.exitCode === null) { + daemon.kill("SIGTERM"); + await new Promise((resolve) => daemon.once("exit", resolve)); + } + await rm(stateDirectory, { force: true, recursive: true }); +} From 857d941b12993f765a0e96093fe63b7149c83d2f Mon Sep 17 00:00:00 2001 From: Jun Penn Date: Tue, 8 Sep 2026 21:04:12 +0800 Subject: [PATCH 2/3] fix: harden live verification runner --- package.json | 2 +- scripts/live-vless.mjs | 219 +++++++++++++++++++++++++----------- scripts/live-vless.test.mjs | 23 ++++ 3 files changed, 175 insertions(+), 69 deletions(-) create mode 100644 scripts/live-vless.test.mjs diff --git a/package.json b/package.json index 322121d..3f0432c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "lint:fix": "turbo run lint:fix", "lint:root": "biome check package.json pnpm-workspace.yaml turbo.json biome.json tsconfig.base.json .github/workflows/release.test.mjs docker/Dockerfile.test.mjs docs/*.test.mjs release scripts", "prepare": "husky", - "test": "pnpm --filter @egresskit/egressd test && node --test .github/workflows/*.test.mjs apps/egressd/*.test.mjs docker/*.test.mjs docs/*.test.mjs release/*.test.mjs", + "test": "pnpm --filter @egresskit/egressd test && node --test .github/workflows/*.test.mjs apps/egressd/*.test.mjs docker/*.test.mjs docs/*.test.mjs release/*.test.mjs scripts/*.test.mjs", "test:live:vless": "node scripts/live-vless.mjs", "typecheck": "turbo run typecheck", "verify": "pnpm lint && pnpm typecheck && pnpm test" diff --git a/scripts/live-vless.mjs b/scripts/live-vless.mjs index b21c3b8..24262b5 100644 --- a/scripts/live-vless.mjs +++ b/scripts/live-vless.mjs @@ -1,90 +1,173 @@ import { spawn, spawnSync } from "node:child_process"; import { randomBytes } from "node:crypto"; import { mkdtemp, rm } from "node:fs/promises"; +import { isIP } from "node:net"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { pathToFileURL } from "node:url"; -const required = (name) => { +export function cleanChildEnvironment(environment) { + return Object.fromEntries( + Object.entries(environment).filter(([name]) => !name.startsWith("EGRESSKIT_LIVE_")), + ); +} + +export function validateTargetUrl(value) { + const target = new URL(value); + if (target.protocol !== "https:") throw new Error("live target must use HTTPS"); + return target; +} + +export function isVerifiedExit(direct, observed) { + return isIP(direct) !== 0 && isIP(observed) !== 0 && direct !== observed; +} + +function required(name) { const value = process.env[name]; if (!value) throw new Error(`${name} is required for the authorized live test`); return value; -}; +} -const run = (command, args, options = {}) => { - const result = spawnSync(command, args, { encoding: "utf8", ...options }); +function run(command, args, options = {}) { + const result = spawnSync(command, args, { + encoding: "utf8", + maxBuffer: 1024 * 1024, + timeout: 60_000, + ...options, + }); if (result.status !== 0) throw new Error(`${command} failed during the live test`); return result.stdout; -}; +} -const waitForStart = (child) => - new Promise((resolve, reject) => { - let output = ""; +function waitForStart(child) { + return new Promise((resolve, reject) => { + let buffered = ""; const timeout = setTimeout(() => reject(new Error("egressd startup timed out")), 10_000); - child.stdout.on("data", (chunk) => { - output += chunk; - const line = output.split("\n").find((entry) => entry.includes('"egressd.started"')); - if (!line) return; + const fail = () => { clearTimeout(timeout); - resolve(JSON.parse(line)); + reject(new Error("egressd exited before startup")); + }; + child.once("error", fail); + child.once("exit", fail); + child.stderr.resume(); + child.stdout.on("data", (chunk) => { + buffered += chunk; + const lines = buffered.split("\n"); + buffered = lines.pop() ?? ""; + for (const line of lines) { + if (!line.includes('"egressd.started"')) continue; + try { + const address = JSON.parse(line); + if (!Number.isInteger(address.port)) throw new Error("invalid port"); + clearTimeout(timeout); + child.off("error", fail); + child.off("exit", fail); + resolve(address); + } catch { + clearTimeout(timeout); + reject(new Error("egressd emitted an invalid startup event")); + } + return; + } }); - child.once("exit", () => reject(new Error("egressd exited before startup"))); }); +} -const subscriptionUrl = required("EGRESSKIT_LIVE_SUBSCRIPTION_URL"); -const targetUrl = required("EGRESSKIT_LIVE_TARGET_URL"); -const mihomoBinary = required("EGRESSKIT_MIHOMO_BINARY"); -const stateDirectory = await mkdtemp(join(tmpdir(), "egresskit-live-vless-")); -const adminToken = randomBytes(24).toString("hex"); -const proxyToken = randomBytes(24).toString("hex"); -let daemon; +async function stopDaemon(child) { + if (!child || child.exitCode !== null) return; + const exited = new Promise((resolve) => child.once("exit", resolve)); + child.kill("SIGTERM"); + const stopped = await Promise.race([ + exited.then(() => true), + new Promise((resolve) => setTimeout(() => resolve(false), 3_000)), + ]); + if (!stopped && child.exitCode === null) { + child.kill("SIGKILL"); + await exited; + } +} -try { - run("pnpm", ["--filter", "@egresskit/egressd", "build"]); - daemon = spawn("node", ["apps/egressd/dist/cli.js"], { - env: { - ...process.env, - EGRESSKIT_ADMIN_TOKEN: adminToken, - EGRESSKIT_MIHOMO_BINARY: mihomoBinary, - EGRESSKIT_PORT: "0", - EGRESSKIT_PROXY_TOKEN: proxyToken, - EGRESSKIT_STATE_DIRECTORY: stateDirectory, - }, - stdio: ["ignore", "pipe", "pipe"], - }); - const address = await waitForStart(daemon); - run("node", ["apps/egressd/dist/control-cli-bin.js", "subscription", "add", "--redact"], { - env: { - ...process.env, - EGRESSKIT_ADMIN_TOKEN: adminToken, - EGRESSKIT_STATE_DIRECTORY: stateDirectory, - }, - input: subscriptionUrl, - }); - const direct = JSON.parse(run("curl", ["--fail", "--silent", "--show-error", targetUrl])).ip; - const observed = JSON.parse( - run("curl", [ - "--fail", - "--silent", - "--show-error", - "--max-time", - "30", - "--proxy", - `http://127.0.0.1:${address.port}`, - "--proxy-user", - `rotate:${proxyToken}`, - targetUrl, - ]), - ).ip; - if (typeof direct !== "string" || typeof observed !== "string" || direct === observed) { - throw new Error("the target did not observe a distinct valid proxy exit"); +function parseObservedIp(output) { + try { + const value = JSON.parse(output).ip; + return typeof value === "string" ? value : undefined; + } catch { + throw new Error("target returned invalid JSON"); } - process.stdout.write( - `${JSON.stringify({ localImplementation: "passed", mihomoAcceptance: "passed", targetReachability: "passed", realExitVerification: "passed" })}\n`, - ); -} finally { - if (daemon && daemon.exitCode === null) { - daemon.kill("SIGTERM"); - await new Promise((resolve) => daemon.once("exit", resolve)); +} + +export async function main() { + const subscriptionUrl = required("EGRESSKIT_LIVE_SUBSCRIPTION_URL"); + const targetUrl = validateTargetUrl(required("EGRESSKIT_LIVE_TARGET_URL")).href; + const mihomoBinary = required("EGRESSKIT_MIHOMO_BINARY"); + const stateDirectory = await mkdtemp(join(tmpdir(), "egresskit-live-vless-")); + const adminToken = randomBytes(24).toString("hex"); + const proxyToken = randomBytes(24).toString("hex"); + const childEnvironment = cleanChildEnvironment(process.env); + let daemon; + const interrupt = () => daemon?.kill("SIGTERM"); + process.once("SIGINT", interrupt); + process.once("SIGTERM", interrupt); + + try { + run("pnpm", ["--filter", "@egresskit/egressd", "build"], { env: childEnvironment }); + daemon = spawn("node", ["apps/egressd/dist/cli.js"], { + env: { + ...childEnvironment, + EGRESSKIT_ADMIN_TOKEN: adminToken, + EGRESSKIT_MIHOMO_BINARY: mihomoBinary, + EGRESSKIT_PORT: "0", + EGRESSKIT_PROXY_TOKEN: proxyToken, + EGRESSKIT_STATE_DIRECTORY: stateDirectory, + }, + stdio: ["ignore", "pipe", "pipe"], + }); + const address = await waitForStart(daemon); + run("node", ["apps/egressd/dist/control-cli-bin.js", "subscription", "add", "--redact"], { + env: { + ...childEnvironment, + EGRESSKIT_ADMIN_TOKEN: adminToken, + EGRESSKIT_STATE_DIRECTORY: stateDirectory, + }, + input: subscriptionUrl, + }); + const curlBase = ["--fail", "--silent", "--show-error", "--max-time", "30"]; + const direct = parseObservedIp( + run("curl", [...curlBase, targetUrl], { env: childEnvironment }), + ); + const observed = parseObservedIp( + run( + "curl", + [ + ...curlBase, + "--noproxy", + "", + "--proxy", + `http://127.0.0.1:${address.port}`, + "--proxy-user", + `rotate:${proxyToken}`, + targetUrl, + ], + { env: childEnvironment }, + ), + ); + if (!isVerifiedExit(direct, observed)) { + throw new Error("the target did not observe a distinct valid proxy exit"); + } + process.stdout.write( + `${JSON.stringify({ localImplementation: "passed", mihomoAcceptance: "passed", targetReachability: "passed", realExitVerification: "passed" })}\n`, + ); + } finally { + process.off("SIGINT", interrupt); + process.off("SIGTERM", interrupt); + await stopDaemon(daemon); + await rm(stateDirectory, { force: true, recursive: true }); } - await rm(stateDirectory, { force: true, recursive: true }); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + main().catch((error) => { + process.stderr.write(`${error instanceof Error ? error.message : "live test failed"}\n`); + process.exitCode = 1; + }); } diff --git a/scripts/live-vless.test.mjs b/scripts/live-vless.test.mjs new file mode 100644 index 0000000..78aa9d5 --- /dev/null +++ b/scripts/live-vless.test.mjs @@ -0,0 +1,23 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { cleanChildEnvironment, isVerifiedExit, validateTargetUrl } from "./live-vless.mjs"; + +test("live target must be HTTPS and both observed values must be distinct IP addresses", () => { + assert.throws(() => validateTargetUrl("http://target.example/json"), /HTTPS/); + assert.equal(validateTargetUrl("https://target.example/json").protocol, "https:"); + assert.equal(isVerifiedExit("direct", "proxy"), false); + assert.equal(isVerifiedExit("192.0.2.1", "192.0.2.1"), false); + assert.equal(isVerifiedExit("192.0.2.1", "2001:db8::1"), true); +}); + +test("live secrets are not inherited by daemon, CLI, Mihomo, or curl", () => { + assert.deepEqual( + cleanChildEnvironment({ + EGRESSKIT_LIVE_SUBSCRIPTION_URL: "secret", + EGRESSKIT_LIVE_TARGET_URL: "https://target.example", + PATH: "/bin", + }), + { PATH: "/bin" }, + ); +}); From 8df170f2f620bb4abac2444b35ba93e5c66e68fc Mon Sep 17 00:00:00 2001 From: Jun Penn Date: Tue, 8 Sep 2026 21:07:31 +0800 Subject: [PATCH 3/3] fix: isolate live verification environment --- scripts/live-vless.mjs | 30 +++++++++++++++++++++++------- scripts/live-vless.test.mjs | 2 ++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/scripts/live-vless.mjs b/scripts/live-vless.mjs index 24262b5..22166a8 100644 --- a/scripts/live-vless.mjs +++ b/scripts/live-vless.mjs @@ -8,7 +8,7 @@ import { pathToFileURL } from "node:url"; export function cleanChildEnvironment(environment) { return Object.fromEntries( - Object.entries(environment).filter(([name]) => !name.startsWith("EGRESSKIT_LIVE_")), + Object.entries(environment).filter(([name]) => !name.startsWith("EGRESSKIT_")), ); } @@ -74,16 +74,17 @@ function waitForStart(child) { } async function stopDaemon(child) { - if (!child || child.exitCode !== null) return; + if (!child || child.exitCode !== null || child.signalCode !== null) return; const exited = new Promise((resolve) => child.once("exit", resolve)); - child.kill("SIGTERM"); + if (child.exitCode !== null || child.signalCode !== null) return; + if (!child.kill("SIGTERM")) return; const stopped = await Promise.race([ exited.then(() => true), new Promise((resolve) => setTimeout(() => resolve(false), 3_000)), ]); if (!stopped && child.exitCode === null) { child.kill("SIGKILL"); - await exited; + await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 1_000))]); } } @@ -105,12 +106,20 @@ export async function main() { const proxyToken = randomBytes(24).toString("hex"); const childEnvironment = cleanChildEnvironment(process.env); let daemon; - const interrupt = () => daemon?.kill("SIGTERM"); + let cancelled = false; + const interrupt = () => { + cancelled = true; + daemon?.kill("SIGTERM"); + }; + const ensureNotCancelled = () => { + if (cancelled) throw new Error("live test interrupted"); + }; process.once("SIGINT", interrupt); process.once("SIGTERM", interrupt); try { run("pnpm", ["--filter", "@egresskit/egressd", "build"], { env: childEnvironment }); + ensureNotCancelled(); daemon = spawn("node", ["apps/egressd/dist/cli.js"], { env: { ...childEnvironment, @@ -123,6 +132,7 @@ export async function main() { stdio: ["ignore", "pipe", "pipe"], }); const address = await waitForStart(daemon); + ensureNotCancelled(); run("node", ["apps/egressd/dist/control-cli-bin.js", "subscription", "add", "--redact"], { env: { ...childEnvironment, @@ -131,10 +141,12 @@ export async function main() { }, input: subscriptionUrl, }); + ensureNotCancelled(); const curlBase = ["--fail", "--silent", "--show-error", "--max-time", "30"]; const direct = parseObservedIp( run("curl", [...curlBase, targetUrl], { env: childEnvironment }), ); + ensureNotCancelled(); const observed = parseObservedIp( run( "curl", @@ -151,6 +163,7 @@ export async function main() { { env: childEnvironment }, ), ); + ensureNotCancelled(); if (!isVerifiedExit(direct, observed)) { throw new Error("the target did not observe a distinct valid proxy exit"); } @@ -160,8 +173,11 @@ export async function main() { } finally { process.off("SIGINT", interrupt); process.off("SIGTERM", interrupt); - await stopDaemon(daemon); - await rm(stateDirectory, { force: true, recursive: true }); + try { + await stopDaemon(daemon); + } finally { + await rm(stateDirectory, { force: true, recursive: true }); + } } } diff --git a/scripts/live-vless.test.mjs b/scripts/live-vless.test.mjs index 78aa9d5..d141d9a 100644 --- a/scripts/live-vless.test.mjs +++ b/scripts/live-vless.test.mjs @@ -16,6 +16,8 @@ test("live secrets are not inherited by daemon, CLI, Mihomo, or curl", () => { cleanChildEnvironment({ EGRESSKIT_LIVE_SUBSCRIPTION_URL: "secret", EGRESSKIT_LIVE_TARGET_URL: "https://target.example", + EGRESSKIT_MIHOMO_HTTP_LISTENER: "http://127.0.0.1:9999", + EGRESSKIT_CONTROL_SOCKET: "/wrong/socket", PATH: "/bin", }), { PATH: "/bin" },