diff --git a/src/application/ntfs/NtfsJournalService.test.ts b/src/application/ntfs/NtfsJournalService.test.ts new file mode 100644 index 0000000000..2fc3794237 --- /dev/null +++ b/src/application/ntfs/NtfsJournalService.test.ts @@ -0,0 +1,38 @@ +import { expect, it, vi } from "vitest"; + +import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js"; +import * as stableArtifact from "../../artifacts/readStableArtifact.js"; +import { projectAnalysisError } from "../../domain/analysisErrorProjection.js"; +import { inspectNtfsJournalEvidence } from "./NtfsJournalService.js"; + +it.each([ + ["EACCES", "access_denied"], + ["EIO", "artifact_operation_failed"], +])( + "preserves the acquisition failure category for %s", + async (systemCode, expectedCode) => { + const resources = new ArtifactResourceScope(); + const spy = vi + .spyOn(stableArtifact, "readStableArtifact") + .mockRejectedValueOnce( + Object.assign(new Error("acquisition read failed"), { + code: systemCode, + }), + ); + try { + const result = await inspectNtfsJournalEvidence( + { + mft_path: "/fixture/mft", + journal_path: "/fixture/journal", + }, + resources, + ); + expect(result.ok).toBe(false); + if (result.ok) throw new Error("expected acquisition failure"); + expect(projectAnalysisError(result.error).code).toBe(expectedCode); + } finally { + spy.mockRestore(); + await resources.close(); + } + }, +); diff --git a/src/application/ntfs/NtfsJournalService.ts b/src/application/ntfs/NtfsJournalService.ts index f8867dbd9e..0648dba1d2 100644 --- a/src/application/ntfs/NtfsJournalService.ts +++ b/src/application/ntfs/NtfsJournalService.ts @@ -11,11 +11,14 @@ import { } from "../../artifacts/StableArtifactFailure.js"; import type { AnalysisError } from "../../domain/analysisErrorBase.js"; import { + AnalysisAccessDeniedError, AnalysisArtifactChangedError, AnalysisCancelledError, AnalysisInputError, AnalysisResourceConstraintError, } from "../../domain/analysisErrorCore.js"; +import { ArtifactOperationError } from "../../domain/artifactOperationError.js"; +import { isSelectedRegularFileError } from "../../filesystem/RegularFile.js"; import { createEvidence, type Evidence } from "../../domain/evidence.js"; import { err, ok, type Result } from "../../domain/result.js"; import { @@ -73,10 +76,29 @@ const readFailure = ( { cause, retryable: false }, ); } - return inputFailure( - parameter, - `Could not read the selected acquisition at ${path}`, - cause, + if ( + cause instanceof ArtifactReaderFailure && + (cause.reason === "path" || cause.reason === "format") + ) + return inputFailure(parameter, cause.message, cause); + if (cause instanceof Error && "code" in cause) { + if (cause.code === "EACCES" || cause.code === "EPERM") + return new AnalysisAccessDeniedError(operation, path, cause.code, { + cause, + }); + if (isSelectedRegularFileError(cause)) + return inputFailure( + parameter, + `Could not read the selected acquisition at ${path}`, + cause, + ); + } + return new ArtifactOperationError( + operation, + "io", + undefined, + "NTFS acquisition read failed.", + { cause }, ); }; diff --git a/src/domain/artifactOperationError.ts b/src/domain/artifactOperationError.ts index 46c4beb832..d8c4f3144d 100644 --- a/src/domain/artifactOperationError.ts +++ b/src/domain/artifactOperationError.ts @@ -38,6 +38,7 @@ export class ArtifactOperationError extends AnalysisError { | "inspect_elf_notes" | "inspect_elf_unwind_ranges" | "inspect_devicetree" + | "inspect_ntfs_journal" | "inspect_api_contract" | "inspect_playwright_trace" | "inspect_kernel_module"