From 3c54f3ce4e22018a7af5ae4c4c4df37ef5533b1d Mon Sep 17 00:00:00 2001 From: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Date: Wed, 11 Nov 2026 01:02:40 +0000 Subject: [PATCH] fix(ntfs): preserve acquisition access and I/O failures Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> --- .../ntfs/NtfsJournalService.test.ts | 38 +++++++++++++++++++ src/application/ntfs/NtfsJournalService.ts | 30 +++++++++++++-- src/domain/artifactOperationError.ts | 1 + 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 src/application/ntfs/NtfsJournalService.test.ts 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"