Skip to content

fix(build): sign all Mach-O files inside non-standard framework bundles#1253

Closed
TimeToBuildBob wants to merge 1 commit intoActivityWatch:masterfrom
TimeToBuildBob:fix/sign-all-framework-mach-o-binaries
Closed

fix(build): sign all Mach-O files inside non-standard framework bundles#1253
TimeToBuildBob wants to merge 1 commit intoActivityWatch:masterfrom
TimeToBuildBob:fix/sign-all-framework-mach-o-binaries

Conversation

@TimeToBuildBob
Copy link
Copy Markdown
Contributor

Root cause

The previous temp-copy fallback for non-standard framework bundles (added in #1250) only signed $fw/$fw_name — e.g., Python.framework/Python.

When cp -r assembles the .app bundle in build_app_tauri.sh, it dereferences symlinks, creating three physical copies of the Python binary:

  • Python.framework/Python
  • Python.framework/Versions/Current/Python
  • Python.framework/Versions/3.9/Python

Apple notarytool scans all three paths independently and rejects each unsigned copy:

"path": "ActivityWatch.app.zip/.../aw-watcher-window/Python.framework/Python",
"message": "The signature of the binary is invalid."

"path": "ActivityWatch.app.zip/.../aw-watcher-window/Python.framework/Versions/Current/Python",
"message": "The signature of the binary is invalid."

"path": "ActivityWatch.app.zip/.../aw-watcher-window/Python.framework/Versions/3.9/Python",
"message": "The signature of the binary is invalid."

Same pattern repeated for aw-watcher-afk and aw-watcher-input. Diagnosed from the rejection log captured by #1251 in run 24193329397.

Fix

In the ambiguous-bundle fallback, use find + xargs file | grep Mach-O to enumerate all Mach-O files inside the framework, then sign each one via the temp-copy approach (outside any .framework directory to avoid the codesign "bundle format is ambiguous" error).

# Before (only signed Python.framework/Python):
fw_binary="$fw/$fw_name"
cp "$fw_binary" "$tmp" && sign "$tmp" && cp "$tmp" "$fw_binary"

# After (signs all three physical copies):
find "$fw" -type f | xargs file | grep "Mach-O" | cut -d: -f1 | while read fw_binary; do
    cp "$fw_binary" "$tmp" && sign "$tmp" && cp "$tmp" "$fw_binary"
done

Validation

  • bash -n scripts/package/build_app_tauri.sh passes
  • The fix is confined to the bundle format is ambiguous fallback — no change to the happy path for standard framework bundles
  • CI will validate on the macOS runners (the only environment with APPLE_PERSONALID)

The previous temp-copy fallback for non-standard framework bundles only
signed $fw/$fw_name (e.g. Python.framework/Python). However, when cp -r
assembles the .app bundle in build_app_tauri.sh, it dereferences symlinks
— creating three physical copies of the Python binary at:
  - Python.framework/Python
  - Python.framework/Versions/Current/Python
  - Python.framework/Versions/3.9/Python

Apple notarytool scans all three paths independently and rejects each
unsigned copy with "The signature of the binary is invalid."

Fix: in the ambiguous-bundle fallback, use `find + xargs file | grep
Mach-O` to enumerate all Mach-O files inside the framework, then sign
each one via the temp-copy approach (outside the .framework directory
context to avoid the "bundle format is ambiguous" error from codesign).
@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

Closing as duplicate of #1252 which was opened by a parallel monitoring session with the same fix.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 9, 2026

Greptile Summary

This PR extends the "bundle format is ambiguous" fallback in the macOS code-signing step to enumerate and sign all Mach-O files inside non-standard framework bundles, rather than only the top-level $fw/$fw_name binary. The root cause was that cp -r dereferences symlinks when assembling the .app, producing multiple physical copies of the Python binary (Python, Versions/Current/Python, Versions/3.9/Python) that Apple notarytool rejects independently.

Confidence Score: 5/5

Safe to merge — the fix correctly addresses all three unsigned-copy paths that Apple notarytool was rejecting, and the only remaining finding is a pre-existing P2 style issue.

The logic change is narrowly scoped to the "bundle format is ambiguous" fallback path and doesn't touch the happy-path signing. The found guard correctly detects an empty Mach-O set. The sole finding (xargs not null-safe for spaces) is a pre-existing pattern already present in Step 1 and is a theoretical edge case for PyInstaller framework paths, which never contain spaces in practice.

No files require special attention beyond the minor xargs null-safety suggestion at line 179.

Vulnerabilities

No security concerns identified. The code-signing changes are scoped to the macOS build/packaging script; no secrets are handled, and no external input is used unsanitized.

Important Files Changed

Filename Overview
scripts/package/build_app_tauri.sh Extends the "bundle format is ambiguous" fallback to sign all Mach-O files inside the framework via temp-copy; the xargs file pipeline is not null-safe for paths with spaces, mirroring the same pre-existing pattern in Step 1.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Step 1: Sign standalone Mach-O files\n(skip .framework/.bundle/.plugin direct children)"] --> B["Step 2: Sign .framework/.bundle/.plugin bundles\n(deepest first)"]
    B --> C{"codesign succeeds?"}
    C -- Yes --> D["Log: Signed bundle"]
    C -- No --> E{"bundle format is ambiguous?"}
    E -- No --> F["ERROR: exit 1"]
    E -- Yes --> G["find all Mach-O files inside framework\nfind fw -type f | xargs file | grep Mach-O"]
    G --> H{"Any Mach-O found?\nfound == 0?"}
    H -- No --> I["ERROR: No Mach-O binaries found\nexit 1"]
    H -- Yes --> J["For each Mach-O binary:"]
    J --> K["mktemp → cp fw_binary → tmp"]
    K --> L["codesign --force --options runtime\n--entitlements on tmp"]
    L --> M["cp tmp → fw_binary (signed)"]
    M --> N["rm tmp"]
    N --> J
    J -- done --> O["Step 3: Sign top-level .app bundle"]
Loading

Reviews (1): Last reviewed commit: "fix(build): sign all Mach-O files inside..." | Re-trigger Greptile

rm -f "$tmp_binary"
else
echo "ERROR: Expected main binary not found at $fw_binary" >&2
done < <(find "$fw" -type f | xargs file | grep "Mach-O" | cut -d: -f1)
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.

P2 xargs file is not null-safe for paths with spaces

find … | xargs file splits on whitespace, so any framework binary whose path contains a space would be silently split into two invalid tokens, causing file to fail on both, grep to find no match, and that binary to be skipped without any warning. The same pattern exists in Step 1 (line 134); fixing both together would be consistent.

Suggested change
done < <(find "$fw" -type f | xargs file | grep "Mach-O" | cut -d: -f1)
done < <(find "$fw" -type f -print0 | xargs -0 file | grep "Mach-O" | cut -d: -f1)

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