From e1597a6f3de71d025649b6846fb75bd50bbaca0c Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:06:51 +0800 Subject: [PATCH] fix(apple): reject empty caches and bind digests to resolution traces --- src/artifacts/apple/DyldSharedCacheReader.ts | 1 + src/artifacts/apple/DylibResolutionReader.ts | 23 ++++++++++++-------- src/domain/apple/dylibResolutionSchema.ts | 2 ++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/artifacts/apple/DyldSharedCacheReader.ts b/src/artifacts/apple/DyldSharedCacheReader.ts index ef1e8d591..2b5d5e8ee 100644 --- a/src/artifacts/apple/DyldSharedCacheReader.ts +++ b/src/artifacts/apple/DyldSharedCacheReader.ts @@ -187,6 +187,7 @@ const readHeader = async (file: CacheFile): Promise => { if ( mappingOffset < 0x20 || mappingOffset > MAX_HEADER_BYTES || + mappingCount === 0 || mappingCount > MAX_MAPPINGS ) throw new ArtifactReaderFailure( diff --git a/src/artifacts/apple/DylibResolutionReader.ts b/src/artifacts/apple/DylibResolutionReader.ts index fb071c2c4..fd1000adc 100644 --- a/src/artifacts/apple/DylibResolutionReader.ts +++ b/src/artifacts/apple/DylibResolutionReader.ts @@ -148,19 +148,24 @@ export const traceDylibResolution = async (options: { "integrity", `Active target digest changed: expected ${options.targetSha256}, observed ${targetDigest}`, ); + // Bind the trace to the cache bytes that produced it: a replaced cache + // can retain header UUID/architecture while changing resolutions. + const cacheIdentity = + cache === undefined || parsed.data.shared_cache === undefined + ? null + : { + path: resolve(parsed.data.shared_cache), + uuid: cache.header.uuid, + architecture: cache.header.architecture, + os_version: cache.header.os_version, + main_file_sha256: await cache.mainSha256(options.signal), + subcache_sha256: await cache.subcacheSha256(options.signal), + }; return dylibResolutionResultSchema.parse({ ...trace, root_path: options.rootPath, target_sha256: options.targetSha256, - shared_cache: - cache === undefined || parsed.data.shared_cache === undefined - ? null - : { - path: resolve(parsed.data.shared_cache), - uuid: cache.header.uuid, - architecture: cache.header.architecture, - os_version: cache.header.os_version, - }, + shared_cache: cacheIdentity, images: trace.images.map((image) => ({ ...image, sha256: digests.get(image.path), diff --git a/src/domain/apple/dylibResolutionSchema.ts b/src/domain/apple/dylibResolutionSchema.ts index 85ebf46ff..a6c618236 100644 --- a/src/domain/apple/dylibResolutionSchema.ts +++ b/src/domain/apple/dylibResolutionSchema.ts @@ -169,6 +169,8 @@ export const dylibResolutionResultSchema = z.strictObject({ uuid: z.string(), architecture: z.string(), os_version: z.string().nullable(), + main_file_sha256: digestSchema, + subcache_sha256: z.record(z.string(), digestSchema).default({}), }) .nullable(), roots: z.array(