Skip to content

feat(desktop): bundle the built-in relay helper, signed and hash-checked (K3) - #628

Merged
TheCryptoDonkey merged 1 commit into
mainfrom
feat/k3-desktop-relay-bundle
Oct 30, 2026
Merged

TheCryptoDonkey merged 1 commit into
mainfrom
feat/k3-desktop-relay-bundle

Conversation

@TheCryptoDonkey

Copy link
Copy Markdown
Member

Stage K3 of the built-in call relay (docs/design/2026-10-28-built-in-relay.md, 1.2 and 7). Every desktop package carries spry-node from the spry-turn 0.4.0 release. Still hidden: nothing starts it; K4 moves the supervisor onto it.

Package Placement
macOS Contents/Resources/spry/spry-node, outside the asar, Developer ID + hardened runtime + timestamp
Windows resources\spry\spry-node.exe (unsigned, like the app)
Linux tarball resources/spry/spry-node, 0755
Deb /opt/KithMoot/resources/spry/spry-node, root, 0755, no setuid (the AppArmor profile is unconfined, so no change there)

How

  • desktop/scripts/spry-release.json pins each archive and its spry-node by SHA-256 (taken from the published release; the release's SHA256SUMS checked first).
  • desktop/scripts/spry-helper.mjs downloads an archive once into artifacts/spry/downloads, checks both hashes every build, and extracts only spry-node, afresh each time. No runtime download.
  • The packagers write spry-manifest.json into the app with the helper's hash per platform. desktop/spry-bundle.mjs gives K4's supervisor the helper's path only while its bytes match, so a helper replaced in a user-writable install is refused.
  • macOS: signing rewrites a binary and re-signing is not repeatable (checked: ad hoc re-signs give different bytes), so the helper is signed first and the app's osx-sign run ignores it; the manifest holds the final bytes' hash. Local previews now sign through osx-sign ad hoc for the same reason (previously codesign --deep, which would re-sign the helper). After signing, the packager checks the bundle (--deep --strict), the helper's own signature, its Developer ID authority and runtime flag, and its hash.

Checks

  • node --test desktop/test/spry-bundle.test.mjs desktop/test/linux-package.test.mjs: 15 pass (path per platform; manifest shape and missing platform refused; mismatched or missing helper refused; staging fetches once, checks archive and binary hashes, extracts only spry-node afresh; tampered archive or binary refused; the checked-in pin covers all four platforms).
  • Real fetch of all four helpers from the release: hashes match the pin.
  • M4: package:linux (tarballs and debs), package:windows, and a local-preview package:mac all build; dpkg -c shows the helper root-owned 0755; the manifest inside the app's asar equals the bundled helper's hash; verifiedHelper passes against the packaged app; the bundled helper launches.
  • This Mac, release keychain: signed package:mac with the helper inside was accepted by Apple notarisation, stapled, and Gatekeeper accepts the app (source=Notarized Developer ID); the helper launches from the notarised bundle.

Release cost: each archive grows by one helper (about 4 to 5 MB uncompressed). The next desktop release ships it; nothing changes for users until K5.

https://claude.ai/code/session_01P4tkjt9pRAySng1JJnHn9t

Stage K3 of the built-in call relay. Every desktop package now carries
spry-node from the spry-turn 0.4.0 release, outside the asar: macOS
Contents/Resources/spry/spry-node, Windows resources\spry\spry-node.exe,
Linux resources/spry/spry-node, and in the deb
/opt/KithMoot/resources/spry/spry-node (root, 0755). Nothing starts it yet.

scripts/spry-release.json pins each archive and binary by SHA-256;
scripts/spry-helper.mjs fetches the archive once, checks both hashes and
extracts only spry-node, afresh for every build. The packagers write
spry-manifest.json into the app with the helper's hash per platform, and
spry-bundle.mjs hands the supervisor (K4) the helper's path only while its
bytes match, so a helper replaced in a user-writable install is refused.

On macOS the helper is signed first (Developer ID, hardened runtime,
timestamp; ad hoc for a local preview) and the app's signing leaves it
alone, because signing rewrites a binary and re-signing is not repeatable;
the manifest holds the hash of the final bytes. A local preview now signs
through osx-sign ad hoc, so it skips the helper too. The packager checks
the helper's signature, authority, runtime flag and hash after signing.
@TheCryptoDonkey
TheCryptoDonkey merged commit 12e62eb into main Oct 30, 2026
47 checks passed
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