Skip to content

feat: configure macOS hardened runtime, entitlements, and build envir…#300

Open
samirpatil2000 wants to merge 1 commit intosiddharthvaddem:mainfrom
samirpatil2000:main
Open

feat: configure macOS hardened runtime, entitlements, and build envir…#300
samirpatil2000 wants to merge 1 commit intosiddharthvaddem:mainfrom
samirpatil2000:main

Conversation

@samirpatil2000
Copy link
Copy Markdown

@samirpatil2000 samirpatil2000 commented Apr 3, 2026

Description

This PR implements a complete, signed, and notarized macOS build pipeline for the Openscreen application. It introduces a manual build script that handles packaging, code signing with Apple's Hardened Runtime, and notarization via Apple's Notary Service (notarytool). This ensures the produced .dmg installers for both arm64 and x64 architectures are trusted by Gatekeeper and can be distributed outside the Mac App Store without security warnings.

Motivation

Previously, macOS builds were unsigned, leading to the "App is from an unidentified developer" warning, which creates friction for users. By implementing the Hardened Runtime and Notarization process, we satisfy Apple's security requirements for distribution.

Type of Change

  • New Feature (Automated build & notarization pipeline)
  • Bug Fix
  • Refactor / Code Cleanup (Updated electron-builder configuration)
  • Documentation Update (Added notarization guide and GitHub Actions instructions)
  • Other (please specify)

Related Issue(s)

Fixes issues related to macOS distribution and Gatekeeper acceptance.

Screenshots / Video

Verification of Notarized DMG:
image

Testing

  • Local Build: Ran chmod +x scripts/build_macos.sh && ./scripts/build_macos.sh on an arm64 Mac.
  • Signature Verification: Used codesign --verify to ensure the .app bundle and the resulting .dmg are correctly signed.
  • Gatekeeper Check: Verified both arm64 and x64 DMGs using spctl to confirm they are accepted by the system as Notarized Developer ID packages.
  • Entitlements: Verified that camera, microphone, and system audio capture permissions are still functional under the new Hardened Runtime.

Checklist

  • I have performed a self-review of my code.
  • I have added any necessary screenshots or videos.
  • I have updated the .gitignore to prevent leaking signing credentials.

Summary by CodeRabbit

  • Chores
    • Added macOS app signing and notarization infrastructure for secure distribution.
    • Implemented automated macOS release build process with code signing and notarization workflow.
    • Configured macOS security entitlements for audio and camera permissions.
    • Added environment configuration templates and build automation scripts.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

A new macOS application build and signing pipeline is established, adding environment variable templates, hardened runtime and entitlements configuration, build automation script, and updated electron-builder settings to enable code signing and notarization for secure distribution.

Changes

Cohort / File(s) Summary
Configuration & Environment
.env.example, .gitignore, electron-builder.json5
Added environment variable templates for build credentials and Apple signing details; updated gitignore to exclude .env; enabled hardened runtime, notarization settings, and entitlements references in macOS builder config.
Code Signing Entitlements
macos.entitlements
New plist file defining macOS entitlements including V8 JIT compilation, unsigned executable memory, disabled library validation, and device permissions for audio input and camera.
Build Automation
scripts/build_macos.sh
New Bash script orchestrating complete macOS release builds, including dependency installation, TypeScript compilation, Electron packaging per architecture, code signing, DMG creation, notarization, and stapling for distribution.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer
    participant Script as build_macos.sh
    participant EB as Electron Builder
    participant Signer as Code Signing (codesign)
    participant Notary as Apple Notarization
    participant Stapler as Stapler Service

    Dev->>Script: Execute build script
    Script->>Script: Validate environment & prerequisites
    Script->>EB: npm ci + npx tsc + npx vite build
    EB->>EB: Compile application
    Script->>EB: electron-builder --mac (arm64 & x64)
    EB->>EB: Package .app bundles
    
    rect rgba(100, 150, 255, 0.5)
        Note over Script,Stapler: Per Architecture (arm64, x64)
    end
    
    Script->>Script: Create DMG structure
    Script->>Signer: codesign --verify .app
    Signer->>Signer: Verify signature
    Script->>Script: hdiutil create DMG
    Script->>Signer: codesign --force --sign DMG
    Signer->>Signer: Sign DMG with identity
    
    Script->>Notary: xcrun notarytool submit DMG
    Notary->>Notary: Scan & validate app
    Notary->>Script: Return ticket
    
    Script->>Stapler: xcrun stapler staple DMG
    Stapler->>Stapler: Attach notarization ticket
    Script->>Script: xcrun stapler validate
    
    Script->>Dev: Output signed/notarized DMGs
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A script hops forth with purpose grand,
Signing apps across the Apple land,
Hardened runtimes, entitlements bright,
Notarization seals—all snug and tight!
From build to staple, each step aligned,
Security and trust, perfectly twined! 🔐

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: configuring macOS hardened runtime, entitlements, and build environment setup. It accurately reflects the primary purpose of the PR.
Description check ✅ Passed The PR description is comprehensive and follows the template structure with all key sections completed: Description, Motivation, Type of Change, Related Issues, Screenshots/Video, Testing, and Checklist.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78901a8076

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

