diff --git a/docs/audit-log-hash-chain.md b/docs/audit-log-hash-chain.md index 109f1a29..4fa69c6c 100644 --- a/docs/audit-log-hash-chain.md +++ b/docs/audit-log-hash-chain.md @@ -43,7 +43,7 @@ npm run migrate ### CLI (operations / CI) ```bash -npm run verify-audit-integrity +npm run verify:audit-integrity ``` - Exit `0` — chain valid @@ -54,13 +54,13 @@ npm run verify-audit-integrity Auditors can verify an exported audit log excerpt against a published cryptographic receipt without running the application server or accessing the database: ```bash -npx ts-node scripts/verify-audit-receipt.ts --receipt --excerpt +npm run verify:audit-receipt -- --receipt --excerpt ``` Or positional format: ```bash -npx ts-node scripts/verify-audit-receipt.ts +npm run verify:audit-receipt -- ``` #### Verification Receipt Schema (`AuditReceipt`) diff --git a/package.json b/package.json index 1af1395e..71b6703a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "test:coverage": "jest --coverage --runInBand", "test:coverage:backend-011": "jest --runInBand --coverage src/middleware/rateLimit.test.ts src/middleware/startupAuthRateTierPolicy.test.ts src/routes/health.test.ts --collectCoverageFrom='src/middleware/rateLimit.ts' --collectCoverageFrom='src/middleware/startupAuthRateTierPolicy.ts' --coverageThreshold='{\"global\":{\"statements\":95,\"lines\":95,\"functions\":95}}'", "audit:ci": "ts-node scripts/audit-gate.ts", + "verify:audit-receipt": "ts-node scripts/verify-audit-receipt.ts", + "verify:audit-integrity": "ts-node src/cli/verifyAuditIntegrity.ts", "validate:alert-mappings": "ts-node scripts/validate-alert-mappings.ts", "drill:ttl-check": "ts-node scripts/failover-drill/ttl-check.ts", "test:watch": "jest --watch", diff --git a/scripts/verify-audit-receipt.test.ts b/scripts/verify-audit-receipt.test.ts index 1a900617..baa1e93e 100644 --- a/scripts/verify-audit-receipt.test.ts +++ b/scripts/verify-audit-receipt.test.ts @@ -438,6 +438,33 @@ describe('verify-audit-receipt script', () => { }); }); + describe('package scripts and fixtures', () => { + it('exposes documented npm entry points for the audit verifier CLIs', () => { + const packageJson = JSON.parse( + fs.readFileSync(path.resolve(__dirname, '..', 'package.json'), 'utf8'), + ); + + expect(packageJson.scripts['verify:audit-receipt']).toBeDefined(); + expect(packageJson.scripts['verify:audit-integrity']).toBeDefined(); + }); + + it('verifies the checked-in audit fixtures as a valid receipt/excerpt pair', async () => { + const actualFs = jest.requireActual('fs') as typeof import('fs'); + (fs.promises.readFile as jest.Mock).mockImplementation((filePath: fs.PathOrFileDescriptor) => + actualFs.promises.readFile(filePath, 'utf8'), + ); + + const receiptPath = path.resolve(__dirname, 'fixtures', 'audit', 'valid-receipt.json'); + const excerptPath = path.resolve(__dirname, 'fixtures', 'audit', 'valid-excerpt.json'); + + const spy = jest.spyOn(console, 'log').mockImplementation(() => {}); + const code = await runCli([`--receipt=${receiptPath}`, `--excerpt=${excerptPath}`]); + + expect(code).toBe(0); + expect(spy).toHaveBeenCalledWith(expect.stringContaining('PASS [VALID INTEGRITY PROOF]')); + }); + }); + describe('parseCliArgs and printHelp', () => { it('parses flag options', () => { const opts = parseCliArgs([ diff --git a/scripts/verify-audit-receipt.ts b/scripts/verify-audit-receipt.ts index 6aaeaf1c..a4fee35e 100644 --- a/scripts/verify-audit-receipt.ts +++ b/scripts/verify-audit-receipt.ts @@ -516,8 +516,12 @@ export function parseCliArgs(args: string[]): CliOptions { const arg = args[i]; if (arg === '--receipt' || arg === '-r') { options.receiptPathOrUrl = args[++i]; + } else if (arg.startsWith('--receipt=')) { + options.receiptPathOrUrl = arg.slice('--receipt='.length); } else if (arg === '--excerpt' || arg === '-e' || arg === '--entries') { options.excerptPathOrUrl = args[++i]; + } else if (arg.startsWith('--excerpt=')) { + options.excerptPathOrUrl = arg.slice('--excerpt='.length); } else if (arg === '--json') { options.jsonOutput = true; } else if (arg === '--quiet' || arg === '-q') {