Summary
getVersion() in src/cli.ts calls require('../package.json') to read the package version, but the project compiles to ES modules ("type": "module" in package.json, "module": "NodeNext" in tsconfig.json). In ESM, require is undefined at runtime, so the try block throws silently and the catch always returns the hard-coded fallback '0.0.0'. Every user who runs security-headers --version or security-headers --help sees 0.0.0 instead of the actual published version (1.0.1).
Investigation Path
- Read
src/cli.ts in full — noticed getVersion() at lines 20–26 uses require('../package.json') inside a try/catch that swallows all errors.
- Read
package.json — confirmed "type": "module" (line 4), making all compiled .js output ES modules.
- Read
tsconfig.json — confirmed "module": "NodeNext" (line 4), which outputs native ESM for .js files when "type": "module" is present in package.json.
- Verified
require is unavailable in ESM context: node --input-type=module -e "console.log(typeof require)" → undefined.
- Built the project (
npm install && npm run build) and ran the compiled CLI directly:
$ node dist/cli.js --version
0.0.0
- Inspected
dist/cli.js — confirmed require('../package.json') is emitted verbatim into the ESM output, where it throws ReferenceError: require is not defined and the catch block silently returns '0.0.0'.
- Searched all open and recently-closed issues for "require", "ESM", "version 0.0.0", "getVersion", and "cli --version" — no existing issue found.
Evidence
- File:
src/cli.ts lines 20–26, commit 81f8735d167b597ed7a7a88cc7f89ba0b440b07d
function getVersion(): string {
try {
const pkg = require('../package.json'); // ← require is undefined in ESM
return pkg.version;
} catch {
return '0.0.0'; // ← always reached at runtime
}
}
- File:
package.json line 4 — "type": "module"
- File:
tsconfig.json line 4 — "module": "NodeNext"
Current behavior: node dist/cli.js --version prints 0.0.0
Expected behavior: node dist/cli.js --version prints 1.0.1 (the version from package.json)
Reference: Node.js ESM documentation — require: "require, exports, module.exports, __filename, __dirname — These CommonJS variables are not available in ES modules."
Impact
- Every CLI user is affected. Anyone who runs
security-headers --version — to check their installed version, to include in a bug report, or to verify a CI pipeline is using the expected release — sees 0.0.0.
--help also shows 0.0.0. The version banner in printHelp() calls getVersion(), so the help output also displays the wrong version.
- Audit trails are broken. Security tooling is often invoked in CI with version-pinning checks. A tool that cannot report its own version undermines the reproducibility guarantee that version checks are meant to provide.
- The
try/catch masks the error silently. There is no warning, no log, and no non-zero exit — the user has no indication that the version being printed is wrong.
Suggested Remediation
Replace require() with a Node.js ESM-compatible approach. Two options:
Option A — module.createRequire (minimal diff):
import { createRequire } from 'node:module';
const _require = createRequire(import.meta.url);
function getVersion(): string {
try {
const pkg = _require('../package.json') as { version: string };
return pkg.version;
} catch {
return '0.0.0';
}
}
Option B — readFileSync + JSON.parse (no implicit CJS shim):
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { join, dirname } from 'node:path';
function getVersion(): string {
try {
const __dirname = dirname(fileURLToPath(import.meta.url));
const raw = readFileSync(join(__dirname, '../package.json'), 'utf8');
return (JSON.parse(raw) as { version: string }).version;
} catch {
return '0.0.0';
}
}
Option C — hardcode via a generated constant (cleanest for published packages):
Create src/version.ts containing export const VERSION = '1.0.1';, add a build:version script that writes this file from package.json before tsc, and import it in cli.ts. This avoids any runtime file I/O and cannot fail.
Option A is the smallest change; Option B avoids the CJS require bridge entirely; Option C is the most robust but requires a build-step addition.
Acceptance Criteria
Claude Code review routine · commit 81f8735d167b597ed7a7a88cc7f89ba0b440b07d · 2026-05-26T11:15:00Z
Summary
getVersion()insrc/cli.tscallsrequire('../package.json')to read the package version, but the project compiles to ES modules ("type": "module"inpackage.json,"module": "NodeNext"intsconfig.json). In ESM,requireisundefinedat runtime, so thetryblock throws silently and thecatchalways returns the hard-coded fallback'0.0.0'. Every user who runssecurity-headers --versionorsecurity-headers --helpsees0.0.0instead of the actual published version (1.0.1).Investigation Path
src/cli.tsin full — noticedgetVersion()at lines 20–26 usesrequire('../package.json')inside atry/catchthat swallows all errors.package.json— confirmed"type": "module"(line 4), making all compiled.jsoutput ES modules.tsconfig.json— confirmed"module": "NodeNext"(line 4), which outputs native ESM for.jsfiles when"type": "module"is present inpackage.json.requireis unavailable in ESM context:node --input-type=module -e "console.log(typeof require)"→undefined.npm install && npm run build) and ran the compiled CLI directly:dist/cli.js— confirmedrequire('../package.json')is emitted verbatim into the ESM output, where it throwsReferenceError: require is not definedand the catch block silently returns'0.0.0'.Evidence
src/cli.tslines 20–26, commit81f8735d167b597ed7a7a88cc7f89ba0b440b07dpackage.jsonline 4 —"type": "module"tsconfig.jsonline 4 —"module": "NodeNext"Current behavior:
node dist/cli.js --versionprints0.0.0Expected behavior:
node dist/cli.js --versionprints1.0.1(the version frompackage.json)Reference: Node.js ESM documentation —
require: "require, exports, module.exports, __filename, __dirname — These CommonJS variables are not available in ES modules."Impact
security-headers --version— to check their installed version, to include in a bug report, or to verify a CI pipeline is using the expected release — sees0.0.0.--helpalso shows0.0.0. The version banner inprintHelp()callsgetVersion(), so the help output also displays the wrong version.try/catchmasks the error silently. There is no warning, no log, and no non-zero exit — the user has no indication that the version being printed is wrong.Suggested Remediation
Replace
require()with a Node.js ESM-compatible approach. Two options:Option A —
module.createRequire(minimal diff):Option B —
readFileSync+JSON.parse(no implicit CJS shim):Option C — hardcode via a generated constant (cleanest for published packages):
Create
src/version.tscontainingexport const VERSION = '1.0.1';, add abuild:versionscript that writes this file frompackage.jsonbeforetsc, and import it incli.ts. This avoids any runtime file I/O and cannot fail.Option A is the smallest change; Option B avoids the CJS require bridge entirely; Option C is the most robust but requires a build-step addition.
Acceptance Criteria
node dist/cli.js --versionprints the version frompackage.json(e.g.1.0.1), not0.0.0node dist/cli.js --helpdisplays the correct version in the banner linerequire()as a bare global (which is unavailable in ESM)npm run buildsucceeds with no new TypeScript errorsnpm testcontinues to exit 0 (no tests broken by the change)Claude Code review routine · commit
81f8735d167b597ed7a7a88cc7f89ba0b440b07d· 2026-05-26T11:15:00Z