#!/usr/bin/env node
/**
* Verification for the "Sanitize before storing" hardening:
* - src/lib/ingest/sanitize.ts (string/number sanitizers + stored-receipt zod schema)
* - src/lib/export/csvGenerator.ts (CSV formula-injection neutralizer)
* - excel / pdf generators (values stay plain strings / literal text)
*
* Runs under plain Node ≥ 23.6 (native TypeScript type-stripping):
* node scripts/verify_sanitize.mjs
*
* Exit code 0 = all assertions passed, 1 = at least one failed.
*/
import { strict as assert } from "node:assert";
import ExcelJS from "exceljs";
import {
SANITIZE_LIMITS,
sanitizeText,
sanitizeMultilineText,
sanitizeUrl,
sanitizeCurrency,
sanitizeReceiptBatch,
sanitizeReceipt,
StoredReceiptSchema,
} from "../src/lib/ingest/sanitize.ts";
import {
generateAccountingCsv,
neutralizeFormulaPrefix,
} from "../src/lib/export/csvGenerator.ts";
import { generateDualSheetExcel } from "../src/lib/export/excelGenerator.ts";
import { generateReceiptPdf } from "../src/lib/export/pdfGenerator.ts";
// ---------------------------------------------------------------------------
// Tiny test runner
// ---------------------------------------------------------------------------
let passed = 0;
let failed = 0;
const failures = [];
function test(name, fn) {
try {
fn();
passed++;
console.log(` PASS ${name}`);
} catch (err) {
failed++;
failures.push({ name, err });
console.error(` FAIL ${name}\n ${err && err.message ? err.message : err}`);
}
}
// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------
function validReceipt(overrides = {}) {
return {
id: "rcpt_test_123",
imageHash: "abc123def456",
merchant: {
name: "REWE",
address: "Hauptstr 1, 10115 Berlin",
taxId: "DE123456789",
confidence: 0.98,
},
date: { isoDate: "2026-08-15", time: "10:30", confidence: 0.97 },
documentType: "KASSENBON",
receiptNumber: "2026-0815-001",
currency: "EUR",
totalAmount: { value: 12.34, confidence: 0.99 },
netAmount: 10.36,
tipAmount: null,
taxBreakdown: [{ ratePercent: 19, taxAmount: 1.98, netAmount: 10.36 }],
lineItems: [
{ description: "Apfel", quantity: 2, price: 12.34, unitPrice: 6.17, taxRate: 19 },
],
suggestedCategory: "Sonstiges",
validation: {
isMathValid: true,
isDuplicateSuspected: false,
needsUserReview: false,
reviewField: "none",
reviewReason: null,
issues: null,
userConfirmed: null,
},
rawText: "REWE\nApfel 2x 6,17\nSUMME 12,34",
paymentMethod: "EC_KARTE",
previewUrl: "https://storage.example/rcpt_test_123.jpg",
originalFileName: "bon.jpg",
fileSizeBytes: 12345,
status: "ready",
createdAt: "2026-08-15T10:31:00.000Z",
updatedAt: "2026-08-15T10:31:00.000Z",
...overrides,
};
}
function maliciousReceipt() {
return validReceipt({
merchant: {
name: "",
address: "
",
taxId: "javascript:alert(3)",
confidence: 1,
},
receiptNumber: '=HYPERLINK("http://evil.example","x")',
suggestedCategory: "@SUM(1+1)",
rawText: '
\nZeile 2\nZeile 3\u0000\u0007',
lineItems: [
{ description: "+cmd|' /C calc'!A0", quantity: 1, price: 12.34, unitPrice: 12.34, taxRate: 19 },
{ description: "