CSC_NAME="$CSC_NAME" npx electron-builder --mac --${ARCH} --dir

# Find the .app bundle
APP_BUNDLE=$(find "${RELEASE_DIR}" -maxdepth 2 -name "*.app" -type d | grep -i "${ARCH}\|mac" | head -n1)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Select app bundle by architecture before creating DMG

The app bundle lookup is not architecture-specific: grep -i "${ARCH}\|mac" | head -n1 will match any previously generated macOS bundle, and the script does not clean release/${version} between arch iterations. When the x64 pass runs after arm64, this can pick the arm64 .app and package/notarize it as the x64 DMG, which would ship a non-runnable build to Intel Mac users. Restrict this lookup to the current arch output path (or clean per-arch outputs) before selecting the bundle.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
.env.example (1)

1-10: Clarify which env vars are required at build time vs one-time setup.

BUNDLE_ID and APPLE_APP_SPECIFIC_PASSWORD appear unused by the current build execution path, so this template can mislead users about mandatory inputs. Consider labeling optional/one-time variables inline.

✏️ Suggested clarification
 APP_NAME=Openscreen
 BUNDLE_ID=com.siddharthvaddem.openscreen
 
 APPLE_ID=
 TEAM_ID=
 SIGN_IDENTITY="Developer ID Application: Samir Patil ()"
 CSC_NAME="Samir Patil ()"
 
+# Required for build+notarize execution
 NOTARY_PROFILE=OpenScreen-notary
