Compare commits

...
Author SHA1 Message Date
Kaoru0822 7aee9900da Merge remote-tracking branch 'origin/main' into fix/pe-resources-input-selection 2026-10-11 02:44:38 +00:00
Kaoru0822andClaude Opus 5.5 e1def2bf1a fix(native): report missing and non-file PE resource targets as input errors
`inspect_pe_resources` turned every acquisition failure other than
limits and access denial into `artifact_operation_failed` with "Artifact
could not be read or written. Check file access and try again." A
missing path, a directory, a symlink or a path under a regular file got
that advice, although the caller selected a target that cannot be a PE
image.

Map the stable reader's non-regular-file refusal and ENOENT/ENOTDIR to
`AnalysisInputError` issues on `path`, and a file that changed during the
read to `AnalysisArtifactChangedError`, as the sibling EVM carrier read
does. Other I/O failures stay artifact faults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 02:30:25 +00:00
2 changed files with 69 additions and 37 deletions

No files matched your search

+51 -34
View File
@@ -5,7 +5,9 @@ import type { ExecutionOptions } from "../../application/AnalysisProvider.js";
import { ArtifactOperationError } from "../../domain/artifactOperationError.js"; import { ArtifactOperationError } from "../../domain/artifactOperationError.js";
import { import {
AnalysisAccessDeniedError, AnalysisAccessDeniedError,
AnalysisArtifactChangedError,
AnalysisCancelledError, AnalysisCancelledError,
AnalysisInputError,
AnalysisResourceConstraintError, AnalysisResourceConstraintError,
} from "../../domain/analysisErrorCore.js"; } from "../../domain/analysisErrorCore.js";
import type { InspectPeResourcesInput } from "../../domain/native/peResources.js"; import type { InspectPeResourcesInput } from "../../domain/native/peResources.js";
@@ -37,47 +39,62 @@ export class PeResourcesProvider implements PeResourcesPort {
), ),
); );
} catch (cause) { } catch (cause) {
if (options?.signal?.aborted) return err(peResourcesFailure(cause, input, options?.signal));
return err( }
new AnalysisCancelledError("inspect_pe_resources", { cause }), }
); }
if (cause instanceof ArtifactReaderFailure && cause.reason === "limit")
return err( const errorCode = (cause: unknown): unknown =>
new AnalysisResourceConstraintError( cause instanceof Error && "code" in cause ? cause.code : undefined;
"inspect_pe_resources",
// A missing or non-regular caller selection is an input error, as for the
// EVM bytecode carrier read; only other I/O remains an artifact fault.
const peResourcesFailure = (
cause: unknown,
input: InspectPeResourcesInput,
signal: AbortSignal | undefined,
) => {
const operation = "inspect_pe_resources";
if (signal?.aborted) return new AnalysisCancelledError(operation, { cause });
const reason = cause instanceof ArtifactReaderFailure ? cause.reason : "io";
const message = cause instanceof Error ? cause.message : String(cause);
if (reason === "limit")
return new AnalysisResourceConstraintError(
operation,
"memory", "memory",
cause.message, message,
{ {
max_file_bytes: input.max_file_bytes, max_file_bytes: input.max_file_bytes,
max_entries: input.max_entries, max_entries: input.max_entries,
}, },
{ cause }, { cause },
),
); );
if ( if (reason === "path")
cause instanceof Error && return new AnalysisInputError(operation, { cause }, [
"code" in cause && { path: ["path"], reason: "invalid_format", message },
(cause.code === "EACCES" || cause.code === "EPERM") ]);
) if (reason === "integrity")
return err( return new AnalysisArtifactChangedError(operation, input.path, message, {
new AnalysisAccessDeniedError( cause,
"inspect_pe_resources", });
input.path, const code = errorCode(cause);
cause.code, if (code === "ENOENT" || code === "ENOTDIR")
{ cause }, return new AnalysisInputError(operation, { cause }, [
), {
); path: ["path"],
return err( reason: "invalid_value",
new ArtifactOperationError( message: `Selected PE image could not be read (${code}): ${input.path}.`,
"inspect_pe_resources", },
cause instanceof ArtifactReaderFailure ? cause.reason : "io", ]);
if (code === "EACCES" || code === "EPERM")
return new AnalysisAccessDeniedError(operation, input.path, code, {
cause,
});
return new ArtifactOperationError(
operation,
reason,
undefined, undefined,
cause instanceof Error cause instanceof Error ? cause.message : "PE artifact inspection failed.",
? cause.message
: "PE artifact inspection failed.",
{ cause }, { cause },
),
); );
} };
}
}
+18 -3
View File
@@ -6,17 +6,32 @@ import { peResourceFixture } from "../../../src/native/pe/PeResources.fixture.js
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
describe("PE resource stable filesystem boundary", () => { describe("PE resource stable filesystem boundary", () => {
it("rejects symlinks, missing files and directories without inventing empty inventory", async () => { it("rejects symlinks, missing files and directories as caller input without inventing empty inventory", async () => {
const root = await createTestTempDirectory("rea-pe-resource-files-"); const root = await createTestTempDirectory("rea-pe-resource-files-");
const path = join(root, "image.exe"); const path = join(root, "image.exe");
const alias = join(root, "alias.exe"); const alias = join(root, "alias.exe");
await writeFile(path, peResourceFixture().bytes); await writeFile(path, peResourceFixture().bytes);
await symlink(path, alias); await symlink(path, alias);
const service = createPeResourcesService(); const service = createPeResourcesService();
for (const candidate of [root, alias, join(root, "missing.exe")]) { for (const [candidate, reason] of [
[root, "invalid_format"],
[alias, "invalid_format"],
[join(root, "missing.exe"), "invalid_value"],
[join(path, "child.exe"), "invalid_value"],
] as const) {
const result = await service.inspect({ path: candidate }); const result = await service.inspect({ path: candidate });
if (result.ok) throw new Error("Expected file acquisition failure"); if (result.ok) throw new Error("Expected file acquisition failure");
expect(result.error._tag).toBe("ArtifactOperationError"); expect(result.error).toMatchObject({
_tag: "AnalysisInputError",
operation: "inspect_pe_resources",
issues: [
{
path: ["path"],
reason,
message: expect.stringContaining(candidate),
},
],
});
} }
}); });