From 5d6029ea62a65ec34fd739ee40223c77c6fdb63b Mon Sep 17 00:00:00 2001
From: morluto <76467478+morluto@users.noreply.github.com>
Date: Sat, 17 Oct 2026 20:10:12 +0800
Subject: [PATCH] fix(apple): derive launchd domains from owning directories
Use the matched Contents/Library/LaunchAgents or LaunchDaemons suffix to
select a plist's domain. Remove the independent scan of every ancestor.
Keep regular-file, application-root, sidecar and suffix admission rules.
Adapt @bnkaiteam's proposal and ZIP boundary regression from PR #3504,
adding public CLI/stdio MCP controls for nested bundles, case variants,
misleading ancestors and excluded paths.
Validation: 53 focused checks, build, check:fast and docs:check pass.
Two new controls fail before the fix. 12 completed actual built CLI cost
controls preserve the ordinary reply and projection ID byte for byte.
No heavy profiling, target or real deep-provider execution; npm release
and whole-agent costs remain unverified.
Fixes #3353
---
src/domain/apple/appleApplication.ts | 37 +++-----
.../applications/appleLaunchdPlists.test.ts | 93 +++++++++++++++++++
.../filesystem/appleApplication.test.ts | 51 ++++++++++
3 files changed, 158 insertions(+), 23 deletions(-)
create mode 100644 tests/acceptance/applications/appleLaunchdPlists.test.ts
diff --git a/src/domain/apple/appleApplication.ts b/src/domain/apple/appleApplication.ts
index 8c013dc7d2..a5b06d0dd3 100644
--- a/src/domain/apple/appleApplication.ts
+++ b/src/domain/apple/appleApplication.ts
@@ -241,6 +241,14 @@ const appleRootFormat = (format: string): RootFormat => {
const isNativeLibrary = (path: string): boolean =>
/\.(?:dylib|so)$/iu.test(path);
+const launchdDomain = (path: string): "agent" | "daemon" | undefined => {
+ if (anatomyTailMatches(path, ["Contents", "Library", "LaunchAgents", "*"]))
+ return "agent";
+ if (anatomyTailMatches(path, ["Contents", "Library", "LaunchDaemons", "*"]))
+ return "daemon";
+ return undefined;
+};
+
const classifyComponents = (
entries: readonly ProjectedInventoryEntry[],
all: readonly Component[],
@@ -280,29 +288,12 @@ const classifyComponents = (
privileged_helpers: matching((path) =>
anatomyTailMatches(path, ["Contents", "Library", "LaunchServices", "*"]),
),
- launchd_plists: matching(
- (path) =>
- path.endsWith(".plist") &&
- (anatomyTailMatches(path, [
- "Contents",
- "Library",
- "LaunchAgents",
- "*",
- ]) ||
- anatomyTailMatches(path, [
- "Contents",
- "Library",
- "LaunchDaemons",
- "*",
- ])),
- ).map((component) => ({
- ...component,
- domain: component.path
- .split("/")
- .some((segment) => anatomyNameIs(segment, "LaunchDaemons"))
- ? ("daemon" as const)
- : ("agent" as const),
- })),
+ launchd_plists: matching((path) => path.endsWith(".plist")).flatMap(
+ (component) => {
+ const domain = launchdDomain(component.path);
+ return domain === undefined ? [] : [{ ...component, domain }];
+ },
+ ),
helpers: matching((path) =>
anatomyTailMatches(path, ["Contents", "Helpers", "*"]),
),
diff --git a/tests/acceptance/applications/appleLaunchdPlists.test.ts b/tests/acceptance/applications/appleLaunchdPlists.test.ts
new file mode 100644
index 0000000000..c319377ef6
--- /dev/null
+++ b/tests/acceptance/applications/appleLaunchdPlists.test.ts
@@ -0,0 +1,93 @@
+import { writeFile } from "node:fs/promises";
+import { join } from "node:path";
+
+import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
+import { expect } from "vitest";
+
+import { appleApplicationProjectionResultSchema } from "../../../src/domain/apple/appleApplication.js";
+import { compareUnicodeCodePoints } from "../../../src/domain/unicodeCodePointOrder.js";
+import { parseEvidence } from "../../../src/domain/evidence.js";
+import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
+import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
+import { cliTest } from "../../support/cli/cliFixture.js";
+
+cliTest(
+ "Apple launchd domains follow their owning directory through CLI and MCP",
+ async ({ cli }) => {
+ const root = await createTestTempDirectory("rea-apple-launchd-cli-");
+ const environment = { HOME: root, USERPROFILE: root };
+ const expected = [
+ [
+ "LaunchDaemons/Sample.app/Contents/Library/LaunchAgents/agent.plist",
+ "agent",
+ ],
+ [
+ "LaunchDaemons/Sample.app/Contents/Resources/LaunchDaemons/Nested.app/Contents/Library/LaunchAgents/nested.plist",
+ "agent",
+ ],
+ [
+ "LaunchAgents/Other.app/Contents/Library/LaunchDaemons/daemon.plist",
+ "daemon",
+ ],
+ [
+ "launchdaemons/Case.app/contents/library/launchagents/mixed.plist",
+ "agent",
+ ],
+ ] as const;
+ const writer = new ZipWriter(new Uint8ArrayWriter());
+ for (const [path] of expected)
+ await writer.add(path, new TextReader(""));
+ for (const path of [
+ "LaunchDaemons/Sample.app/Contents/Library/LaunchAgents/nested/ignored.plist",
+ "LaunchDaemons/Sample.app/Contents/Library/LaunchAgents/not-a-plist",
+ "Contents/Library/LaunchAgents/unrooted.plist",
+ "__MACOSX/Sample.app/Contents/Library/LaunchAgents/sidecar.plist",
+ ])
+ await writer.add(path, new TextReader(""));
+ await writer.add(
+ "LaunchDaemons/Sample.app/Contents/Library/LaunchAgents/directory.plist/",
+ undefined,
+ { directory: true },
+ );
+ const path = join(root, "Launchd.zip");
+ await writeFile(path, await writer.close());
+ const inspected = await cli.run({
+ arguments: ["inspect-artifact", path, "--json"],
+ environment,
+ });
+ expect(inspected.exitCode, inspected.stderr).toBe(0);
+ const source = parseEvidence(inspected.json);
+ const input = { inventory_evidence: [source] };
+ const projected = await cli.run({
+ arguments: [
+ "project-apple-application-graph",
+ JSON.stringify(input),
+ "--json",
+ ],
+ environment,
+ });
+ expect(projected.exitCode, projected.stderr).toBe(0);
+ const evidence = parseEvidence(projected.json);
+ const result = appleApplicationProjectionResultSchema.parse(
+ evidence.normalized_result,
+ );
+ expect(
+ result.components.launchd_plists.map(({ path, domain }) => [
+ path,
+ domain,
+ ]),
+ ).toEqual(
+ [...expected].sort(([left], [right]) =>
+ compareUnicodeCodePoints(left, right),
+ ),
+ );
+ expect(result.coverage).toEqual({
+ status: "complete-within-inventory",
+ inventory_complete: true,
+ });
+ const { call } = await connectLocalToolsMcp(environment);
+ const mcp = await call("project_apple_application_graph", input);
+ expect(mcp.isError).not.toBe(true);
+ expect(parseEvidence(mcp.structuredContent)).toEqual(evidence);
+ },
+);
diff --git a/tests/boundary/filesystem/appleApplication.test.ts b/tests/boundary/filesystem/appleApplication.test.ts
index c83dcaf589..94f4daa85c 100644
--- a/tests/boundary/filesystem/appleApplication.test.ts
+++ b/tests/boundary/filesystem/appleApplication.test.ts
@@ -318,6 +318,57 @@ it("applies application-root and sidecar rules to unread entries", async () => {
});
describe("macOS application projection", () => {
+ it("classifies launchd plists by their owning directory instead of unrelated ancestors", async () => {
+ const root = await createTestTempDirectory("rea-apple-launchd-domain-");
+ const path = join(root, "Launchd.zip");
+ const expected = [
+ [
+ "LaunchDaemons/Sample.app/Contents/Library/LaunchAgents/agent.plist",
+ "agent",
+ ],
+ [
+ "LaunchDaemons/Sample.app/Contents/Resources/LaunchDaemons/Nested.app/Contents/Library/LaunchAgents/nested.plist",
+ "agent",
+ ],
+ [
+ "LaunchAgents/Other.app/Contents/Library/LaunchDaemons/daemon.plist",
+ "daemon",
+ ],
+ [
+ "launchdaemons/Case.app/contents/library/launchagents/mixed.plist",
+ "agent",
+ ],
+ ] as const;
+ const writer = new ZipWriter(new Uint8ArrayWriter());
+ for (const [entry] of expected)
+ await writer.add(entry, new TextReader(""));
+ await writer.add(
+ "LaunchDaemons/Sample.app/Contents/Library/LaunchAgents/nested/ignored.plist",
+ new TextReader(""),
+ );
+ await writeFile(path, await writer.close());
+
+ const inventory = parseEvidence(
+ await runProviderAnalysis(path, "inventory_artifact", {}),
+ );
+ const projection = appleApplicationProjectionResultSchema.parse(
+ requireSuccessfulProjection(
+ projectAppleApplicationEvidence({ inventory_evidence: [inventory] }),
+ ).normalized_result,
+ );
+ expect(
+ projection.components.launchd_plists.map(({ path, domain }) => [
+ path,
+ domain,
+ ]),
+ ).toEqual(expect.arrayContaining([...expected]));
+ expect(projection.components.launchd_plists).toHaveLength(expected.length);
+ expect(projection.coverage).toEqual({
+ status: "complete-within-inventory",
+ inventory_complete: true,
+ });
+ });
+
it("projects a macOS .app directory inventory with nested bundle roles", async () => {
const root = await createTestTempDirectory("rea-apple-macos-");
const app = join(root, "Fixture.app");