import { access, chmod, mkdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { expect, it, vi } from "vitest"; import { inventoryFirmwareOutput, FirmwareFileAccess, } from "../../../src/firmware/FirmwareFiles.js"; import { readFirmwareCommandReport } from "../../../src/firmware/FirmwareCommand.js"; import { FIRMWARE_LIMITS } from "../../../src/firmware/FirmwareRelease.js"; import { AnalysisError } from "../../../src/domain/analysisErrorBase.js"; import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js"; import { SafeOutputTree } from "../../../src/artifacts/SafeOutputTree.js"; import { normalizeBinwalkReport, normalizeUnblobReport, } from "../../../src/firmware/FirmwareReports.js"; import { firmwareResultSchemas, firmwareRequestSchema, } from "../../../src/domain/firmware/firmwareAnalysis.js"; import { createHash } from "node:crypto"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; it.each([ ["invalid leading byte", [0xff]], ["incomplete sequence", [0xe2, 0x82]], ["overlong sequence", [0xc0, 0xaf]], ["encoded surrogate", [0xed, 0xa0, 0x80]], ])( "rejects complete JSON containing %s without repairing producer bytes", async (_name, bytes) => { const root = await createTestTempDirectory("rea-firmware-report-"); const path = join(root, "report.json"); await writeFile( path, Buffer.concat([ Buffer.from('{"description":"'), Buffer.from(bytes), Buffer.from('"}'), ]), ); await expect( new FirmwareFileAccess().report(path, "inspect_firmware_regions"), ).rejects.toMatchObject({ _tag: "AnalysisOutputError", reason: "Tool report is not valid complete JSON", }); }, ); it.each([ ["missing", "Report was not created", async () => {}], ["empty", "Report is empty", (path: string) => writeFile(path, "")], [ "invalid_json", "Tool report is not valid complete JSON", (path: string) => writeFile(path, '[{"Analysis":'), ], [ "oversized", `Report exceeded ${FIRMWARE_LIMITS.reportBytes} bytes`, (path: string) => writeFile(path, Buffer.alloc(FIRMWARE_LIMITS.reportBytes + 1, 0x20)), ], [ "not_regular_file", "Report is not a regular file", (path: string) => mkdir(path), ], ] as const)( "keeps report state %s after a successful exit distinct from an empty scan", async (state, reason, prepare) => { const root = await createTestTempDirectory("rea-firmware-report-"); const path = join(root, "report.json"); await prepare(path); const failure: unknown = await readFirmwareCommandReport({ path, engine: "binwalk", operation: "inspect_firmware_regions", execution: { stdout: { text: "", bytes: 0, observedBytes: 0 }, stderr: { text: "", bytes: 0, observedBytes: 0 }, exit_code: 0, signal: null, command: "/opt/binwalk", args: ["--quiet", "--log", path, "/work/firmware.bin"], resource_limiter: "/usr/bin/prlimit", }, files: new FirmwareFileAccess(), }).catch((cause: unknown) => cause); if (!(failure instanceof AnalysisError)) throw new Error("Expected a typed report failure"); const projected = projectAnalysisError(failure); expect(projected).toMatchObject({ code: "unreadable_output", retryable: false, message: expect.stringContaining("not an empty or complete result"), details: { reason: `binwalk exited 0 without a usable report: ${reason}`, diagnostics: { report_state: state, report_path: path, command: "/opt/binwalk", args: ["--quiet", "--log", path, "/work/firmware.bin"], resource_limiter: "/usr/bin/prlimit", exit_code: 0, signal: null, stdout_bytes: 0, stderr_bytes: 0, }, }, }); // Generic doctor advice is not presented as a repair for a missing report. expect(projected.message).not.toContain("rea doctor"); // Recorded arguments name a removed private workspace; recovery must not // present them as a replayable command. expect(projected.remediation.action).toContain("diagnostics.report_state"); expect(projected.remediation.action).toContain("cannot be replayed as-is"); if (state === "oversized") { expect(projected.remediation.action).toContain("no runtime override"); expect(projected.remediation.action).toContain( "Repeating the unchanged request fails the same way", ); expect(projected.remediation.action).not.toContain( "Rerun the same REA request", ); expect(projected.message).toContain("no runtime override"); expect(projected.message).not.toContain("Rerun the same REA request"); } else { expect(projected.remediation.action).toContain( "replacing every private path", ); expect(projected.remediation.action).toContain( "Rerun the same REA request", ); expect(projected.remediation.action).not.toContain( "Repeating the unchanged request fails the same way", ); } }, ); it("preserves valid Unicode and a literal replacement character in producer JSON", async () => { const root = await createTestTempDirectory("rea-firmware-report-"); const path = join(root, "report.json"); const report = { description: "固件 🧩 \ufffd", path: "/输入/firmware.bin" }; await writeFile(path, JSON.stringify(report)); expect( await new FirmwareFileAccess().report(path, "inspect_firmware_regions"), ).toEqual(report); }); it("orders extracted firmware paths by code point independently of host locale", async () => { const root = await createTestTempDirectory("rea-firmware-inventory-"); await mkdir(join(root, "Lib")); for (const path of ["a.bin", "z.bin", "\u00e4.bin", "B.bin", "Lib/x.bin"]) await writeFile(join(root, path), ""); const entries = await inventoryFirmwareOutput( root, 1024, 16, "inspect_firmware_regions", ); // Locale collation would interleave case and place "ä" by language rules. expect(entries.map(({ relativePath }) => relativePath)).toEqual([ "B.bin", "Lib/x.bin", "a.bin", "z.bin", "\u00e4.bin", ]); }); it.skipIf(process.platform !== "linux")( "refuses raw filenames that cannot round-trip through UTF-8 instead of omitting their bytes", async () => { const root = await createTestTempDirectory("rea-firmware-raw-name-"); await writeFile( Buffer.concat([ Buffer.from(`${root}/bad`), Buffer.from([0xff]), Buffer.from(".bin"), ]), "123", ); await writeFile(join(root, "good.bin"), "1234"); await expect( inventoryFirmwareOutput(root, 4, 10, "extract_firmware"), ).rejects.toMatchObject({ _tag: "AnalysisOutputError", diagnostics: { raw_name_hex: "626164ff2e62696e" }, }); }, ); it.each([ { bytes: 1, files: 10, expected: "max_output_bytes" }, { bytes: 10, files: 1, expected: "max_output_files" }, ])( "reports the selected $expected staging budget as a deterministic resource refusal", async ({ bytes, files, expected }) => { const root = await createTestTempDirectory("rea-firmware-staging-"); await writeFile(join(root, "a"), "1"); await writeFile(join(root, "b"), "2"); await expect( inventoryFirmwareOutput(root, bytes, files, "extract_firmware"), ).rejects.toMatchObject({ _tag: "AnalysisResourceConstraintError", resource: "capacity", reportedLimits: { [expected]: 1 }, retryable: false, remediationAction: expect.stringContaining(expected), }); }, ); it("attributes missing sources to the selected path without relabeling snapshot destination failure", async () => { const root = await createTestTempDirectory("rea-firmware-source-"); const files = new FirmwareFileAccess(); const request = (path: string) => firmwareRequestSchema.parse({ operation: "inspect_firmware_regions", input: { path }, }); await expect( files.snapshot( request(join(root, "absent")), root, new AbortController().signal, ), ).rejects.toMatchObject({ _tag: "AnalysisInputError", issues: [{ path: ["path"], reason: "invalid_value" }], }); const source = join(root, "source"); await writeFile(source, "firmware"); await expect( files.snapshot( request(source), join(root, "missing-workspace"), new AbortController().signal, ), ).rejects.toMatchObject({ code: "ENOENT" }); await files.close(); }); it("does not activate an abort-bound source during awaited destination setup", async () => { const root = await createTestTempDirectory("rea-firmware-publish-abort-"); const source = join(root, "source"); const bytes = Buffer.from("firmware"); await writeFile(source, bytes); const files = new FirmwareFileAccess(); const tree = await SafeOutputTree.create(join(root, "published")); const controller = new AbortController(); const reason = { stop: "during destination setup" }; const write = tree.write.bind(tree); const spy = vi.spyOn(tree, "write").mockImplementation(async (...args) => { controller.abort(reason); // Yield a turn while the source has no consumer, reproducing the handoff. await new Promise((resolve) => setImmediate(resolve)); return write(...args); }); try { await expect( files.publish( { path: source, relativePath: "nested/file", kind: "file", size: bytes.length, linkTarget: null, }, createHash("sha256").update(bytes).digest("hex"), tree, controller.signal, ), ).rejects.toMatchObject({ reason: "cancelled", cause: reason }); } finally { spy.mockRestore(); await tree.rollback(); await files.close(); } await expect(access(join(root, "published"))).rejects.toMatchObject({ code: "ENOENT", }); expect(Buffer.compare(await readFile(source), bytes)).toBe(0); }); it.skipIf(process.platform === "win32" || process.getuid?.() === 0)( "keeps denied source access distinct from invalid paths and producer failures", async () => { const root = await createTestTempDirectory("rea-firmware-source-denial-"); const source = join(root, "input"); await writeFile(source, "firmware", { mode: 0o000 }); const files = new FirmwareFileAccess(); try { await expect( files.snapshot( firmwareRequestSchema.parse({ operation: "inspect_firmware_regions", input: { path: source }, }), root, new AbortController().signal, ), ).rejects.toMatchObject({ _tag: "AnalysisAccessDeniedError", path: source, systemCode: "EACCES", }); } finally { await chmod(source, 0o600); await files.close(); } }, ); it("retains unfamiliar Unblob report members from JSON files through public diagnostics", async () => { const root = await createTestTempDirectory("rea-firmware-report-metadata-"); const inputPath = join(root, "input.bin"); const reportPath = join(root, "report.json"); const sha256 = "a".repeat(64); const unfamiliar: unknown = JSON.parse( '{"__typename__":"FutureReport","__proto__":{"source":"local","empty":null},"constructor":false,"prototype":0,"nested":{"__proto__":"retained"}}', ); const record = { task: { path: inputPath, depth: 0, blob_id: "", is_multi_file: false }, reports: [ { __typename__: "HashReport", sha256 }, { __typename__: "StatReport", path: inputPath, size: 4, is_dir: false, is_file: true, is_link: false, link_target: null, }, unfamiliar, ], subtasks: [], }; await writeFile(reportPath, JSON.stringify([record])); const files = new FirmwareFileAccess(); try { const raw = await files.report(reportPath, "extract_firmware"); const normalized = normalizeUnblobReport(raw, { inputPath, outputRoot: join(root, "output"), sha256, length: 4, offset: 0, maxDepth: 3, }); const diagnostics = firmwareResultSchemas.extract_firmware.shape.diagnostics.parse( normalized.diagnostics, ); expect(JSON.stringify(diagnostics)).toBe( JSON.stringify([{ input_path: "$input", report: unfamiliar }]), ); expect(normalized.coverage).toBe("partial"); for (const malformed of [{}, { __typename__: null }, { __typename__: 1 }]) { expect(() => normalizeUnblobReport( [{ ...record, reports: [...record.reports.slice(0, 2), malformed] }], { inputPath, outputRoot: join(root, "output"), sha256, length: 4, offset: 0, maxDepth: 3, }, ), ).toThrow(); } } finally { await files.close(); } }); it("rejects Binwalk extraction activity under every own JSON member", async () => { const root = await createTestTempDirectory( "rea-firmware-report-extractions-", ); const reportPath = join(root, "report.json"); const inputPath = join(root, "input.bin"); const files = new FirmwareFileAccess(); try { for (const extractions of [ "{}", '{"__proto__":null}', '{"ordinary":false}', ]) { await writeFile( reportPath, `[{"Analysis":{"file_path":${JSON.stringify(inputPath)},"file_map":[],"extractions":${extractions}}}]`, ); const raw = await files.report(reportPath, "inspect_firmware_regions"); const normalize = () => normalizeBinwalkReport(raw, inputPath, 4); if (extractions === "{}") expect(normalize()).toEqual([]); else expect(normalize).toThrow("Expected one inspection-only report"); } } finally { await files.close(); } });