Checking Parallex for yourself
Everything on this page can be checked on your own Mac: how each release is signed, how an update proves it's Parallex's before it's installed, what the app is made of, every address it talks to, and the record it keeps of what's done with it. Where something isn't done yet, it says so.
On this page
How a release is signed
- Developer ID. Parallex.app and every program and library inside it (the
parallexcommand, the launcher, the link router, the web shell, the keychain helper and both libraries copies load) are signed with Parallex's Developer ID, Team ID UK74VYMUP7, with the hardened runtime and a secure timestamp. - Notarized and stapled. Apple notarizes the app before each release, and the ticket is stapled to it, so Gatekeeper can check it without asking Apple. The disk image is signed, notarized and stapled too.
- Checked at every start. The app and the
parallexcommand check their own signature when they start. A copy changed after it was signed is treated as unlicensed: it keeps running and opens every instance, and never deletes anything. - What Parallex makes on your Mac is signed there. Copies are re-signed with a certificate made on your Mac, and instances' small wrapper apps are signed ad hoc, so neither carries the Developer ID. Parallex in managed fleets says what that means for EDR and Santa.
To check the app you have:
codesign --verify --deep --strict --verbose=2 /Applications/Parallex.app
spctl -a -vv /Applications/Parallex.app
# source=Notarized Developer ID
# origin=Developer ID Application: Mandip Adhikari (UK74VYMUP7)
xcrun stapler validate /Applications/Parallex.app
How an update proves it's Parallex's
Two keys are involved, and they're kept apart. The feed key (Ed25519, key id f1) is on Parallex's server and signs its answers. The release key (Ed25519) is kept only in the maintainer's keychain and signs each release when it's built; the server never has it, so it can't sign a release. The public halves of both are built into the app. Before an update is installed:
- Parallex asks parallex.mandip.dev for the newest release. The answer must be signed with the feed key, over the version, the archive's address and SHA-256, the signature's address, the release page, a hash of the release notes and when it was signed. An unsigned answer isn't used, nor one older than the last one Parallex saw, older than 30 days, or dated more than a day ahead.
- It downloads the release's manifest, signed with the release key:
parallex-release-v1, the version, the archive's SHA-256 and its size. It has to match what the feed named, and the download stops at that size. - It downloads the archive and checks its signature with the release key, and its SHA-256 against both the manifest and the feed.
- It unpacks it and checks that the app inside is Parallex (
com.parallex.app), at that version, and newer than the one running. An older release is never installed. - Releases withdrawn after they went out are on a list signed with the release key; Parallex keeps the newest list it has seen and never installs a version on it, whatever a server offers.
So even someone who took over the server couldn't get Parallex to install anything but a release signed with the release key, or one older than the Parallex that's running. An organization can turn Parallex's own updates off (AppUpdatesAllowed) or hold each release back for a number of days (UpdateDeferralDays); see the managed preferences.
Checking a release by hand
Every release's files are on Releases, under https://parallex.mandip.dev/download/<version>/: the app archive Parallex-<version>.zip, its signature .zip.sig, its manifest .zip.manifest and the manifest's signature .zip.manifest.sig, a .sha256 checksum, the disk image, the installer package and, from 3.7.0, the SBOM. Put the archive and its companions in one folder, then either run verify-release.swift (it has the release key in it, needs only Swift, which comes with Xcode's command line tools, and sends nothing anywhere):
swift verify-release.swift Parallex-3.7.0.zip
# ✓ Parallex-3.7.0.zip is signed with Parallex's release key.
# ✓ Parallex-3.7.0.zip is the file Parallex's signed manifest names: …
or use OpenSSL 3 (Homebrew's openssl@3; the openssl macOS comes with can't check Ed25519) with the release key's public half:
cat > parallex-release.pem <<'KEY'
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEARr6I27BgnXlmopmP1pjqCrIYuKgHJMC5+1gowUaVVEc=
-----END PUBLIC KEY-----
KEY
base64 -D -i Parallex-3.7.0.zip.manifest.sig -o manifest.sig
openssl pkeyutl -verify -pubin -inkey parallex-release.pem -rawin \
-in Parallex-3.7.0.zip.manifest -sigfile manifest.sig
# Signature Verified Successfully
cat Parallex-3.7.0.zip.manifest
shasum -a 256 Parallex-3.7.0.zip && stat -f %z Parallex-3.7.0.zip
The SHA-256 and size have to be the ones the manifest names. The archive's own signature (.zip.sig) is checked the same way, over the archive.
The installer package
Since 3.3.1, each release also comes as Parallex-<version>.pkg, signed with Parallex's Developer ID Installer certificate and notarized. It installs Parallex.app in /Applications and, when nothing else is there, links the parallex command into /usr/local/bin. Its one script runs as root and touches nothing of any user's: no preferences, no login items, nothing in anyone's home. Check it with pkgutil --check-signature and spctl -a -vv -t install: the Team ID is UK74VYMUP7. Parallex in managed fleets covers deploying it.
What it's made of: the SBOM
From 3.7.0, each release comes with a software bill of materials, Parallex-<version>.cdx.json (CycloneDX 1.6), made from the app as it's built:
- Parallex's own programs and libraries, each with its file's SHA-256, and the SHA-256 of the release archive it's for.
- The one Swift package it's built with, swift-argument-parser (Apache 2.0 with the Swift runtime exception), linked into the
parallexcommand, at its exact version and commit. - The third-party material in it, from its notices: the BIP-39 word list Carry uses, and the list of macOS services sealed agent runs may reach, adapted from OpenAI Codex (Apache 2.0).
- The toolchain that built it: Swift, Xcode and the macOS SDK, by version.
Nothing else is bundled: the app links only macOS's own frameworks and the Swift runtime that comes with macOS. Its manifest, .cdx.json.manifest, is signed with the release key under its own label (parallex-sbom-v1), so it can't be taken for a release's; check it with swift verify-release.swift Parallex-<version>.cdx.json, or with OpenSSL as above. Releases before 3.7.0 have no SBOM. The website's server, a Cloudflare Worker, uses no npm packages when it runs.
Every address Parallex contacts
This is the whole list, for the app, the parallex command, the launcher, the link router, the web shell and the libraries copies load. It's kept in Parallex's code, and a test fails when a new address appears in the code without being on it. parallex network prints it on your Mac, and it's also published as network-inventory.json. What leaves your Mac says in full what each request holds and what the server keeps.
https://parallex.mandip.dev/api/v1/releases/latest (Parallex's server). Whether there's a new version of Parallex, as a signed answer (key f1).
- Sends: Parallex's version, macOS's version, the chip, whether it's the first check today, this week or this month, a number from 0 to 99 picked once, and a random Mac number picked once. When a release needs a license, a second request carries the license token, the versions, the chip and the 0 to 99 number, and never the Mac number.
- When: Every few hours while the app runs, when the Mac wakes, and when you choose Check for Updates.
- To turn it off: Settings › About › Check for updates automatically. Managed: AutomaticUpdateChecks false, or AppUpdatesAllowed false for no update checks or installs at all.
https://parallex.mandip.dev/download/…/Parallex-….zip (with .sig, .manifest and .manifest.sig) (Parallex's server). The update itself, its signature and its signed manifest, at the addresses the signed answer named.
- Sends: Nothing beyond a download request. The server adds one to that file's download count.
- When: When you install an update.
- To turn it off: Don't install updates. Managed: AppUpdatesAllowed false.
https://parallex.mandip.dev/advisories.json (and .sig) (Parallex's server). A signed list of notices about apps whose copies misbehave. The same file for everyone.
- Sends: Nothing beyond Parallex's name and version (User-Agent).
- When: A little after the app starts and twice a day, only while it checks for updates automatically; and when you choose Check for Updates or run parallex notices.
- To turn it off: Settings › About › Check for updates automatically turns the automatic fetch off too. Managed: AutomaticUpdateChecks false, or AppUpdatesAllowed false.
https://parallex.mandip.dev/api/v2/report (Parallex's server). The anonymous usage report.
- Sends: A random install number (renewed every 180 days), the versions and chip, counts of instances and features, well-known apps copied, what happened since the last report (counted), and Parallex's own crash frames. Never names, paths, contents or the license.
- When: Once a day, with the update check, only while sharing is on.
- To turn it off: Settings › About › Share anonymous usage, or parallex usage --no-share. Managed: TelemetrySharing false.
https://parallex.mandip.dev/api/v1/feedback (Parallex's server). A note you write.
- Sends: Your words, the versions and chip, and for an instance you pick, its kind, a well-known app's ID and version, and how it has been doing. An email address only if you type one. See What's Sent shows it all first.
- When: Only when you press Send.
- To turn it off: Don't send one.
https://parallex.mandip.dev/api/v1/licence/… (trial, activate, deactivate, refresh, challenge, founding, account, upgrade/link, prices, breaker) (Parallex's server). Your license: starting the trial, adding or taking a Mac off a license, the daily token refresh, an optional account, and the current prices.
- Sends: As the request needs: the license key (activating), the token (refreshing), a code made on this Mac from its hardware ID by a one-way function, a name for the Mac, Parallex's and macOS's versions, and on macOS 27 an App Attest proof. Prices and breaker send nothing but Parallex's name and version.
- When: When you start a trial, activate, sign in or free a Mac; about once a day to refresh the token; prices when a purchase screen shows them; breaker when a license can't be confirmed, and every 3 hours while licensing is paused.
- To turn it off: Needed to make and change instances with a license. Copies you've made keep opening without it. Managed: LicenceKey adds Macs to a license without anyone typing it.
https://parallex.mandip.dev/ (Parallex's server). Whether Parallex's server answers, for the checkup.
- Sends: Nothing beyond Parallex's name and version. Nothing is counted or kept.
- When: Only when you run Doctor or open Diagnostics.
- To turn it off: Don't run Doctor.
https://parallex.mandip.dev/no-updates.xml (Parallex's server). Where a copy's own updater is pointed, so the vendor's build can't replace the copy. The file never has an update.
- Sends: Whatever that app's updater sends, if it asks at all. Nothing is counted or kept.
- When: When a copy's own updater looks for updates.
- To turn it off: Use plain instances (they keep the app's own updater), not copies.
Apple's App Attest service (through macOS) (Apple). On macOS 27 and later, proof for the license server that this is the signed Parallex on a real Mac.
- Sends: What macOS sends to Apple for App Attest, under Apple's privacy policy.
- When: With license requests that start something (trial, activation, account sign-in).
- To turn it off: Comes with licensing on macOS 27.
The websites you add as web instances (presets: WhatsApp, Slack, Discord, Gmail, Outlook, Teams, Telegram, Messenger) (Your choice). The site itself, like any browser.
- Sends: What any browser sends to that site.
- When: While a web instance is open.
- To turn it off: Don't make web instances, or remove them.
The website you're adding (Your choice). The site's icon, for a new web instance.
- Sends: A plain request for its home page and icon.
- When: When you create a web instance.
- To turn it off: Choose your own icon instead.
The proxy a workspace or your organization sets (WorkspaceProxy) (Your choice). Their own traffic goes through the proxy you chose. Parallex sends nothing of its own there.
- Sends: That app's or agent's own traffic.
- When: While those copies or runs are open.
- To turn it off: Remove the workspace's proxy. Managed: WorkspaceProxy.
http://127.0.0.1:… (a port picked per run) (This Mac). Each agent run's own proxy, on this Mac: a sealed run can reach only the hosts it allows, and every request is in the run's proxy log. It passes on what the agent asks for, and nothing of Parallex's own.
- Sends: The agent's own traffic, to the hosts it asks for.
- When: While an agent run is going.
- To turn it off: Don't run agents through Parallex. Managed: AgentWorkspacesAllowed false.
Parallex sends nothing anywhere else. Pages it links to (its own site, GitHub's settings pages) open in your browser only when you click them.
Copies and web instances talk to the services they're copies of, as the apps would on their own; that traffic is theirs, not Parallex's. Notices (advisories.json) follow update checks: with automatic checks off, in Settings › About or with AutomaticUpdateChecks (or AppUpdatesAllowed) set to false, they're fetched only when someone chooses Check for Updates or runs parallex notices.
The audit log
From 3.7, Parallex keeps a record of what's done with it that matters for security, on your Mac, in ~/Library/Application Support/Parallex/audit/. It's never sent anywhere. Settings › Audit shows it, and parallex audit show, verify and export --since work with it from Terminal.
- Chained. Each entry is a line of JSON that holds the SHA-256 of the entry before it and its own, so
parallex audit verifyfinds an entry that was changed, removed or moved, and a log that was cut short. An export can be checked the same way on another Mac (parallex audit verify --file). It's tamper-evident, not tamper-proof: someone who can write your files can write a whole new log. - In the unified log too. Each entry also goes to macOS's unified log at notice level (subsystem
com.parallex.app, categoryaudit), with its kind, result, plain facts and the start of its hash in public, and names kept private. That's the copy your MDM or EDR can collect:log show --last 1d --predicate 'subsystem == "com.parallex.app" AND category == "audit"'. - Never in it: secrets, tokens, license keys, what's inside an instance, or paths beyond a file's name. Every value goes through the same redaction as a diagnostic bundle.
- Size. The newest 2 MB are kept, with four older files beside them; when the oldest goes, the log says so, and its chain goes on.
- On by default. You can turn it off in Settings › Audit; turning it off and on are entries of their own. An organization can keep it on, or turn it off, with the managed preference
AuditLog.
| Entry | What it means |
|---|---|
audit.started | The audit log started |
audit.on | The audit log was turned on |
audit.off | The audit log was turned off |
audit.rotated | The audit log went on in a new file |
audit.pruned | The oldest audit file was removed |
instance.create | Made an instance |
instance.remove | Removed an instance |
instance.rebuild | Rebuilt an instance |
instance.import | Imported an instance |
instance.export | Exported an instance |
instance.isolation | Changed how an instance is kept apart |
repair | Repaired what a check found |
backup.create | Backed up Parallex's settings |
backup.restore | Restored Parallex's settings from a backup |
snapshot.restore | Restored a snapshot |
carry.send | Moved copies to another Mac |
carry.receive | Received copies from another Mac |
carry.back | Brought a moved copy back |
fleet.run | Started a fleet |
fleet.merge | Merged a fleet's lane |
agent.run | Started an agent run |
policy.seen | Read your organization's settings |
licence.trial | Started the free trial |
licence.activate | Added this Mac to a license |
licence.release | Took this Mac off its license |
licence.free | Freed another Mac on the license |
update.install | Installed a Parallex update |
Reporting a security problem
Write to hello@mandip.dev with "Security" in the subject. The security policy says what's covered, how to test in good faith, and what happens next: a reply within 3 working days, serious problems fixed first, and up to 90 days before details are published. The address is also in security.txt. Fixes ship as Parallex updates, and the release notes say what was fixed once it's safe to.
What Parallex never does
- Change the apps you copy: the original in /Applications is never touched.
- Send your instances' names, files, paths or anything inside them anywhere.
- Install an update that isn't signed with the release key, or an older version.
- Ask for Full Disk Access, Accessibility, Screen Recording, the camera or the microphone for itself.
- Install a kernel or system extension, or a helper that runs as root.
- Carry another company's analytics, advertising or crash-reporting code: the SBOM lists everything that's in it.
- Send the audit log or a diagnostics file anywhere. Both stay on your Mac unless you send them yourself.
Questions
Can I collect the audit log from many Macs?
Yes: each entry is in macOS's unified log (subsystem com.parallex.app, category audit), which device management and EDR tools can collect. The file itself stays on each Mac; parallex audit export writes a copy that can be checked anywhere.
Can someone on my Mac change the audit log?
Someone who can write your files can change it, but not without parallex audit verify finding it, unless they rewrite every entry from that point on. The copy in the unified log can't be changed without an administrator's rights.
Why does my security tool flag Parallex's copies?
Copies are re-signed on each Mac and load Parallex's own library, which some EDR rules look for. Parallex in managed fleets explains it, and what to allow by Team ID UK74VYMUP7.
Or in Terminal
curl -fsSL https://parallex.mandip.dev/install | shOr with Homebrew
brew install --cask mandipadk/parallex/parallex