import { createHash, randomUUID } from "node:crypto"; import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { createApp } from "../../apps/api/src/app.js"; import { createPublicAssetResolver, validateReadOnlyAssetRoot, } from "../../apps/api/src/local-data-root.js"; const temporaryDirectories: string[] = []; afterEach(() => { for (const directory of temporaryDirectories.splice(0)) { if (resolve(directory).startsWith(resolve(tmpdir()))) rmSync(directory, { force: true, recursive: true }); } }); describe("TDD-WP0-DATA-001-root-validation resource boundary", () => { it("serves a manifest resource by stable ID without exposing the absolute root or object key", async () => { const base = mkdtempSync(join(tmpdir(), "dada-wp0-04-api-")); temporaryDirectories.push(base); const assetRoot = join(base, "read-only-assets"); const relativePath = "images/source.png"; const bytes = Buffer.from("synthetic png fixture"); const assetId = "STK001"; mkdirSync(join(assetRoot, "images"), { recursive: true }); writeFileSync(join(assetRoot, relativePath), bytes); const manifest = JSON.stringify({ assets: [{ asset_id: assetId, relative_path: relativePath }] }); writeFileSync(join(assetRoot, "catalog.json"), manifest); const validatedRoot = validateReadOnlyAssetRoot({ dataRoot: join(base, "data"), expectedSha256: createHash("sha256").update(manifest).digest("hex"), manifestRelativePath: "catalog.json", root: assetRoot, rootRef: "fixture_assets", }); if (!validatedRoot.ok) throw new Error(validatedRoot.reason); const publicAssets = createPublicAssetResolver({ entries: [{ assetId, mimeType: "image/png", relativePath, resourceVersion: "fixture-v1", rootRef: "fixture_assets", sha256: createHash("sha256").update(bytes).digest("hex"), }], roots: [validatedRoot], }); const app = await createApp({ browserGate: false, publicAssets }); try { const resourceUrl = `/api/v1/assets/public/fixture-v1/${assetId}`; const response = await app.inject({ headers: { host: "127.0.0.1:43121" }, method: "GET", url: resourceUrl, }); expect(response.statusCode).toBe(200); expect(response.headers["content-type"]).toContain("image/png"); expect(response.rawPayload).toEqual(bytes); const wrongVersion = await app.inject({ headers: { host: "127.0.0.1:43121" }, method: "GET", url: `/api/v1/assets/public/other-version/${assetId}`, }); expect(wrongVersion.statusCode).toBe(404); const browserVisible = JSON.stringify({ headers: response.headers, resource_id: assetId, url: resourceUrl, }); expect(browserVisible).not.toContain(assetRoot); expect(browserVisible).not.toContain(relativePath); expect(browserVisible).not.toMatch(/[A-Za-z]:\\/); const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_DATA_ROOT; if (evidenceDirectory) { mkdirSync(evidenceDirectory, { recursive: true }); writeFileSync(join(evidenceDirectory, "response.json"), `${JSON.stringify({ content_type: response.headers["content-type"], path_exposed: false, resource_id: assetId, status: "passed", }, null, 2)}\n`); } } finally { await app.close(); } }); it("does not resolve unknown IDs or path-like route parameters", async () => { const app = await createApp({ browserGate: false, publicAssets: createPublicAssetResolver({ entries: [], roots: [] }) }); try { for (const target of [randomUUID(), "..%2F..%2Fsecret", "C:%5CUsers%5Csecret"]) { const response = await app.inject({ headers: { host: "127.0.0.1:43121" }, method: "GET", url: `/api/v1/assets/public/fixture-v1/${target}`, }); expect(response.statusCode).toBe(404); expect(response.body).not.toMatch(/[A-Za-z]:\\/); } } finally { await app.close(); } }); }); describe("TDD-WP0-DATA-002-no-backup-migration route inventory", () => { it("contains no product backup, archive, business import, migration or recovery route", async () => { const app = await createApp({ browserGate: false }); try { await app.ready(); const routes = Object.keys(app.swagger().paths ?? {}).map((route) => route.toLowerCase()); for (const forbidden of ["backup", "archive", "project-package", "business-import", "migration", "recovery"]) { expect(routes.some((route) => route.includes(forbidden))).toBe(false); } expect(routes.filter((route) => route.includes("restore"))).toEqual(["/api/v1/projects/{projectid}/restore"]); const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_NO_TRANSFER; if (evidenceDirectory) { mkdirSync(evidenceDirectory, { recursive: true }); writeFileSync(join(evidenceDirectory, "route-inventory.json"), `${JSON.stringify({ forbidden_routes: [], status: "passed", }, null, 2)}\n`); } } finally { await app.close(); } }); });