From ff7b48548782f9018cd79eb7cd1b7cdabd81a2d2 Mon Sep 17 00:00:00 2001 From: GeneralD Date: Mon, 15 Jun 2026 19:14:23 +0900 Subject: [PATCH 1/2] =?UTF-8?q?chore(#23):=20=E3=83=90=E3=82=A4=E3=83=8A?= =?UTF-8?q?=E3=83=AA=E3=81=ABInfo.plist=E3=82=92=E5=9F=8B=E3=82=81?= =?UTF-8?q?=E8=BE=BC=E3=81=BFTCC=E7=94=A8=E3=81=AE=E5=AE=89=E5=AE=9Abundle?= =?UTF-8?q?=20identifier=E3=82=92=E4=BB=98=E4=B8=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TCCは権限グラント(将来のスペクトラムアナライザのシステム音声キャプチャ等)を bundle identityでキーする。埋め込みInfo.plistが無いとグラントは実行ファイルの パスにキーされ、再インストールごとにリセットされる。これが#23のBLOCKER。 - Sources/CLI/Info.plist: CFBundleIdentifier=com.generald.lyra を定義 - Package.swift: -sectcreate __TEXT __info_plist でMach-Oに埋め込む (バンドルリソースではなくセクション埋め込みなので exclude も指定) - Makefile / CI(auto-tag.yml): `codesign --force --sign -` で署名にバインド swift build -c release のad-hoc署名はInfo.plistをbindしないため再署名が必要 --- .github/workflows/auto-tag.yml | 6 ++++++ Makefile | 4 ++++ Package.swift | 17 +++++++++++++++++ Sources/CLI/Info.plist | 14 ++++++++++++++ Sources/VersionHandler/Resources/version.txt | 2 +- 5 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 Sources/CLI/Info.plist diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml index b992afc5..9a1979bb 100644 --- a/.github/workflows/auto-tag.yml +++ b/.github/workflows/auto-tag.yml @@ -64,6 +64,9 @@ jobs: STAGING="lyra-${VERSION}-macos-arm64" mkdir -p "$STAGING" cp "$BUILD_DIR/lyra" "$STAGING/" + # Bind the embedded __info_plist (CFBundleIdentifier) into the ad-hoc + # signature so TCC keys permission grants by bundle identity (#23). + codesign --force --sign - "$STAGING/lyra" find "$BUILD_DIR" -name '*.bundle' -exec cp -R {} "$STAGING/" \; tar czf "${STAGING}.tar.gz" "$STAGING" echo "ARCHIVE=${STAGING}.tar.gz" >> "$GITHUB_ENV" @@ -89,6 +92,9 @@ jobs: STAGING="lyra-${VERSION}-macos-arm64" mkdir -p "$STAGING" cp "$BUILD_DIR/lyra" "$STAGING/" + # Bind the embedded __info_plist (CFBundleIdentifier) into the ad-hoc + # signature so TCC keys permission grants by bundle identity (#23). + codesign --force --sign - "$STAGING/lyra" find "$BUILD_DIR" -name '*.bundle' -exec cp -R {} "$STAGING/" \; tar czf "${STAGING}.tar.gz" "$STAGING" gh release upload "$TAG" "${STAGING}.tar.gz" --clobber diff --git a/Makefile b/Makefile index 6b7a1377..9fb0b75d 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,10 @@ install: build install -d $(PREFIX)/bin install $(BUILD_DIR)/$(BINARY) $(PREFIX)/bin/$(BINARY) find $(BUILD_DIR) -name '*.bundle' -exec cp -R {} $(PREFIX)/bin/ \; + # Bind the embedded __TEXT,__info_plist (CFBundleIdentifier) into the + # signature. `swift build -c release` leaves it unbound; TCC keys permission + # grants by bundle identity, so the binary needs a re-sign to expose it (#23). + codesign --force --sign - $(PREFIX)/bin/$(BINARY) uninstall: rm -f $(PREFIX)/bin/$(BINARY) diff --git a/Package.swift b/Package.swift index 55f7c56f..99a3f611 100644 --- a/Package.swift +++ b/Package.swift @@ -25,6 +25,23 @@ let package = Package( "AsyncRunnableCommand", .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "Dependencies", package: "swift-dependencies"), + ], + // Info.plist is embedded into the Mach-O __TEXT,__info_plist section + // (see linkerSettings) rather than copied as a bundle resource, so it + // is excluded from SwiftPM's resource processing here. + exclude: ["Info.plist"], + // Embed Info.plist so the binary carries a stable CFBundleIdentifier. + // TCC keys permission grants (e.g. system-audio capture for the planned + // spectrum analyzer, #23) by bundle identity; without an embedded plist + // the grant is keyed to the executable path and resets on every reinstall. + // The path is relative to the package root, where the linker is invoked. + linkerSettings: [ + .unsafeFlags([ + "-Xlinker", "-sectcreate", + "-Xlinker", "__TEXT", + "-Xlinker", "__info_plist", + "-Xlinker", "Sources/CLI/Info.plist", + ]) ] ), diff --git a/Sources/CLI/Info.plist b/Sources/CLI/Info.plist new file mode 100644 index 00000000..70c29e90 --- /dev/null +++ b/Sources/CLI/Info.plist @@ -0,0 +1,14 @@ + + + + + CFBundleIdentifier + com.generald.lyra + CFBundleName + lyra + CFBundleExecutable + lyra + CFBundleInfoDictionaryVersion + 6.0 + + diff --git a/Sources/VersionHandler/Resources/version.txt b/Sources/VersionHandler/Resources/version.txt index 68e69e40..3b1fc795 100644 --- a/Sources/VersionHandler/Resources/version.txt +++ b/Sources/VersionHandler/Resources/version.txt @@ -1 +1 @@ -2.15.0 +2.15.1 From b0cd360449e09ae46e82edc4927ba80b0776128f Mon Sep 17 00:00:00 2001 From: GeneralD Date: Mon, 15 Jun 2026 19:22:41 +0900 Subject: [PATCH 2/2] =?UTF-8?q?docs(#23):=20VM=E6=A4=9C=E8=A8=BCrule?= =?UTF-8?q?=E3=81=ABcodesign/Info.plist=E3=83=90=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=83=89=E6=A4=9C=E8=A8=BC=E6=89=8B=E9=A0=86=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #291の署名バインドの検証方法をプロジェクトの検証レーンに反映。 併せて実地で踏んだ2点のgotchaも記録: - run-lyraの「daemon crashed at startup」はswift-interpret初回起動の 遅延によるfalse negativeがある(pgrepで生存確認すべき) - run-lyra同時実行は/tmp/lyra-dropのscp衝突を起こすため禁止 - env表にLYRA_VM_SSH_HOST(Apple Virtualization backend必須)を追加 --- .claude/rules/vm-verification.md | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/.claude/rules/vm-verification.md b/.claude/rules/vm-verification.md index 63701321..59f97f38 100644 --- a/.claude/rules/vm-verification.md +++ b/.claude/rules/vm-verification.md @@ -26,6 +26,7 @@ self-contained; no user-global skill or rule is required to run it. | CPU / memory profiling (`lyra benchmark`, `sample`) | VM | | Screen resolution change (approximation via Dynamic Resolution) | VM — see note below | | `lyra healthcheck` / API smoke | VM | +| Code signing / Info.plist binding (TCC bundle identity) | VM (`codesign -dvv`, `otool -P`) — see scenario below | | Display hot-plug (external monitor attach / detach) | ScreenProvider fixture + final manual smoke | | NSScreen topology change (`NSApplicationDidChangeScreenParameters`) | ScreenProvider fixture | | Visual overlay pixel verification | Host debug-build lane (`dev-verification.md`) | @@ -100,6 +101,7 @@ $SCRIPT shutdown $VM # graceful guest shutdown | Variable | Default | Purpose | |---|---|---| +| `LYRA_VM_SSH_HOST` | (unset) | Guest IP override — **required for Apple Virtualization backend**, where `utmctl ip-address` is unsupported. Find it via the guest's `/var/db/dhcpd_leases` or `ifconfig`. | | `LYRA_VM_SSH_USER` | `admin` | Guest login name | | `LYRA_VM_SSH_KEY` | `~/.ssh/vm_rsa` | SSH private key path | | `LYRA_VM_SSH_PORT` | `22` | Guest SSH port | @@ -153,6 +155,32 @@ $SCRIPT restore $VM $SCRIPT shutdown $VM ``` +### Code signing / Info.plist binding (TCC bundle identity) + +When a change embeds an `Info.plist` (Mach-O `__TEXT,__info_plist` section) so +TCC can key permission grants by **bundle identity** rather than executable +path (#23), verify the binding inside the guest — a clean macOS install proves +the result without the host's accumulated signing state. + +`swift build -c release` embeds the section but its ad-hoc signature leaves it +**unbound** (`Info.plist=not bound`, `Identifier=`). Only an +explicit `codesign --force --sign -` (what `make install` and CI packaging run) +binds it — codesign then derives `Identifier` from the embedded +`CFBundleIdentifier`. + +```sh +$SCRIPT run-lyra $VM # pushes the release binary +BIN=/tmp/lyra-vm-test/lyra +$SCRIPT exec $VM -- "otool -P $BIN" # section present? CFBundleIdentifier? +$SCRIPT exec $VM -- "codesign -dvv $BIN 2>&1 | grep -E 'Identifier|Info.plist'" # BEFORE: not bound +$SCRIPT exec $VM -- "codesign --force --sign - $BIN && codesign -dvv $BIN 2>&1 | grep -E 'Identifier|Info.plist'" # AFTER: entries=N +``` + +Expected transition: `Identifier=lyra` / `Info.plist=not bound` → +`Identifier=com.generald.lyra` / `Info.plist entries=4`. Re-signing changes the +cdhash, so **restart the daemon** with the bound binary and re-`capture` to +prove it still executes and renders (no-regression). + --- ## Agent rules @@ -168,3 +196,14 @@ $SCRIPT shutdown $VM replace this requirement. - **Restore always runs.** The `restore` subcommand must run even if an intermediate step fails. Use `trap` in any script that calls `run-lyra`. +- **`run-lyra` "daemon crashed at startup" can be a false negative.** The + harness checks `kill -0 $pid` shortly after launch, but the daemon's + first-launch `swift-frontend -interpret` of the MediaRemote helper takes + 1–2 s; a slow guest can trip the check while the process is in fact alive. + Before trusting the `die`, confirm with `$SCRIPT exec $VM -- "pgrep -x lyra"` + and the daemon log — if the PID is alive, proceed. +- **Never run two `run-lyra` concurrently.** Both build under the same + `.build` (SwiftPM serializes with a lock) and both stage into the guest's + `/tmp/lyra-drop`; the second `scp` hits `Permission denied` on the + half-written bundle. Let one finish, or `sudo rm -rf /tmp/lyra-drop` on the + guest before retrying.