From e13da084e7c5967b80632ae5ad8d3d37bcf57469 Mon Sep 17 00:00:00 2001 From: yasshmittal Date: Tue, 13 Oct 2026 20:25:21 +0530 Subject: [PATCH] fix(apple): exclude AppleDouble sidecars from IPA components (#2838) * fix(apple): exclude AppleDouble sidecars from IPA components IPA projection returned every inventoried component before the AppleDouble sidecar filter applied to directory, ZIP and DMG roots, so ._* and __MACOSX/ metadata appeared as native libraries and frameworks while the result reported that sidecars are excluded. Apply the sidecar exclusion first for every root format; IPA still keeps non-sidecar components outside Payload/. Fixes #2546 Co-Authored-By: Claude Opus 5.5 * test(apple): exercise the __MACOSX exclusion with a non-dotfile name The IPA fixture's __MACOSX entry also started with "._", so the AppleDouble filename rule excluded it and a broken __MACOSX check would go unnoticed. Use a library name that only the __MACOSX rule excludes; the native library assertion now fails if that rule regresses. Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- src/domain/apple/appleApplication.test.ts | 28 +++++++++++++++++++++++ src/domain/apple/appleApplication.ts | 7 +++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/domain/apple/appleApplication.test.ts b/src/domain/apple/appleApplication.test.ts index 9caf21fcbb..3f8906e2b8 100644 --- a/src/domain/apple/appleApplication.test.ts +++ b/src/domain/apple/appleApplication.test.ts @@ -417,6 +417,34 @@ describe("Apple bundle layouts and containers", () => { }); }); +describe("Apple IPA components", () => { + it("excludes AppleDouble sidecars from IPA components", () => { + const result = project( + inventoryEvidence("ipa", "Fixture.ipa", [ + { path: "Payload/Fixture.app/Info.plist" }, + macho("Payload/Fixture.app/Fixture"), + { path: "Payload/Fixture.app/Contents/Resources/._libfoo.dylib" }, + { path: "Payload/Fixture.app/Frameworks/Foo.framework/._Foo" }, + { path: "__MACOSX/Payload/Fixture.app/libghost.dylib" }, + macho("Payload/Fixture.app/Contents/Resources/libreal.dylib"), + macho("SwiftSupport/iphoneos/libswiftCore.dylib"), + ]), + ); + expect(result.components.native_libraries.map(({ path }) => path)).toEqual([ + "Payload/Fixture.app/Contents/Resources/libreal.dylib", + "SwiftSupport/iphoneos/libswiftCore.dylib", + ]); + expect(result.components.frameworks).toEqual([]); + expect(result.components.executables.map(({ path }) => path)).toEqual([ + "Payload/Fixture.app/Contents/Resources/libreal.dylib", + "Payload/Fixture.app/Fixture", + ]); + expect(result.limitations).toContainEqual( + expect.stringContaining("AppleDouble sidecar entries"), + ); + }); +}); + describe("macOS application scope", () => { it("attributes only components inside the application roots", () => { const result = project( diff --git a/src/domain/apple/appleApplication.ts b/src/domain/apple/appleApplication.ts index d46ddf26b0..dfb34e3c54 100644 --- a/src/domain/apple/appleApplication.ts +++ b/src/domain/apple/appleApplication.ts @@ -218,8 +218,9 @@ export const appleProjectionId = (projection: unknown): string => `aap_${digest(projection)}`; /** - * IPA projection keeps every archive component, as before. Directory, ZIP, - * and DMG inventories can hold installers and other siblings, so only + * AppleDouble sidecars describe neighbouring files and are never components. + * IPA projection keeps every other archive component, as before. Directory, + * ZIP, and DMG inventories can hold installers and other siblings, so only * components inside an application root are attributed to it. */ const attributeComponents = ( @@ -227,8 +228,8 @@ const attributeComponents = ( roots: readonly string[], rootFormat: RootFormat, ): { readonly all: Component[]; readonly unattributed: number } => { - if (rootFormat === "ipa") return { all: [...inventoried], unattributed: 0 }; const visible = inventoried.filter(({ path }) => !isSidecar(path)); + if (rootFormat === "ipa") return { all: visible, unattributed: 0 }; const all = visible.filter(({ path }) => roots.some((root) => isWithin(path, root)), );