fix(artifacts): report encrypted archive entries as unsupported extraction

extract_artifact on a ZIP containing a password-protected entry failed
the whole extraction with "Artifact is invalid or has changed. Get a
fresh copy" and named only an opaque occurrence ID. The archive was
intact; REA does not decrypt entries, so complete extraction is
unsupported for this target.

Raise AnalysisUnsupportedTargetError naming the encrypted entry's path.
Other unextractable occurrences keep their format failure but now name
the logical path alongside the occurrence ID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 committed 2026-10-09 02:23:44 +00:00
1 parent 8e74c9c62f
commit de43da71ae
2 files changed
+25 -2

No files matched your search

@@ -67,15 +67,22 @@ export const extractArtifact = async (
nodes,
};
const selected = selectedOccurrences.map((occurrence) => {
// REA never decrypts archive entries, so an encrypted entry makes the
// complete extraction unsupported rather than the archive invalid.
if (occurrence.encrypted)
throw new AnalysisUnsupportedTargetError(
"extract_artifact",
sourcePath,
`Archive entry ${occurrence.logical_path} is encrypted; REA does not decrypt archive entries, so the archive cannot be extracted completely`,
);
if (
(occurrence.entry_kind !== "file" && occurrence.entry_kind !== "slice") ||
occurrence.artifact_id === null ||
occurrence.encrypted ||
occurrence.logical_path === "."
)
throw new ArtifactReaderFailure(
"format",
`Selected occurrence is not an extractable regular child file: ${occurrence.occurrence_id}`,
`Selected occurrence is not an extractable regular child file: ${occurrence.logical_path} (${occurrence.occurrence_id})`,
);
const node = inventory.nodes.get(occurrence.artifact_id);
if (node === undefined)
@@ -89,6 +89,22 @@ it("preserves encrypted-member and corrupt-CRC observations", async () => {
hash_status: "unavailable",
}),
);
// REA does not decrypt entries, so complete extraction is unsupported.
const extractionRoot = join(root, "encrypted-output");
expect(
await new ArtifactProvider()
.createClient(encryptedTarget.value)
.execute("extract_artifact", { output_root: extractionRoot }),
).toMatchObject({
ok: false,
error: {
_tag: "AnalysisUnsupportedTargetError",
reason: expect.stringContaining("Archive entry secret.txt is encrypted"),
},
});
await expect(readFile(extractionRoot)).rejects.toMatchObject({
code: "ENOENT",
});
const corruptPath = join(root, "corrupt-crc.zip");
const corruptWriter = new ZipWriter(new Uint8ArrayWriter());