Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/desktop/resources/entitlements.mac.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
</dict>
Comment on lines +9 to +11

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical resources/entitlements.mac.plist:9

The entitlements file includes com.apple.security.device.audio-input for microphone access but is missing com.apple.security.device.camera, so camera access will be silently denied on signed builds with hardened runtime enabled despite the NSCameraUsageDescription being present in Info.plist. Consider adding the camera entitlement to match the intended TCC permissions fix.

 	<key>com.apple.security.device.audio-input</key>
 	<true/>
+	<key>com.apple.security.device.camera</key>
+	<true/>
 </dict>
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/desktop/resources/entitlements.mac.plist around lines 9-11:

The entitlements file includes `com.apple.security.device.audio-input` for microphone access but is missing `com.apple.security.device.camera`, so camera access will be silently denied on signed builds with hardened runtime enabled despite the `NSCameraUsageDescription` being present in Info.plist. Consider adding the camera entitlement to match the intended TCC permissions fix.

</plist>
11 changes: 11 additions & 0 deletions apps/desktop/scripts/electron-launcher.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ function patchMainBundleInfoPlist(appBundlePath, iconPath) {
copyFileSync(iconPath, join(resourcesDir, "electron.icns"));
}

function resignAppBundle(appBundlePath) {
const result = spawnSync("codesign", ["--force", "--deep", "--sign", "-", appBundlePath], {
encoding: "utf8",
});
if (result.status !== 0) {
const detail = result.stderr?.trim() || result.stdout?.trim() || "";
throw new Error(`Failed to re-sign app bundle at ${appBundlePath}: ${detail}`);
}
}

function patchHelperBundleInfoPlists(appBundlePath) {
const frameworksDir = join(appBundlePath, "Contents", "Frameworks");
if (!existsSync(frameworksDir)) {
Expand Down Expand Up @@ -127,6 +137,7 @@ function buildMacLauncher(electronBinaryPath) {
cpSync(sourceAppBundlePath, targetAppBundlePath, { recursive: true });
patchMainBundleInfoPlist(targetAppBundlePath, iconPath);
patchHelperBundleInfoPlists(targetAppBundlePath);
resignAppBundle(targetAppBundlePath);
writeFileSync(metadataPath, `${JSON.stringify(expectedMetadata, null, 2)}\n`);

return targetBinaryPath;
Expand Down
29 changes: 28 additions & 1 deletion scripts/build-desktop-artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,26 @@ const createBuildConfig = Effect.fn("createBuildConfig")(function* (
target: target === "dmg" ? [target, "zip"] : [target],
icon: "icon.icns",
category: "public.app-category.developer-tools",
extendInfo: {
NSMicrophoneUsageDescription:
"T3 Code needs microphone access so that apps launched from its integrated terminal can use audio input.",
NSCameraUsageDescription:
"T3 Code needs camera access so that apps launched from its integrated terminal can use the camera.",
NSAppleEventsUsageDescription:
"T3 Code needs to send Apple events to control other applications.",
},
...(signed
? {
// Signed builds: hardened runtime + entitlements (required for notarization).
entitlements: "apps/desktop/resources/entitlements.mac.plist",
entitlementsInherit: "apps/desktop/resources/entitlements.mac.plist",
}
: {
// Unsigned builds: ad-hoc sign without hardened runtime so macOS TCC
// can identify the app and show privacy permission prompts.
identity: "-",
hardenedRuntime: false,
}),
};
}

Expand Down Expand Up @@ -654,7 +674,14 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
}
}
if (!options.signed) {
buildEnv.CSC_IDENTITY_AUTO_DISCOVERY = "false";
// On macOS, we still need at least ad-hoc signing (identity: "-" in the
// mac build config) so macOS TCC can attribute privacy permissions (mic,
// camera, etc.) to the app. Disabling signing entirely would leave the
// app with an unbound Info.plist and no sealed resources, causing macOS to
// silently deny TCC prompts for child processes launched from the terminal.
if (options.platform !== "mac") {
buildEnv.CSC_IDENTITY_AUTO_DISCOVERY = "false";
}
delete buildEnv.CSC_LINK;
delete buildEnv.CSC_KEY_PASSWORD;
delete buildEnv.APPLE_API_KEY;
Expand Down