From 5a3a82ad7ed1b0fb39bece10d8521fee6b123848 Mon Sep 17 00:00:00 2001 From: SDS <209957663+dkitchell@users.noreply.github.com> Date: Thu, 20 Aug 2026 17:59:37 -0600 Subject: [PATCH] =?UTF-8?q?fix:=20CLI=20silently=20did=20nothing=20when=20?= =?UTF-8?q?invoked=20as=20a=20bin=20=E2=80=94=20a=20verifier=20failing=20O?= =?UTF-8?q?PEN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The entry guard: if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) Through a bin, process.argv[1] is the symlink npm creates (node_modules/.bin/verify) while import.meta.url is the resolved real path (node_modules/@certifieddata/verify/dist/cli.js). They never match, so main() was never called. The process started, printed nothing, and exited 0. ./node_modules/.bin/verify 00000000-0000-0000-0000-000000000000 → (no output) EXIT=0 node .../dist/cli.js 00000000-0000-0000-0000-000000000000 → ✗ NOT_FOUND EXIT=3 A nonsense receipt id returned SUCCESS through the documented invocation path. That is the worst failure mode a verifier can have: not a crash, not a false negative, but silent assent. Every consumer the exit codes exist for — `verify $ID && deploy`, CI gates, the audit pipeline in our own docs — got a pass for a receipt that was never checked. The previous exit-127 bug was loud and safe by comparison; this failed open. It shipped because it is invisible on Windows. npm writes .cmd shims there that pass the RESOLVED path as argv[1], so the comparison matched. On Linux and macOS npm writes a true symlink and it never did. Reproduced locally on Windows via a directory junction: EXIT=0 and zero bytes of output. Fixed by removing the guard entirely rather than repairing it. A realpathSync() comparison would also work, but it keeps a conditional on the critical path and every way that conditional can be wrong fails silently and open. This file is only ever an entry point — nothing in the repo imports it — so it just runs. Why no existing test caught it: every other test spawns `node dist/cli.js`, the resolved real path, which is the one invocation form no consumer uses. src/bin-invocation.test.ts adds five tests that go through the bin, and asserts NON-EMPTY OUTPUT as well as the exit code — the failure was a correct-looking exit code with nothing behind it, so a code-only assertion would have passed while the tool did nothing. The load-bearing test invokes cli.js through a symlinked directory, reproducing the argv[1]/import.meta.url divergence on every platform. The npm-install tests alone cannot catch this on Windows, and a guard that only works on some CI legs is half a guard. Verified both directions: that test FAILS against the old guard (exit 0, zero bytes) and passes against the fix (exit 3, NOT_FOUND). Verified through a real install of the packed tarball: valid -> EXIT=0 ✓ VALID tampered -> EXIT=1 ✗ INVALID nonsense -> EXIT=3 ✗ NOT_FOUND 70 tests pass, up from 65. Co-Authored-By: Claude Fable 5 --- dist/bin-invocation.test.d.ts | 37 ++++++ dist/bin-invocation.test.d.ts.map | 1 + dist/bin-invocation.test.js | 167 ++++++++++++++++++++++++ dist/bin-invocation.test.js.map | 1 + dist/cli.d.ts.map | 2 +- dist/cli.js | 54 +++++--- dist/cli.js.map | 2 +- package.json | 2 +- src/bin-invocation.test.ts | 204 ++++++++++++++++++++++++++++++ src/cli.ts | 54 +++++--- 10 files changed, 479 insertions(+), 45 deletions(-) create mode 100644 dist/bin-invocation.test.d.ts create mode 100644 dist/bin-invocation.test.d.ts.map create mode 100644 dist/bin-invocation.test.js create mode 100644 dist/bin-invocation.test.js.map create mode 100644 src/bin-invocation.test.ts diff --git a/dist/bin-invocation.test.d.ts b/dist/bin-invocation.test.d.ts new file mode 100644 index 0000000..8b43b3a --- /dev/null +++ b/dist/bin-invocation.test.d.ts @@ -0,0 +1,37 @@ +/** + * Invocation through the installed bin — the path every real user takes. + * + * This test exists because the CLI silently did nothing when run as a bin, and + * every other test in this repo missed it. They all spawn `node dist/cli.js`, + * the resolved real path, which is the one invocation form no consumer uses. + * + * The bug: + * + * if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) + * + * Through a bin, process.argv[1] is the symlink npm created + * (node_modules/.bin/verify) while import.meta.url is the resolved real path. + * They never match, so main() was never called — the process started, printed + * nothing, and exited 0. + * + * ./node_modules/.bin/verify 00000000-0000-0000-0000-000000000000 + * → (no output) EXIT=0 + * + * A nonsense receipt id returning success is the worst failure a verifier can + * have. Not a crash, not a false negative — silent assent. `verify $ID && deploy` + * passed for a receipt nobody checked. + * + * It was invisible on Windows, where npm writes .cmd shims that pass the real + * path as argv[1], so the comparison matched. On Linux and macOS npm writes a + * true symlink and it never did. + * + * ASSERTING THE OUTPUT IS NOT EMPTY MATTERS AS MUCH AS THE EXIT CODE. The + * failure mode was a correct-looking exit code with nothing behind it, so a test + * that only checked codes would have passed while the tool did nothing. + * + * The offline cases below are the load-bearing ones: `--version` and the no-args + * usage error both produce output and a specific code with no network, and both + * would have caught this. + */ +export {}; +//# sourceMappingURL=bin-invocation.test.d.ts.map \ No newline at end of file diff --git a/dist/bin-invocation.test.d.ts.map b/dist/bin-invocation.test.d.ts.map new file mode 100644 index 0000000..9851442 --- /dev/null +++ b/dist/bin-invocation.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"bin-invocation.test.d.ts","sourceRoot":"","sources":["../src/bin-invocation.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG"} \ No newline at end of file diff --git a/dist/bin-invocation.test.js b/dist/bin-invocation.test.js new file mode 100644 index 0000000..a231246 --- /dev/null +++ b/dist/bin-invocation.test.js @@ -0,0 +1,167 @@ +/** + * Invocation through the installed bin — the path every real user takes. + * + * This test exists because the CLI silently did nothing when run as a bin, and + * every other test in this repo missed it. They all spawn `node dist/cli.js`, + * the resolved real path, which is the one invocation form no consumer uses. + * + * The bug: + * + * if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) + * + * Through a bin, process.argv[1] is the symlink npm created + * (node_modules/.bin/verify) while import.meta.url is the resolved real path. + * They never match, so main() was never called — the process started, printed + * nothing, and exited 0. + * + * ./node_modules/.bin/verify 00000000-0000-0000-0000-000000000000 + * → (no output) EXIT=0 + * + * A nonsense receipt id returning success is the worst failure a verifier can + * have. Not a crash, not a false negative — silent assent. `verify $ID && deploy` + * passed for a receipt nobody checked. + * + * It was invisible on Windows, where npm writes .cmd shims that pass the real + * path as argv[1], so the comparison matched. On Linux and macOS npm writes a + * true symlink and it never did. + * + * ASSERTING THE OUTPUT IS NOT EMPTY MATTERS AS MUCH AS THE EXIT CODE. The + * failure mode was a correct-looking exit code with nothing behind it, so a test + * that only checked codes would have passed while the tool did nothing. + * + * The offline cases below are the load-bearing ones: `--version` and the no-args + * usage error both produce output and a specific code with no network, and both + * would have caught this. + */ +import { test, before, after } from "node:test"; +import assert from "node:assert/strict"; +import { execFileSync, spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, existsSync, readdirSync, symlinkSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; +const here = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(here, ".."); +const isWindows = process.platform === "win32"; +let sandbox = null; +let binPath = null; +before(() => { + try { + sandbox = mkdtempSync(join(tmpdir(), "cdverify-bin-")); + // Pack and install exactly what a consumer receives, rather than linking the + // working tree — npm link produces different bin plumbing. + const tarball = execFileSync("npm", ["pack", "--silent", "--pack-destination", sandbox], { + cwd: repoRoot, + encoding: "utf8", + shell: isWindows, + }) + .trim() + .split("\n") + .pop(); + execFileSync("npm", ["init", "-y"], { cwd: sandbox, stdio: "ignore", shell: isWindows }); + execFileSync("npm", ["install", join(sandbox, tarball)], { + cwd: sandbox, + stdio: "ignore", + shell: isWindows, + }); + const binDir = join(sandbox, "node_modules", ".bin"); + const candidate = join(binDir, isWindows ? "verify.cmd" : "verify"); + binPath = existsSync(candidate) ? candidate : null; + if (!binPath) { + throw new Error(`bin 'verify' not present after install. Contents: ${readdirSync(binDir).join(", ")}`); + } + } + catch (e) { + // Leave binPath null; every test below fails loudly rather than silently + // skipping, because a silent skip is the same class of problem as the bug. + // eslint-disable-next-line no-console + console.error(`[bin-invocation] setup failed: ${e.message}`); + } +}); +after(() => { + if (sandbox) + rmSync(sandbox, { recursive: true, force: true }); +}); +function runBin(args) { + assert.ok(binPath, "bin was not installed — setup failed, see stderr above"); + const r = spawnSync(binPath, args, { + encoding: "utf8", + env: { ...process.env, NO_COLOR: "1" }, + shell: isWindows, + }); + return { + stdout: r.stdout ?? "", + stderr: r.stderr ?? "", + code: r.status ?? -1, + combined: (r.stdout ?? "") + (r.stderr ?? ""), + }; +} +// ── Offline: these alone would have caught the bug ────────────────────────── +test("bin --version produces output and exits 0", () => { + const r = runBin(["--version"]); + // The silent-no-op produced exit 0 too, so the OUTPUT is the real assertion. + assert.notEqual(r.combined.trim(), "", "bin produced no output at all — it did not run"); + assert.match(r.stdout, /@certifieddata\/verify/); + assert.equal(r.code, 0); +}); +test("bin with no arguments is a usage error, exit 64 with output", () => { + // The decisive offline case: under the bug this exited 0 in silence. Correct + // behavior is a non-zero usage code AND an explanation. + const r = runBin([]); + assert.notEqual(r.combined.trim(), "", "bin produced no output at all — it did not run"); + assert.equal(r.code, 64, `expected USAGE=64, got ${r.code}. combined: ${r.combined}`); +}); +test("bin --help produces the help text and exits 0", () => { + const r = runBin(["--help"]); + assert.match(r.stdout, /Exit codes:/); + assert.equal(r.code, 0); +}); +// ── The mechanism itself, on every platform ───────────────────────────────── +// +// The npm-install tests above cannot catch this bug on Windows: npm writes .cmd +// shims there that pass the RESOLVED path as argv[1], so a broken guard still +// matches and the tests pass. That platform difference is precisely why the bug +// shipped, and a guard that only works on some CI legs is half a guard. +// +// So reproduce the condition directly: invoke cli.js through a symlinked +// directory. Node resolves symlinks for import.meta.url but argv[1] keeps the +// link path, which is exactly the divergence npm's bin symlink creates. +// Verified by hand: against the old guard this yields EXIT=0 and ZERO bytes; +// against the fix, EXIT=3 and a NOT_FOUND message. +test("invoking through a symlinked path still runs — no silent no-op", () => { + const dir = mkdtempSync(join(tmpdir(), "cdverify-link-")); + try { + const link = join(dir, "linked-dist"); + try { + // "junction" is the only form Windows allows without elevation; it is + // ignored on POSIX, where a normal directory symlink is created. + symlinkSync(join(here), link, "junction"); + } + catch (e) { + assert.fail(`could not create a symlink to exercise the bin path: ${e.message}. ` + + `This test must not be skipped — it is the only cross-platform guard against ` + + `the CLI silently doing nothing when invoked as a bin.`); + } + const r = spawnSync(process.execPath, [join(link, "cli.js"), "00000000-0000-0000-0000-000000000000"], { encoding: "utf8", env: { ...process.env, NO_COLOR: "1" } }); + const combined = (r.stdout ?? "") + (r.stderr ?? ""); + assert.notEqual(combined.trim(), "", "invoked through a symlink the CLI produced NO output — main() never ran, " + + "which is the fail-open bug this test exists to catch"); + assert.notEqual(r.status, 0, "a nonsense id returned SUCCESS through a symlinked path — failing open"); + } + finally { + rmSync(dir, { recursive: true, force: true }); + } +}); +// ── Network: the case the reviewer reproduced ─────────────────────────────── +test("bin fails closed on a nonsense id — never exit 0", async () => { + const r = runBin(["00000000-0000-0000-0000-000000000000"]); + if (/NETWORK|ENOTFOUND|ECONN|fetch failed/i.test(r.combined)) { + // A network failure must still not be success. Assert that much and stop. + assert.notEqual(r.code, 0, "network failure must not report success"); + return; + } + assert.notEqual(r.combined.trim(), "", "bin produced no output at all — it did not run"); + assert.notEqual(r.code, 0, "a nonsense receipt id returned SUCCESS through the bin — the verifier is failing open"); + assert.equal(r.code, 3, `expected MALFORMED=3, got ${r.code}. combined: ${r.combined}`); +}); +//# sourceMappingURL=bin-invocation.test.js.map \ No newline at end of file diff --git a/dist/bin-invocation.test.js.map b/dist/bin-invocation.test.js.map new file mode 100644 index 0000000..521c851 --- /dev/null +++ b/dist/bin-invocation.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"bin-invocation.test.js","sourceRoot":"","sources":["../src/bin-invocation.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/cli.d.ts.map b/dist/cli.d.ts.map index 07e0e28..31b199e 100644 --- a/dist/cli.d.ts.map +++ b/dist/cli.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAgEA,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAwG1D"} \ No newline at end of file +{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA+DA,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAwG1D"} \ No newline at end of file diff --git a/dist/cli.js b/dist/cli.js index 504fcce..859e91a 100644 --- a/dist/cli.js +++ b/dist/cli.js @@ -1,5 +1,4 @@ #!/usr/bin/env node -import { pathToFileURL } from "node:url"; import { fetchCert } from "./fetch-cert.js"; import { loadKeys } from "./keys.js"; import { verifyCertificate } from "./verify.js"; @@ -292,24 +291,37 @@ async function readVersion() { return "@certifieddata/verify (unknown version)"; } } -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { - // Set exitCode and let the event loop drain, rather than process.exit(code). - // - // process.exit() tears the process down immediately. On Windows, if a libuv - // async handle is mid-close when that happens — which it is on any path that - // did fs or network I/O — Node aborts with: - // - // Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 76 - // - // The verdict has already been printed by then, so the output looks correct - // while the shell sees an abnormal exit (127) instead of the documented code. - // That silently breaks every CI consumer, which is the one audience that - // reads the exit code rather than the text. - // - // Assigning exitCode lets Node close its handles and exit normally with the - // code we asked for. Nothing here keeps the loop alive deliberately. - main(process.argv.slice(2)).then((code) => { - process.exitCode = code; - }); -} +// ── Entry point ───────────────────────────────────────────────────────────── +// +// Run unconditionally. There is deliberately no "was I invoked directly?" guard, +// because the guard that used to be here made this tool FAIL OPEN: +// +// if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) +// +// Invoked through a bin, process.argv[1] is the symlink npm created +// (node_modules/.bin/verify) while import.meta.url is the resolved real path +// (node_modules/@certifieddata/verify/dist/cli.js). Those never match, so main() +// was never called: the process started, printed nothing, and exited 0. +// +// A verifier that exits 0 without verifying is the worst failure this tool can +// have. Not a crash, not a false negative — silent assent. `verify $ID && deploy` +// passed for a receipt nobody checked, and a nonsense id returned success. +// +// It was invisible on Windows, which is why it shipped: npm writes .cmd shims +// there that pass the real path as argv[1], so the comparison matched. On Linux +// and macOS npm writes a true symlink and it never did. +// +// A realpathSync() comparison would also fix the symlink case, but it keeps a +// conditional on the critical path — and every way that conditional can be wrong +// fails silently and open. This file is only ever an entry point; nothing in the +// repo imports it. So it just runs. +// +// exitCode rather than process.exit(): process.exit() tears the process down +// immediately, and if a libuv handle is mid-close — true on any path that did fs +// or network I/O — Node aborts with +// "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)". The verdict is +// printed before that, so output looks right while the shell sees 127 instead of +// the documented code. Assigning exitCode lets Node close its handles and exit +// normally. +process.exitCode = await main(process.argv.slice(2)); //# sourceMappingURL=cli.js.map \ No newline at end of file diff --git a/dist/cli.js.map b/dist/cli.js.map index c748f5d..877eda1 100644 --- a/dist/cli.js.map +++ b/dist/cli.js.map @@ -1 +1 @@ -{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/package.json b/package.json index 7606ad6..7db81cc 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "prepare": "npm run build", "typecheck": "tsc --noEmit", "lint": "eslint src/", - "test": "tsc -p tsconfig.json && node --test dist/canonicalize.test.js dist/verify.test.js dist/cli.test.js dist/receipt.test.js dist/receipt-vectors.test.js dist/exit-codes.test.js", + "test": "tsc -p tsconfig.json && node --test dist/canonicalize.test.js dist/verify.test.js dist/cli.test.js dist/receipt.test.js dist/receipt-vectors.test.js dist/exit-codes.test.js dist/bin-invocation.test.js", "fixtures": "node fixtures/generate.mjs", "prepublishOnly": "npm run build && npm test" }, diff --git a/src/bin-invocation.test.ts b/src/bin-invocation.test.ts new file mode 100644 index 0000000..77e9996 --- /dev/null +++ b/src/bin-invocation.test.ts @@ -0,0 +1,204 @@ +/** + * Invocation through the installed bin — the path every real user takes. + * + * This test exists because the CLI silently did nothing when run as a bin, and + * every other test in this repo missed it. They all spawn `node dist/cli.js`, + * the resolved real path, which is the one invocation form no consumer uses. + * + * The bug: + * + * if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) + * + * Through a bin, process.argv[1] is the symlink npm created + * (node_modules/.bin/verify) while import.meta.url is the resolved real path. + * They never match, so main() was never called — the process started, printed + * nothing, and exited 0. + * + * ./node_modules/.bin/verify 00000000-0000-0000-0000-000000000000 + * → (no output) EXIT=0 + * + * A nonsense receipt id returning success is the worst failure a verifier can + * have. Not a crash, not a false negative — silent assent. `verify $ID && deploy` + * passed for a receipt nobody checked. + * + * It was invisible on Windows, where npm writes .cmd shims that pass the real + * path as argv[1], so the comparison matched. On Linux and macOS npm writes a + * true symlink and it never did. + * + * ASSERTING THE OUTPUT IS NOT EMPTY MATTERS AS MUCH AS THE EXIT CODE. The + * failure mode was a correct-looking exit code with nothing behind it, so a test + * that only checked codes would have passed while the tool did nothing. + * + * The offline cases below are the load-bearing ones: `--version` and the no-args + * usage error both produce output and a specific code with no network, and both + * would have caught this. + */ + +import { test, before, after } from "node:test"; +import assert from "node:assert/strict"; +import { execFileSync, spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, existsSync, readdirSync, symlinkSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +const here = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(here, ".."); +const isWindows = process.platform === "win32"; + +let sandbox: string | null = null; +let binPath: string | null = null; + +before(() => { + try { + sandbox = mkdtempSync(join(tmpdir(), "cdverify-bin-")); + + // Pack and install exactly what a consumer receives, rather than linking the + // working tree — npm link produces different bin plumbing. + const tarball = execFileSync("npm", ["pack", "--silent", "--pack-destination", sandbox], { + cwd: repoRoot, + encoding: "utf8", + shell: isWindows, + }) + .trim() + .split("\n") + .pop()!; + + execFileSync("npm", ["init", "-y"], { cwd: sandbox, stdio: "ignore", shell: isWindows }); + execFileSync("npm", ["install", join(sandbox, tarball)], { + cwd: sandbox, + stdio: "ignore", + shell: isWindows, + }); + + const binDir = join(sandbox, "node_modules", ".bin"); + const candidate = join(binDir, isWindows ? "verify.cmd" : "verify"); + binPath = existsSync(candidate) ? candidate : null; + + if (!binPath) { + throw new Error( + `bin 'verify' not present after install. Contents: ${readdirSync(binDir).join(", ")}`, + ); + } + } catch (e) { + // Leave binPath null; every test below fails loudly rather than silently + // skipping, because a silent skip is the same class of problem as the bug. + // eslint-disable-next-line no-console + console.error(`[bin-invocation] setup failed: ${(e as Error).message}`); + } +}); + +after(() => { + if (sandbox) rmSync(sandbox, { recursive: true, force: true }); +}); + +function runBin(args: string[]) { + assert.ok(binPath, "bin was not installed — setup failed, see stderr above"); + const r = spawnSync(binPath!, args, { + encoding: "utf8", + env: { ...process.env, NO_COLOR: "1" }, + shell: isWindows, + }); + return { + stdout: r.stdout ?? "", + stderr: r.stderr ?? "", + code: r.status ?? -1, + combined: (r.stdout ?? "") + (r.stderr ?? ""), + }; +} + +// ── Offline: these alone would have caught the bug ────────────────────────── + +test("bin --version produces output and exits 0", () => { + const r = runBin(["--version"]); + // The silent-no-op produced exit 0 too, so the OUTPUT is the real assertion. + assert.notEqual(r.combined.trim(), "", "bin produced no output at all — it did not run"); + assert.match(r.stdout, /@certifieddata\/verify/); + assert.equal(r.code, 0); +}); + +test("bin with no arguments is a usage error, exit 64 with output", () => { + // The decisive offline case: under the bug this exited 0 in silence. Correct + // behavior is a non-zero usage code AND an explanation. + const r = runBin([]); + assert.notEqual(r.combined.trim(), "", "bin produced no output at all — it did not run"); + assert.equal(r.code, 64, `expected USAGE=64, got ${r.code}. combined: ${r.combined}`); +}); + +test("bin --help produces the help text and exits 0", () => { + const r = runBin(["--help"]); + assert.match(r.stdout, /Exit codes:/); + assert.equal(r.code, 0); +}); + +// ── The mechanism itself, on every platform ───────────────────────────────── +// +// The npm-install tests above cannot catch this bug on Windows: npm writes .cmd +// shims there that pass the RESOLVED path as argv[1], so a broken guard still +// matches and the tests pass. That platform difference is precisely why the bug +// shipped, and a guard that only works on some CI legs is half a guard. +// +// So reproduce the condition directly: invoke cli.js through a symlinked +// directory. Node resolves symlinks for import.meta.url but argv[1] keeps the +// link path, which is exactly the divergence npm's bin symlink creates. +// Verified by hand: against the old guard this yields EXIT=0 and ZERO bytes; +// against the fix, EXIT=3 and a NOT_FOUND message. + +test("invoking through a symlinked path still runs — no silent no-op", () => { + const dir = mkdtempSync(join(tmpdir(), "cdverify-link-")); + try { + const link = join(dir, "linked-dist"); + try { + // "junction" is the only form Windows allows without elevation; it is + // ignored on POSIX, where a normal directory symlink is created. + symlinkSync(join(here), link, "junction"); + } catch (e) { + assert.fail( + `could not create a symlink to exercise the bin path: ${(e as Error).message}. ` + + `This test must not be skipped — it is the only cross-platform guard against ` + + `the CLI silently doing nothing when invoked as a bin.`, + ); + } + + const r = spawnSync( + process.execPath, + [join(link, "cli.js"), "00000000-0000-0000-0000-000000000000"], + { encoding: "utf8", env: { ...process.env, NO_COLOR: "1" } }, + ); + const combined = (r.stdout ?? "") + (r.stderr ?? ""); + + assert.notEqual( + combined.trim(), + "", + "invoked through a symlink the CLI produced NO output — main() never ran, " + + "which is the fail-open bug this test exists to catch", + ); + assert.notEqual( + r.status, + 0, + "a nonsense id returned SUCCESS through a symlinked path — failing open", + ); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + +// ── Network: the case the reviewer reproduced ─────────────────────────────── + +test("bin fails closed on a nonsense id — never exit 0", async () => { + const r = runBin(["00000000-0000-0000-0000-000000000000"]); + + if (/NETWORK|ENOTFOUND|ECONN|fetch failed/i.test(r.combined)) { + // A network failure must still not be success. Assert that much and stop. + assert.notEqual(r.code, 0, "network failure must not report success"); + return; + } + + assert.notEqual(r.combined.trim(), "", "bin produced no output at all — it did not run"); + assert.notEqual( + r.code, + 0, + "a nonsense receipt id returned SUCCESS through the bin — the verifier is failing open", + ); + assert.equal(r.code, 3, `expected MALFORMED=3, got ${r.code}. combined: ${r.combined}`); +}); diff --git a/src/cli.ts b/src/cli.ts index 9b786d6..6895598 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,5 +1,4 @@ #!/usr/bin/env node -import { pathToFileURL } from "node:url"; import { fetchCert } from "./fetch-cert.js"; import { loadKeys } from "./keys.js"; import { verifyCertificate } from "./verify.js"; @@ -288,23 +287,36 @@ async function readVersion(): Promise { } catch { return "@certifieddata/verify (unknown version)"; } } -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { - // Set exitCode and let the event loop drain, rather than process.exit(code). - // - // process.exit() tears the process down immediately. On Windows, if a libuv - // async handle is mid-close when that happens — which it is on any path that - // did fs or network I/O — Node aborts with: - // - // Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 76 - // - // The verdict has already been printed by then, so the output looks correct - // while the shell sees an abnormal exit (127) instead of the documented code. - // That silently breaks every CI consumer, which is the one audience that - // reads the exit code rather than the text. - // - // Assigning exitCode lets Node close its handles and exit normally with the - // code we asked for. Nothing here keeps the loop alive deliberately. - main(process.argv.slice(2)).then((code) => { - process.exitCode = code; - }); -} +// ── Entry point ───────────────────────────────────────────────────────────── +// +// Run unconditionally. There is deliberately no "was I invoked directly?" guard, +// because the guard that used to be here made this tool FAIL OPEN: +// +// if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) +// +// Invoked through a bin, process.argv[1] is the symlink npm created +// (node_modules/.bin/verify) while import.meta.url is the resolved real path +// (node_modules/@certifieddata/verify/dist/cli.js). Those never match, so main() +// was never called: the process started, printed nothing, and exited 0. +// +// A verifier that exits 0 without verifying is the worst failure this tool can +// have. Not a crash, not a false negative — silent assent. `verify $ID && deploy` +// passed for a receipt nobody checked, and a nonsense id returned success. +// +// It was invisible on Windows, which is why it shipped: npm writes .cmd shims +// there that pass the real path as argv[1], so the comparison matched. On Linux +// and macOS npm writes a true symlink and it never did. +// +// A realpathSync() comparison would also fix the symlink case, but it keeps a +// conditional on the critical path — and every way that conditional can be wrong +// fails silently and open. This file is only ever an entry point; nothing in the +// repo imports it. So it just runs. +// +// exitCode rather than process.exit(): process.exit() tears the process down +// immediately, and if a libuv handle is mid-close — true on any path that did fs +// or network I/O — Node aborts with +// "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)". The verdict is +// printed before that, so output looks right while the shell sees 127 instead of +// the documented code. Assigning exitCode lets Node close its handles and exit +// normally. +process.exitCode = await main(process.argv.slice(2));