[PR #947] [CLOSED] feat(apple): inspect dyld shared caches and resolve system paths #2524

Closed
opened 2026-10-17 21:07:09 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/947
Author: @kaoru0822-kitauji
Created: 10/7/2026
Status: ❌ Closed

Base: main ← Head: feat/dyld-shared-cache


📝 Commits (10+)

  • ade11fa feat(apple): inspect dyld shared caches and resolve system paths
  • 22beae9 Merge remote-tracking branch 'origin/feat/dylib-resolution' into feat/dyld-shared-cache
  • 202661f Merge branch 'feat/dylib-resolution' into feat/dyld-shared-cache
  • 305474d fix(apple): gate shared-cache answers on platform and verified subcaches
  • 1d943c5 Merge branch 'feat/dylib-resolution' into feat/dyld-shared-cache
  • 3b4969e Merge branch 'feat/dylib-resolution' into feat/dyld-shared-cache
  • ed40d2e Merge branch 'feat/dylib-resolution' into feat/dyld-shared-cache
  • 858aa9f fix(apple): hash the dyld cache through the handle its header was read from
  • 8e53f79 Merge branch 'feat/dylib-resolution' into feat/dyld-shared-cache
  • 09344aa chore(apple): drop an unused DylibCandidate re-export

📊 Changes

42 files changed (+3425 additions, -254 deletions)

View changed files

📝 .github/workflows/ci.yml (+3 -6)
📝 README_ar.md (+14 -14)
📝 README_ja.md (+14 -14)
📝 README_ko.md (+1 -1)
📝 README_zh.md (+1 -1)
📝 docs/apple-application-analysis.md (+67 -7)
📝 docs/testing.md (+6 -0)
📝 package.json (+3 -1)
📝 scripts/lib/artifact-e2e.mjs (+9 -6)
📝 scripts/lib/catalog-builders.mjs (+5 -2)
➕ scripts/verify/apple/dyld-shared-cache.mjs (+222 -0)
📝 skill-src/reverse-engineer-anything/references/native-and-artifacts.md (+4 -2)
➕ src/application/apple/DyldSharedCacheService.ts (+195 -0)
➕ src/artifacts/apple/DyldSharedCache.fixture.ts (+138 -0)
➕ src/artifacts/apple/DyldSharedCacheReader.ts (+780 -0)
📝 src/artifacts/apple/DylibResolutionReader.ts (+69 -5)
➕ src/artifacts/apple/FilesystemDyldPaths.test.ts (+53 -0)
➕ src/artifacts/apple/FilesystemDyldPaths.ts (+39 -0)
📝 src/artifacts/apple/MachoImage.fixture.ts (+13 -0)
📝 src/artifacts/apple/MachoLoadCommandReader.test.ts (+39 -0)

...and 22 more files

📄 Description

Builds on #927 (trace_dylib_resolution); this branch contains it. Once #927 merges, the diff shrinks to the last commit.

Summary

On macOS 11 and later, system libraries exist only inside the dyld shared cache. trace_dylib_resolution therefore left every /usr/lib and /System load undetermined, and an @rpath that lists /usr/lib/swift before a bundled copy stayed conditional. REA also had no way to look inside a cache.

  • New inspect_dyld_shared_cache (CLI inspect-dyld-shared-cache) reads a cache file set selected by path, without an open target. It reports the header, mappings, subcaches and the complete image list, and reads load commands for requested images.
  • trace_dylib_resolution gains an optional shared_cache (CLI --shared-cache). Absolute candidates found in a cache that serves the process's CPU family resolve as shared-cache.

Change and scope

  • DyldSharedCache (src/artifacts/DyldSharedCacheReader.ts):
    • Pure TypeScript. It reads dyld_cache_header fields by the offsets in Apple's open-source dyld, valid only below mappingOffset, so older single-file caches fall back to the legacy image table.
    • It reports the UUID, architecture, platform and OS version, Mac Catalyst alternate, cache type, shared region and max slide.
    • It reports every mapping, and every subcache by file suffix, checked against its UUID (present, missing or uuid-mismatch; anything but present makes coverage partial).
    • It lists every image with its address. Lookups use a map, and positional reads are block-cached.
    • Requested images are parsed through the cache's VM mappings by the existing MachoLoadCommandReader. They come back as parsed, absent, unmapped, malformed or unsupported.
  • inspect_dyld_shared_cache is an application-family tool. Its Evidence subject is the main cache file, identified by digest, under APPLE_APPLICATION_PROVIDER.
    • Errors are typed: input errors, path for a missing file, format for a non-cache file, unavailable when it cannot be read, and cancelled.
  • Trace integration:
    • A shared-cache candidate outcome stops the search, as dyld does, so a cached /usr/lib/swift/libswiftCore.dylib beats a later bundled copy.
    • A cache serves its CPU family (arm64e serves arm64). Processes of another family keep undetermined, with a limitation.
    • Cached images are not traversed.
    • The result's shared_cache names the path, UUID, architecture and OS version used.

Not included: extracting images, slide info, local symbols, cache code signatures, or analysing cached images with Hopper or Ghidra.

Contract and boundary impact

  • CLI and MCP:
    • New tool and command; the catalog goes from 132 to 133 tools, and Application workflows from 13 to 14 in all READMEs.
    • trace_dylib_resolution input gains shared_cache?, and its output gains shared_cache, a shared-cache candidate outcome and a shared-cache resolution status.
  • Effects: read-only evidence, with no process launch.
  • Generated metadata: catalog (the Apple provider now lists both tools), skill (version 29) and conformance digests are regenerated. scripts/lib/catalog-builders.mjs attributes the new tool to rea-apple-application.

Real results on macOS 26.6 (arm64e cache F2E86C53-…, 12 subcaches, 3,649 images):

Target Without shared_cache With the host cache
Lane fixture 14 system edges undetermined 14 shared-cache
Raycast 235 undetermined 232 shared-cache; 3 remain undetermined because those /usr/lib/swift libraries are not in this OS's cache

Evidence and regression coverage

  • tests/boundary/filesystem/dyldSharedCache.test.ts uses a synthetic cache from the new DyldSharedCache.fixture.ts:
    • header, subcache and image list, with a subcache image's dylib_use_command flags;
    • absent, uuid-mismatch, missing subcache, and unmapped images;
    • legacy single-file headers;
    • input, missing-file and non-cache errors;
    • the Evidence wrapper.
  • src/domain/dylibResolution.test.ts:
    • cached system and @rpath candidates beat bundled fallbacks;
    • uncached paths stay undetermined;
    • an x86_64 process is not served by an arm64e cache.
  • CLI and MCP boundary tests:
    • trace-dylib-resolution --shared-cache;
    • inspect_dyld_shared_cache through an in-memory MCP client, with no target and no provider launch.
  • Real lane npm run verify:dyld-shared-cache, added to the macOS CI job:
    • all 3,649 host-cache images, with every dependency and its re-export/weak-link/upward/delay-init attributes, equal dyld_info -all_dyld_cache -linked_dylibs;
    • CLI and stdio MCP results match;
    • the clang fixture traced with --shared-cache must name the same cache UUID;
    • every shared-cache edge must be a library dyld actually loads under DYLD_PRINT_LIBRARIES.

Remaining proof gaps

  • Real verification covers one macOS 26.6 arm64e cache locally, plus the macOS 14 CI runner. Intel x86_64h caches and iOS caches extracted from IPSWs are exercised only through synthetic fixtures.

Validation performed

  • npm run check:changed: 292 files passed.
  • npm run test:focused -- src/domain/dylibResolution.test.ts tests/boundary/filesystem/dyldSharedCache.test.ts tests/boundary/mcp/dyldSharedCacheMcp.test.ts tests/boundary/cli/dylibResolutionCli.test.ts tests/boundary/mcp/dylibResolutionMcp.test.ts tests/boundary/cli/productCatalog.test.ts tests/boundary/mcp/toolSchemaValidity.test.ts tests/acceptance/contractPresentation.test.ts: passed.
  • npm run docs:generate && npm run docs:check, npx oxfmt --check ., npm run lint: no new errors.
  • npm run verify:dyld-shared-cache: {"ok":true,"mocked":false,"cli":true,"stdio_mcp":true,"cache":{"architecture":"arm64e","os_version":"26.6.0","images":3649},"images_matching_dyld_info":3649,"trace_shared_cache_edges":5,"dyld_loaded_every_cached_edge":true,…}.
  • npm run verify:macos-bundle: still passes.

Compatibility, safety, and release

  • Breaking changes: none. The additions are additive and shared_cache is optional.
  • Real Hopper/Ghidra coverage: not applicable.
  • Security: read-only; the header, mapping, subcache, image and path sizes are bounded, and subcache suffixes must match ^\.[A-Za-z0-9.]+$.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/947 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/7/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `feat/dyld-shared-cache` --- ### 📝 Commits (10+) - [`ade11fa`](https://github.com/morluto/rea/commit/ade11fa02d833d0c2a00da01a426cdabc04c7982) feat(apple): inspect dyld shared caches and resolve system paths - [`22beae9`](https://github.com/morluto/rea/commit/22beae9768af86b7839e7eb5c5c4f129c3f2b226) Merge remote-tracking branch 'origin/feat/dylib-resolution' into feat/dyld-shared-cache - [`202661f`](https://github.com/morluto/rea/commit/202661f83bdb681cabb21fa27df1cdb25489b3ef) Merge branch 'feat/dylib-resolution' into feat/dyld-shared-cache - [`305474d`](https://github.com/morluto/rea/commit/305474df6cbfd4120e52a832854830365a6f7651) fix(apple): gate shared-cache answers on platform and verified subcaches - [`1d943c5`](https://github.com/morluto/rea/commit/1d943c5eec1002d0605cf746f093fe643047ce4a) Merge branch 'feat/dylib-resolution' into feat/dyld-shared-cache - [`3b4969e`](https://github.com/morluto/rea/commit/3b4969e3dcc138535a14be70e697726544d9db4e) Merge branch 'feat/dylib-resolution' into feat/dyld-shared-cache - [`ed40d2e`](https://github.com/morluto/rea/commit/ed40d2e0017e79ff705e28968df145ae9fe660f6) Merge branch 'feat/dylib-resolution' into feat/dyld-shared-cache - [`858aa9f`](https://github.com/morluto/rea/commit/858aa9fd7d9b49e0f375fe4ba5fefba876a8e19b) fix(apple): hash the dyld cache through the handle its header was read from - [`8e53f79`](https://github.com/morluto/rea/commit/8e53f794f1e4b2a74216b2bf8db8bf5a7b5092b9) Merge branch 'feat/dylib-resolution' into feat/dyld-shared-cache - [`09344aa`](https://github.com/morluto/rea/commit/09344aa33d80108256f663e3d185d3226d7a6175) chore(apple): drop an unused DylibCandidate re-export ### 📊 Changes **42 files changed** (+3425 additions, -254 deletions) <details> <summary>View changed files</summary> 📝 `.github/workflows/ci.yml` (+3 -6) 📝 `README_ar.md` (+14 -14) 📝 `README_ja.md` (+14 -14) 📝 `README_ko.md` (+1 -1) 📝 `README_zh.md` (+1 -1) 📝 `docs/apple-application-analysis.md` (+67 -7) 📝 `docs/testing.md` (+6 -0) 📝 `package.json` (+3 -1) 📝 `scripts/lib/artifact-e2e.mjs` (+9 -6) 📝 `scripts/lib/catalog-builders.mjs` (+5 -2) ➕ `scripts/verify/apple/dyld-shared-cache.mjs` (+222 -0) 📝 `skill-src/reverse-engineer-anything/references/native-and-artifacts.md` (+4 -2) ➕ `src/application/apple/DyldSharedCacheService.ts` (+195 -0) ➕ `src/artifacts/apple/DyldSharedCache.fixture.ts` (+138 -0) ➕ `src/artifacts/apple/DyldSharedCacheReader.ts` (+780 -0) 📝 `src/artifacts/apple/DylibResolutionReader.ts` (+69 -5) ➕ `src/artifacts/apple/FilesystemDyldPaths.test.ts` (+53 -0) ➕ `src/artifacts/apple/FilesystemDyldPaths.ts` (+39 -0) 📝 `src/artifacts/apple/MachoImage.fixture.ts` (+13 -0) 📝 `src/artifacts/apple/MachoLoadCommandReader.test.ts` (+39 -0) _...and 22 more files_ </details> ### 📄 Description Builds on #927 (`trace_dylib_resolution`); this branch contains it. Once #927 merges, the diff shrinks to the last commit. ## Summary On macOS 11 and later, system libraries exist only inside the dyld shared cache. `trace_dylib_resolution` therefore left every `/usr/lib` and `/System` load `undetermined`, and an `@rpath` that lists `/usr/lib/swift` before a bundled copy stayed `conditional`. REA also had no way to look inside a cache. - **New `inspect_dyld_shared_cache`** (CLI `inspect-dyld-shared-cache`) reads a cache file set selected by path, without an open target. It reports the header, mappings, subcaches and the complete image list, and reads load commands for requested images. - **`trace_dylib_resolution` gains an optional `shared_cache`** (CLI `--shared-cache`). Absolute candidates found in a cache that serves the process's CPU family resolve as `shared-cache`. ## Change and scope - **`DyldSharedCache`** (`src/artifacts/DyldSharedCacheReader.ts`): - Pure TypeScript. It reads `dyld_cache_header` fields by the offsets in Apple's open-source dyld, valid only below `mappingOffset`, so older single-file caches fall back to the legacy image table. - It reports the UUID, architecture, platform and OS version, Mac Catalyst alternate, cache type, shared region and max slide. - It reports every mapping, and every subcache by file suffix, checked against its UUID (`present`, `missing` or `uuid-mismatch`; anything but `present` makes coverage `partial`). - It lists every image with its address. Lookups use a map, and positional reads are block-cached. - Requested images are parsed through the cache's VM mappings by the existing `MachoLoadCommandReader`. They come back as `parsed`, `absent`, `unmapped`, `malformed` or `unsupported`. - **`inspect_dyld_shared_cache`** is an application-family tool. Its Evidence subject is the main cache file, identified by digest, under `APPLE_APPLICATION_PROVIDER`. - Errors are typed: input errors, `path` for a missing file, `format` for a non-cache file, `unavailable` when it cannot be read, and `cancelled`. - **Trace integration:** - A `shared-cache` candidate outcome stops the search, as dyld does, so a cached `/usr/lib/swift/libswiftCore.dylib` beats a later bundled copy. - A cache serves its CPU family (arm64e serves arm64). Processes of another family keep `undetermined`, with a limitation. - Cached images are not traversed. - The result's `shared_cache` names the path, UUID, architecture and OS version used. Not included: extracting images, slide info, local symbols, cache code signatures, or analysing cached images with Hopper or Ghidra. ## Contract and boundary impact - **CLI and MCP:** - New tool and command; the catalog goes from 132 to 133 tools, and Application workflows from 13 to 14 in all READMEs. - `trace_dylib_resolution` input gains `shared_cache?`, and its output gains `shared_cache`, a `shared-cache` candidate outcome and a `shared-cache` resolution status. - **Effects:** read-only `evidence`, with no process launch. - **Generated metadata:** catalog (the Apple provider now lists both tools), skill (version 29) and conformance digests are regenerated. `scripts/lib/catalog-builders.mjs` attributes the new tool to `rea-apple-application`. Real results on macOS 26.6 (arm64e cache `F2E86C53-…`, 12 subcaches, 3,649 images): | Target | Without `shared_cache` | With the host cache | | --- | --- | --- | | Lane fixture | 14 system edges `undetermined` | 14 `shared-cache` | | Raycast | 235 `undetermined` | 232 `shared-cache`; 3 remain `undetermined` because those `/usr/lib/swift` libraries are not in this OS's cache | ## Evidence and regression coverage - `tests/boundary/filesystem/dyldSharedCache.test.ts` uses a synthetic cache from the new `DyldSharedCache.fixture.ts`: - header, subcache and image list, with a subcache image's `dylib_use_command` flags; - absent, uuid-mismatch, missing subcache, and unmapped images; - legacy single-file headers; - input, missing-file and non-cache errors; - the Evidence wrapper. - `src/domain/dylibResolution.test.ts`: - cached system and `@rpath` candidates beat bundled fallbacks; - uncached paths stay `undetermined`; - an x86_64 process is not served by an arm64e cache. - CLI and MCP boundary tests: - `trace-dylib-resolution --shared-cache`; - `inspect_dyld_shared_cache` through an in-memory MCP client, with no target and no provider launch. - **Real lane `npm run verify:dyld-shared-cache`**, added to the macOS CI job: - **all** 3,649 host-cache images, with every dependency and its `re-export`/`weak-link`/`upward`/`delay-init` attributes, equal `dyld_info -all_dyld_cache -linked_dylibs`; - CLI and stdio MCP results match; - the clang fixture traced with `--shared-cache` must name the same cache UUID; - every `shared-cache` edge must be a library dyld actually loads under `DYLD_PRINT_LIBRARIES`. **Remaining proof gaps** - Real verification covers one macOS 26.6 arm64e cache locally, plus the macOS 14 CI runner. Intel `x86_64h` caches and iOS caches extracted from IPSWs are exercised only through synthetic fixtures. ## Validation performed - `npm run check:changed`: 292 files passed. - `npm run test:focused -- src/domain/dylibResolution.test.ts tests/boundary/filesystem/dyldSharedCache.test.ts tests/boundary/mcp/dyldSharedCacheMcp.test.ts tests/boundary/cli/dylibResolutionCli.test.ts tests/boundary/mcp/dylibResolutionMcp.test.ts tests/boundary/cli/productCatalog.test.ts tests/boundary/mcp/toolSchemaValidity.test.ts tests/acceptance/contractPresentation.test.ts`: passed. - `npm run docs:generate && npm run docs:check`, `npx oxfmt --check .`, `npm run lint`: no new errors. - `npm run verify:dyld-shared-cache`: `{"ok":true,"mocked":false,"cli":true,"stdio_mcp":true,"cache":{"architecture":"arm64e","os_version":"26.6.0","images":3649},"images_matching_dyld_info":3649,"trace_shared_cache_edges":5,"dyld_loaded_every_cached_edge":true,…}`. - `npm run verify:macos-bundle`: still passes. ## Compatibility, safety, and release - Breaking changes: none. The additions are additive and `shared_cache` is optional. - Real Hopper/Ghidra coverage: not applicable. - Security: read-only; the header, mapping, subcache, image and path sizes are bounded, and subcache suffixes must match `^\.[A-Za-z0-9.]+$`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-17 21:07:09 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#2524
No description provided.