import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { spawnSync } from "node:child_process"; import { createHash } from "node:crypto"; import { describe, expect, it } from "vitest"; describe("A05 Injection Safety", () => { function runNormalizedScript(scriptPath: string, env: Record) { const tmpScriptDir = mkdtempSync(join(tmpdir(), "skymoney-a05-script-")); const normalizedScript = join(tmpScriptDir, "script.sh"); writeFileSync(normalizedScript, readFileSync(scriptPath, "utf8").replace(/\r/g, "")); chmodSync(normalizedScript, 0o755); const res = spawnSync("bash", [normalizedScript], { env: { ...process.env, ...env }, encoding: "utf8", }); rmSync(tmpScriptDir, { recursive: true, force: true }); return res; } it("contains no unsafe Prisma raw SQL APIs across API source", () => { const apiRoot = resolve(__dirname, ".."); const badPatterns = ["$queryRawUnsafe(", "$executeRawUnsafe("]; const queue = [resolve(apiRoot, "src")]; const offenders: string[] = []; while (queue.length > 0) { const current = queue.pop() as string; for (const name of readdirSync(current)) { const full = join(current, name); if (statSync(full).isDirectory()) { queue.push(full); continue; } if (!full.endsWith(".ts")) continue; const content = readFileSync(full, "utf8"); if (badPatterns.some((p) => content.includes(p))) offenders.push(full); } } expect(offenders).toEqual([]); }); it("rejects malicious restore DB identifiers before DB command execution", () => { const repoRoot = resolve(__dirname, "..", ".."); const restoreScriptPath = resolve(repoRoot, "scripts/restore.sh"); const tmpRoot = mkdtempSync(join(tmpdir(), "skymoney-a05-restore-")); const fakeBin = join(tmpRoot, "bin"); const backupFile = join(tmpRoot, "sample.dump"); const checksumFile = `${backupFile}.sha256`; const markerFile = join(tmpRoot, "db_called.marker"); try { mkdirSync(fakeBin, { recursive: true }); writeFileSync(backupFile, "valid-content"); const backupBytes = readFileSync(backupFile); const hash = createHash("sha256").update(backupBytes).digest("hex"); writeFileSync(checksumFile, `${hash} sample.dump\n`); const fakePsql = join(fakeBin, "psql"); const fakePgRestore = join(fakeBin, "pg_restore"); const fakeSha256sum = join(fakeBin, "sha256sum"); writeFileSync(fakePsql, `#!/usr/bin/env bash\nset -euo pipefail\ntouch "${markerFile}"\n`); writeFileSync(fakePgRestore, `#!/usr/bin/env bash\nset -euo pipefail\ntouch "${markerFile}"\n`); writeFileSync( fakeSha256sum, `#!/usr/bin/env bash\nset -euo pipefail\necho "${hash} $1"\n` ); chmodSync(fakePsql, 0o755); chmodSync(fakePgRestore, 0o755); chmodSync(fakeSha256sum, 0o755); const res = runNormalizedScript(restoreScriptPath, { PATH: `${fakeBin}:${process.env.PATH ?? ""}`, BACKUP_FILE: backupFile, DATABASE_URL: "postgres://app:app@127.0.0.1:5432/skymoney", RESTORE_DATABASE_URL: "postgres://app:app@127.0.0.1:5432/skymoney_restore_test", RESTORE_DB: 'bad-name"; DROP DATABASE skymoney; --', }); expect(res.status).not.toBe(0); expect(`${res.stdout}${res.stderr}`).toContain("RESTORE_DB must match"); expect(existsSync(markerFile)).toBe(false); } finally { rmSync(tmpRoot, { recursive: true, force: true }); } }); });