+
+# Optional: only needed when creating/updating notary credentials profile
 APPLE_APP_SPECIFIC_PASSWORD=
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.env.example around lines 1 - 10, The .env.example currently lists variables
like BUNDLE_ID and APPLE_APP_SPECIFIC_PASSWORD without indicating whether they
are required at build time or only needed for one-time setup, which can mislead
users; update the template to annotate each variable (e.g., BUNDLE_ID, APPLE_ID,
TEAM_ID, SIGN_IDENTITY, CSC_NAME, NOTARY_PROFILE, APPLE_APP_SPECIFIC_PASSWORD)
with a short inline comment marking it as "required at build time", "one-time
setup", or "optional", and where applicable note the exact command or step that
consumes it (e.g., notarization step uses NOTARY_PROFILE and
APPLE_APP_SPECIFIC_PASSWORD) so users know when to populate them.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/build_macos.sh`:
- Around line 15-23: After sourcing ENV_FILE, add an explicit non-empty
validation step for required environment variables (e.g., APP_NAME,
SIGN_IDENTITY, NOTARY_PROFILE, etc.) to prevent empty values slipping through;
implement this by declaring a list/array like REQUIRED_ENV=("APP_NAME"
"SIGN_IDENTITY" "NOTARY_PROFILE") and looping over it, testing each with: if [
-z "${!var}" ]; then echo "ERROR: $var is required and must not be empty"; exit
1; fi; run this check immediately after the source "$ENV_FILE" block so
preflight code (the later checks that reference SIGN_IDENTITY and others) never
sees empty values.
- Around line 138-139: The script currently masks codesign failures by piping a
failing codesign into a warning and then unconditionally printing success;
change the logic around the codesign --verify --deep --strict "$APP_BUNDLE" call
(referenced by APP_BUNDLE and ARCH and the print_warn/print_ok helpers) so that
you capture its exit status and fail the script on non-zero (e.g., call
print_error and exit 1) instead of downgrading to a warning; only call print_ok
"[${ARCH}] .app signature verified" when the verify command succeeded, and if
you still want to surface non-fatal warnings, separate stdout/stderr parsing
from the command exit code rather than overriding a non-zero exit to a success
path.
- Around line 124-128: The current APP_BUNDLE selection is non-deterministic
because the grep "-i 'ARCH|mac'" can match unrelated results and pick the wrong
.app; change the selection to search deterministically by first finding .app
directories whose names include the exact ARCH token (case-insensitive) using
RELEASE_DIR and ARCH (e.g. find -iname "*${ARCH}*.app"), then if none found fall
back to a mac-specific name (e.g. "*mac*.app"), and only as a final fallback
pick any .app; update the APP_BUNDLE assignment logic to try these searches in
that order so APP_BUNDLE reliably picks the correct architecture bundle.

---

Nitpick comments:
In @.env.example:
- Around line 1-10: The .env.example currently lists variables like BUNDLE_ID
and APPLE_APP_SPECIFIC_PASSWORD without indicating whether they are required at
build time or only needed for one-time setup, which can mislead users; update
the template to annotate each variable (e.g., BUNDLE_ID, APPLE_ID, TEAM_ID,
SIGN_IDENTITY, CSC_NAME, NOTARY_PROFILE, APPLE_APP_SPECIFIC_PASSWORD) with a
short inline comment marking it as "required at build time", "one-time setup",
or "optional", and where applicable note the exact command or step that consumes
it (e.g., notarization step uses NOTARY_PROFILE and APPLE_APP_SPECIFIC_PASSWORD)
so users know when to populate them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aca11e56-f7d3-4acd-8550-494508ed5d48

📥 Commits

Reviewing files that changed from the base of the PR and between b101820 and 78901a8.

📒 Files selected for processing (5)
  • .env.example
  • .gitignore
  • electron-builder.json5
  • macos.entitlements
  • scripts/build_macos.sh

Comment on lines +15 to +23
if [ -f "$ENV_FILE" ]; then
set -a
source "$ENV_FILE"
set +a
else
echo "ERROR: .env file not found at ${ENV_FILE}"
echo "Create one with APP_NAME, SIGN_IDENTITY, NOTARY_PROFILE, etc."
exit 1
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate required env vars are non-empty before preflight checks.

Right now unset variables fail fast, but empty values can slip through (notably Line 70 with an empty SIGN_IDENTITY) and break later stages unpredictably.

🛠️ Proposed fix
 if [ -f "$ENV_FILE" ]; then
     set -a
     source "$ENV_FILE"
     set +a
 else
@@
 fi
+
+REQUIRED_VARS=(APP_NAME SIGN_IDENTITY CSC_NAME NOTARY_PROFILE)
+for var in "${REQUIRED_VARS[@]}"; do
+    if [ -z "${!var:-}" ]; then
+        print_err "Required variable '$var' is missing or empty in .env"
+        exit 1
+    fi
+done
@@
-if ! security find-identity -v -p codesigning | grep -q "$SIGN_IDENTITY"; then
+if ! security find-identity -v -p codesigning | grep -Fq "$SIGN_IDENTITY"; then

Also applies to: 69-75

🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 17-17: ShellCheck can't follow non-constant source. Use a directive to specify location.

(SC1090)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/build_macos.sh` around lines 15 - 23, After sourcing ENV_FILE, add an
explicit non-empty validation step for required environment variables (e.g.,
APP_NAME, SIGN_IDENTITY, NOTARY_PROFILE, etc.) to prevent empty values slipping
through; implement this by declaring a list/array like REQUIRED_ENV=("APP_NAME"
"SIGN_IDENTITY" "NOTARY_PROFILE") and looping over it, testing each with: if [
-z "${!var}" ]; then echo "ERROR: $var is required and must not be empty"; exit
1; fi; run this check immediately after the source "$ENV_FILE" block so
preflight code (the later checks that reference SIGN_IDENTITY and others) never
sees empty values.

