fix(native): bind initializer evidence to selected Mach-O slices (#10013)

* fix(native): bind initializer evidence to selected Mach-O slices

* fix(native): reject initializer slices outside the artifact

* test(native): retain completed initializer evidence on late cancellation

completeProviderEvidence keeps the finished report on
AnalysisCancelledError.partialObservation and the caller's abort reason.
The regression only checked the error tag, so dropping that evidence
would pass. Restore both structured assertions.

* fix(native): bind initializer facts to the selected slice label and range

A universal report could keep an arm64 cpu_type beside an x86_64 slice
label, and entry file offsets only had to fall inside the container.
Require the slice architecture to match the request, and require each
slot and value file offset to lie in that slice.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
morlutoandCursor Agent authored and GitHub committed 2026-11-11 17:19:16 +08:00
1 parent 7f0555b75e
commit 5b02119b69
2 files changed
+233 -6

No files matched your search

@@ -12,6 +12,7 @@ import {
import { err, type Result } from "../../domain/result.js";
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
import { parseAnalysisInput } from "../AnalysisInput.js";
import { MACHO_CPU_TYPES } from "../../domain/apple/machoTargetSelection.js";
const OPERATION = "inspect_native_initializers";
@@ -67,7 +68,7 @@ export class NativeInitializersService {
createEvidence(
{
path: value.artifact.path,
format: value.format,
format: value.slice === null ? value.format : "mach-o-universal",
sha256: value.artifact.sha256,
...(value.architecture === null
? {}
@@ -93,9 +94,28 @@ const validReport = (
input: InspectNativeInitializersInput,
): boolean => {
const { coverage } = report;
const inside = (offset: number | null, bytes: number): boolean =>
offset === null || offset <= report.artifact.bytes - bytes;
const slice = report.slice;
if (
slice === null
? input.architecture !== undefined
: report.format !== "mach-o" ||
input.architecture === undefined ||
slice.cpu_type !== MACHO_CPU_TYPES[input.architecture] ||
slice.architecture !== input.architecture ||
report.machine !== slice.cpu_type ||
report.architecture !== input.architecture
)
return false;
const insideArtifact = (offset: number, bytes: number): boolean =>
offset <= report.artifact.bytes - bytes;
// Entry file offsets name the selected image, not another slice in the container.
const imageStart = slice?.file_offset ?? 0;
const imageEnd =
slice === null ? report.artifact.bytes : slice.file_offset + slice.size;
const insideImage = (offset: number | null, bytes: number): boolean =>
offset === null || (offset >= imageStart && bytes <= imageEnd - offset);
if (
(slice !== null && !insideArtifact(slice.file_offset, slice.size)) ||
report.artifact.bytes > input.max_file_bytes ||
coverage.offset !== input.offset ||
coverage.limit !== input.limit ||
@@ -109,7 +129,7 @@ const validReport = (
return report.entries.every(
(entry, position) =>
entry.index === coverage.offset + position &&
inside(entry.slot_file_offset, entry.slot_bytes) &&
inside(entry.value_file_offset, 1),
insideImage(entry.slot_file_offset, entry.slot_bytes) &&
insideImage(entry.value_file_offset, 1),
);
};