Skip to content

fix(security): sandbox, release hardening, and provenance attestations - #9

Merged
bendns merged 3 commits into
mainfrom
fix/security-hardening
Oct 20, 2026
Merged

bendns merged 3 commits into
mainfrom
fix/security-hardening

Conversation

@bendns

@bendns bendns commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Fixes from a security audit of the codebase. Nothing critical was found: the app has no network code, URL schemes, XPC or shell-outs, and no secrets in git history. These changes harden how Hop is built, signed and released.

Root causes and fixes

1. Release builds could be attached to by any local process (confirmed)

  • Ad-hoc signing (CODE_SIGN_IDENTITY=-) adds com.apple.security.get-task-allow even in Release. That cancels the hardened runtime's protection against debugger attachment and code injection.
  • Fix: set CODE_SIGN_INJECT_BASE_ENTITLEMENTS: NO for Release. Verified: the built app carries only the sandbox entitlements.
  • The release workflow now fails if get-task-allow ever reappears.
  • make install installs a Release build instead of Debug.

2. The release pipeline ran unpinned tools while holding a write token

  • Split into a build job (read-only, plus attestation permissions) and a publish job, the only one with contents: write, which runs no third-party tooling.
  • Actions are pinned to commit SHAs, each release at least two weeks old (checkout v7.0.1, upload-artifact v7.0.1, download-artifact v8.0.1, attest-build-provenance v4.2.2).
  • XcodeGen is pinned to 2.46.0 with a SHA-256 check (scripts/install-xcodegen.sh) instead of brew install.
  • persist-credentials: false on every checkout.

3. Users couldn't verify the download came from this repo

  • Releases now carry signed build provenance attestations. The release notes and README document gh attestation verify Hop-<version>.zip -R bendns/hop.
  • Developer ID signing and notarization stay out of scope (they need a paid Apple Developer account).

4. The manual-release tag input wasn't validated

  • The tag must match ^v[0-9]+\.[0-9]+\.[0-9]+$.
  • Step outputs are passed through env instead of being interpolated into shell scripts.
  • gh release create --verify-tag refuses to create a tag that doesn't exist.
  • Manual runs now build the tagged commit. Previously they built the branch the workflow was started from.

5. The app wasn't sandboxed

  • App Sandbox enabled, with files.user-selected.read-write for CSV export.
  • Existing data is copied into the container once by LegacyDataMigration, using read-only temporary exceptions for exactly two paths. The originals are never modified.
  • I didn't use Apple's container-migration.plist: it only runs when the container is first created. A local test run had already created the container (MCMMetadataCreator = com.apple.dt.xcodebuild), so that one-time migration had been skipped.
  • Unit test hosts skip the migration so they can't use up a developer's one-time copy.

Verification

  • 61 unit tests pass (added LegacyDataMigrationTests).
  • End-to-end migration check: I re-signed a Release build under a separate bundle id (com.bendns.hopapp.migtest) and launched it sandboxed with only the narrow entitlements. It copied the real history.json (26.6K) and all hop.* preferences into its own container, and the originals were unchanged.
  • The release workflow can only be fully exercised by a real tag push. CI on this PR covers the pinned XcodeGen installer.

Notes

  • Dependabot PR build(deps): bump actions/checkout from 4 to 7 #6 (checkout v4 to v7) is superseded. Dependabot will keep updating the SHA pins.
  • The docs/plans removal landed in the first commit instead of the docs commit. Squash merge makes this irrelevant.

Files changed

  • project.yml, Hop/Hop.entitlements, Makefile
  • Hop/HopApp.swift, Hop/Persistence/HistoryStore.swift, Hop/Persistence/LegacyDataMigration.swift (new)
  • HopTests/LegacyDataMigrationTests.swift (new)
  • .github/workflows/ci.yml, .github/workflows/release.yml, scripts/install-xcodegen.sh (new)
  • README.md, docs/plans/ (removed)

Parts of this fix were generated with Claude Code assistance.

…uilds

- Release: CODE_SIGN_INJECT_BASE_ENTITLEMENTS=NO; ad-hoc signing otherwise
  ships get-task-allow, letting local processes attach despite the
  hardened runtime. make install now installs a Release build
- enable the App Sandbox with user-selected read-write for CSV export
- copy pre-sandbox history and preferences into the container once,
  via read-only temporary exceptions for those two paths; originals are
  left untouched and test hosts skip the migration
…ance

- build job runs read-only; only the publish job gets contents: write
- pin actions to commit SHAs and XcodeGen to 2.46.0 with a SHA-256 check
- persist-credentials: false on checkout
- validate the release tag and build the tagged commit on manual runs
- pass step outputs via env instead of interpolating them into scripts
- fail the release if get-task-allow ever reappears
- add build provenance attestations and document gh attestation verify
@bendns
bendns marked this pull request as ready for review October 20, 2026 13:42
@bendns
bendns merged commit 64e8b3c into main Oct 20, 2026
1 check passed
@bendns
bendns deleted the fix/security-hardening branch October 20, 2026 13:43
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