Comment on lines +124 to +128
APP_BUNDLE=$(find "${RELEASE_DIR}" -maxdepth 2 -name "*.app" -type d | grep -i "${ARCH}\|mac" | head -n1)
if [ -z "$APP_BUNDLE" ]; then
# Fallback: find any .app in the output
APP_BUNDLE=$(find "${RELEASE_DIR}" -maxdepth 2 -name "*.app" -type d | head -n1)
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Architecture bundle selection is non-deterministic and can package the wrong .app.

Line 124 matches any path containing mac, so during the second loop iteration it can pick the previous architecture’s app bundle. This can produce mislabeled installers.

🧭 Proposed fix
-    APP_BUNDLE=$(find "${RELEASE_DIR}" -maxdepth 2 -name "*.app" -type d | grep -i "${ARCH}\|mac" | head -n1)
-    if [ -z "$APP_BUNDLE" ]; then
-        # Fallback: find any .app in the output
-        APP_BUNDLE=$(find "${RELEASE_DIR}" -maxdepth 2 -name "*.app" -type d | head -n1)
-    fi
+    APP_BUNDLE=""
+    case "$ARCH" in
+      arm64)
+        [ -d "${RELEASE_DIR}/mac-arm64/${APP_NAME}.app" ] && APP_BUNDLE="${RELEASE_DIR}/mac-arm64/${APP_NAME}.app"
+        ;;
+      x64)
+        [ -d "${RELEASE_DIR}/mac/${APP_NAME}.app" ] && APP_BUNDLE="${RELEASE_DIR}/mac/${APP_NAME}.app"
+        [ -z "$APP_BUNDLE" ] && [ -d "${RELEASE_DIR}/mac-x64/${APP_NAME}.app" ] && APP_BUNDLE="${RELEASE_DIR}/mac-x64/${APP_NAME}.app"
+        ;;
+    esac
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/build_macos.sh` around lines 124 - 128, The current APP_BUNDLE
selection is non-deterministic because the grep "-i 'ARCH|mac'" can match
unrelated results and pick the wrong .app; change the selection to search
deterministically by first finding .app directories whose names include the
exact ARCH token (case-insensitive) using RELEASE_DIR and ARCH (e.g. find -iname
"*${ARCH}*.app"), then if none found fall back to a mac-specific name (e.g.
"*mac*.app"), and only as a final fallback pick any .app; update the APP_BUNDLE
assignment logic to try these searches in that order so APP_BUNDLE reliably
picks the correct architecture bundle.

Comment on lines +138 to +139
codesign --verify --deep --strict "$APP_BUNDLE" 2>&1 || print_warn "[${ARCH}] Deep verify had warnings (may be expected pre-notarization)"
print_ok "[${ARCH}] .app signature verified"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not mask code-sign verification failures.

Line 138 currently downgrades verification failure to warning and still prints success on Line 139, which can push a broken artifact further into notarization/stapling.

✅ Proposed fix
-    codesign --verify --deep --strict "$APP_BUNDLE" 2>&1 || print_warn "[${ARCH}] Deep verify had warnings (may be expected pre-notarization)"
-    print_ok "[${ARCH}] .app signature verified"
+    if ! codesign --verify --deep --strict "$APP_BUNDLE" 2>&1; then
+        print_err "[${ARCH}] .app signature verification failed"
+        exit 1
+    fi
+    print_ok "[${ARCH}] .app signature verified"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
codesign --verify --deep --strict "$APP_BUNDLE" 2>&1 || print_warn "[${ARCH}] Deep verify had warnings (may be expected pre-notarization)"
print_ok "[${ARCH}] .app signature verified"
if ! codesign --verify --deep --strict "$APP_BUNDLE" 2>&1; then
print_err "[${ARCH}] .app signature verification failed"
exit 1
fi
print_ok "[${ARCH}] .app signature verified"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/build_macos.sh` around lines 138 - 139, The script currently masks
codesign failures by piping a failing codesign into a warning and then
unconditionally printing success; change the logic around the codesign --verify
--deep --strict "$APP_BUNDLE" call (referenced by APP_BUNDLE and ARCH and the
print_warn/print_ok helpers) so that you capture its exit status and fail the
script on non-zero (e.g., call print_error and exit 1) instead of downgrading to
a warning; only call print_ok "[${ARCH}] .app signature verified" when the
verify command succeeded, and if you still want to surface non-fatal warnings,
separate stdout/stderr parsing from the command exit code rather than overriding
a non-zero exit to a success path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant