5.2 KiB
Rsync Studio — Direct Distribution Release
Rsync Studio is designed for direct macOS distribution rather than the Mac App Store because it intentionally launches rsync/ssh and accesses user-selected arbitrary paths.
Release security baseline
- Developer ID Application signing.
- Hardened Runtime enabled.
- App Sandbox intentionally disabled for this product architecture.
- No Hardened Runtime exception entitlements.
- SSH host-key verification is never disabled.
- SSH uses BatchMode, connection timeout and keepalive limits.
- Password storage/authentication is not supported; use a private key or the macOS SSH agent.
- Advanced rsync arguments that can replace the SSH transport, run remote commands, load passwords, read/write arbitrary option/list/log/batch files or bypass controlled delete handling are rejected.
- Scheduled/file-watch
--deleterequires explicit per-job authorisation. - Run logs are sanitised and capped before storage and expire according to Settings.
PrivacyInfo.xcprivacydeclares no tracking/no collected data and the UserDefaults required-reason API.
Mandatory preflight
Before every release archive, run on the release Mac:
./Scripts/release-preflight.sh
This performs the static security policy checks, validates the privacy manifest, and performs a clean unsigned Release build with the installed macOS SDK so compiler/typecheck warnings or SDK incompatibilities are caught before signing.
Archive
In Xcode select Any Mac (Apple Silicon, Intel), choose Product > Archive, then distribute using Developer ID.
Alternatively:
xcodebuild -project "Rsync Studio.xcodeproj" -scheme "Rsync Studio" -configuration Release -archivePath "build/Rsync Studio.xcarchive" archive
Choose your Developer ID Application certificate/team in Xcode signing settings before archiving.
Verify signature
codesign --verify --deep --strict --verbose=2 "/path/to/Rsync Studio.app"
codesign -d --entitlements :- "/path/to/Rsync Studio.app"
The Release build should show Hardened Runtime and should not contain com.apple.security.get-task-allow.
Notarise
Store notarisation credentials in a Keychain profile once:
xcrun notarytool store-credentials "Rsync Studio Notary"
Submit a signed ZIP/DMG/PKG:
ditto -c -k --keepParent "/path/to/Rsync Studio.app" "Rsync Studio.zip"
xcrun notarytool submit "Rsync Studio.zip" --keychain-profile "Rsync Studio Notary" --wait
Staple and validate:
xcrun stapler staple "/path/to/Rsync Studio.app"
xcrun stapler validate "/path/to/Rsync Studio.app"
spctl --assess --type execute --verbose=4 "/path/to/Rsync Studio.app"
Pre-release functional security tests
- Local source nested inside destination is rejected.
- Local destination nested inside source is rejected.
- SSH host beginning with
-is rejected. - SSH username containing spaces/control characters is rejected.
- A
.pubkey cannot be selected as the effective private key. - Advanced arguments
-e,--rsh,--rsync-path,--password-file,--files-from,--log-file,--write-batch,--remove-source-files,--delete*and-Mare rejected, and malformed quoting is rejected. - Remote destination shell metacharacters are rejected before rsync is launched.
- Scheduled/watch job using
--deletecannot run until Allow unattended delete is explicitly enabled. - SSH to a previously unknown host does not silently bypass host-key checking.
- Scheduled SSH job with unavailable authentication fails rather than prompting indefinitely.
- History expiry removes old completed runs according to Settings.
- A failed persistent SwiftData store opens Recovery Mode instead of immediately crashing.
Scripts/security-check.shpasses before every signed build.
Signed update feed
The application contains a dependency-free signed update checker. It remains disabled until these generated Info.plist values are populated in the Release configuration:
RsyncStudioUpdateManifestURL: HTTPS URL to the JSON manifest.RsyncStudioUpdatePublicKey: Base64 raw Ed25519/Curve25519.Signing public key.
Manifest schema:
{
"version": "1.2.0",
"build": 120,
"downloadURL": "https://example.com/releases/Rsync Studio-1.2.0.dmg",
"signature": "BASE64_ED25519_SIGNATURE"
}
The exact signed bytes are UTF-8: version + "\n" + build + "\n" + downloadURL. The checker refuses HTTP feeds, unsigned manifests, invalid signatures, and non-HTTPS download URLs. It opens the verified signed download location; it does not silently install software. Keep the private signing key outside the source repository and build machine where practical.
Automated commercial release pipeline
Stage 2 adds Scripts/release.sh, which automates archive/export, signed-app verification, drag-to-Applications DMG creation, Apple notarization, stapling, Gatekeeper verification, SHA-256 generation and release metadata. It intentionally does not publish artifacts.
See Docs/COMMERCIAL_RELEASE_STAGE2.md for first-time setup and usage.
Stage 4 licensing backend
See Docs/COMMERCIAL_RELEASE_STAGE4.md and the Licensing Server/ Laravel overlay for production activation, purchase provisioning and webhook handling.