From 8a9acd29409340ba2df149dc93e7c7e442cf65ce Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 2 Jun 2026 01:56:16 +0200 Subject: [PATCH] test(mac): exercise codesign entitlement use --- test/scripts/codesign-mac-app.test.ts | 99 ++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/test/scripts/codesign-mac-app.test.ts b/test/scripts/codesign-mac-app.test.ts index bf03c066728..a6d20a1fce5 100644 --- a/test/scripts/codesign-mac-app.test.ts +++ b/test/scripts/codesign-mac-app.test.ts @@ -1,5 +1,14 @@ import { spawnSync } from "node:child_process"; -import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs"; +import { + chmodSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + rmSync, + writeFileSync, +} from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; @@ -28,6 +37,50 @@ function runCodesign(args: string[], tempRoot: string) { }); } +function installFakeCodesign(binDir: string) { + const fakeCodesign = path.join(binDir, "codesign"); + writeFileSync( + fakeCodesign, + `#!/usr/bin/env bash +set -euo pipefail + +entitlements="" +target="" +while [ "$#" -gt 0 ]; do + case "$1" in + --entitlements) + shift + entitlements="$1" + ;; + esac + target="$1" + shift || true +done + +if [ -z "$target" ]; then + echo "missing codesign target" >&2 + exit 2 +fi + +if [ -n "$entitlements" ]; then + count_file="$CODESIGN_CAPTURE_DIR/count" + count=0 + if [ -f "$count_file" ]; then + count="$(cat "$count_file")" + fi + count=$((count + 1)) + printf '%s' "$count" >"$count_file" + copy="$CODESIGN_CAPTURE_DIR/entitlements-$count.plist" + cp "$entitlements" "$copy" + printf 'entitled\\t%s\\t%s\\t%s\\n' "$target" "$entitlements" "$copy" >>"$CODESIGN_LOG" +else + printf 'plain\\t%s\\n' "$target" >>"$CODESIGN_LOG" +fi +`, + ); + chmodSync(fakeCodesign, 0o755); +} + afterEach(() => { for (const dir of tempDirs.splice(0)) { rmSync(dir, { recursive: true, force: true }); @@ -82,4 +135,48 @@ describe("codesign-mac-app temp file hygiene", () => { expect(result.status).not.toBe(0); expect(entitlementTemps(tempRoot)).toEqual([]); }); + + it("passes generated app entitlements to signing commands and cleans them", () => { + const tempRoot = makeTempDir("openclaw-codesign-success-"); + const app = path.join(tempRoot, "Fake.app"); + const binDir = path.join(tempRoot, "bin"); + const captureDir = path.join(tempRoot, "capture"); + const logPath = path.join(captureDir, "codesign.log"); + mkdirSync(path.join(app, "Contents", "MacOS"), { recursive: true }); + mkdirSync(binDir); + mkdirSync(captureDir); + writeFileSync(path.join(app, "Contents", "MacOS", "OpenClaw"), "#!/bin/sh\n"); + installFakeCodesign(binDir); + + const result = spawnSync("bash", [scriptPath, app], { + cwd: process.cwd(), + encoding: "utf8", + env: { + ...process.env, + CODESIGN_CAPTURE_DIR: captureDir, + CODESIGN_LOG: logPath, + PATH: `${binDir}${path.delimiter}${process.env.PATH ?? ""}`, + SIGN_IDENTITY: "-", + SKIP_TEAM_ID_CHECK: "1", + TMPDIR: tempRoot, + }, + }); + + expect(result.status).toBe(0); + expect(result.stdout).toContain(`Codesign complete for ${app}`); + + const signLines = readFileSync(logPath, "utf8").trim().split("\n"); + expect(signLines).toHaveLength(2); + expect(signLines[0]).toContain(`${path.join(app, "Contents", "MacOS", "OpenClaw")}\t`); + expect(signLines[1]).toContain(`${app}\t`); + for (const line of signLines) { + const [, , entitlementPath, copiedEntitlementsPath] = line.split("\t"); + const copiedEntitlements = readFileSync(copiedEntitlementsPath, "utf8"); + expect(entitlementPath).toContain("openclaw-entitlements"); + expect(existsSync(entitlementPath)).toBe(false); + expect(copiedEntitlements).toContain("com.apple.security.automation.apple-events"); + expect(copiedEntitlements).toContain("com.apple.security.device.camera"); + } + expect(entitlementTemps(tempRoot)).toEqual([]); + }); });