Every claim on this page names the part of the code that implements it. The source is private; a college, privacy officer or reviewer can be given access to check any line here against it. A control-by-control check against the OWASP Mobile Application Security Verification Standard (MASVS) is kept alongside the code.
The short version
- The logbook lives on the surgeon’s phone, encrypted. Opus is not a cloud service holding a copy of everyone’s cases.
- The server never holds readable patient data. A case shared with a colleague is encrypted on the sender’s phone to that colleague’s specific devices; the server stores ciphertext it cannot read.
- Nothing is uploaded unless the surgeon does something that uploads it: sharing a case, saving a backup, exporting a file.
- Patient identifiers are never stored in a form the server can search. Even the local search index holds only a keyed hash.
- Opus is a record-keeping tool. It gives no treatment advice, and the wording was stripped of anything that read as advice.
1. What is stored, and where
| Data | Where it lives | Protection |
|---|---|---|
| Cases, diagnoses, procedures, outcomes | The phone only | XChaCha20-Poly1305, per-user key in the iOS Keychain |
| Clinical photos | The phone only | AES-256-GCM, one key per photo, each wrapped under the user’s key |
| Patient name, date of birth, NHI or URN | The phone only | Inside the encrypted case record; the local index holds an HMAC-SHA256 hash |
| Training rotations, MALT entry status, assessment targets | The phone only | Same encryption as cases |
| Account: email, name, hospitals, career stage, registration number | Server (United States) | TLS in transit; ordinary database columns |
| Colleague list (names, and the email, phone or registration used to match) | Server | Ordinary database columns |
| Shared cases, including patient identity and photos | Server | End-to-end encrypted; the server holds ciphertext and per-device sealed keys |
| Blinded assessments | Server | End-to-end encrypted, with a commit-reveal protocol (section 5) |
| Supervisor attestation (a procedure id plus the confirmed role and level of supervision) | Server, linked to the shared case | Plaintext: no patient detail, readable by the owner on a device that never decrypted the share |
| Backups the surgeon saves | Wherever the surgeon put them | Sealed to a Recovery Key only they hold |
The asymmetry is the point. Clinical content is on the device; the server’s job is identity, matching colleagues and relaying ciphertext.
On the device
Records are sealed with XChaCha20-Poly1305 under a 32-byte key generated on first run and stored in the iOS Keychain as this device only, when unlocked: readable only while the phone is unlocked, never synced to iCloud, not restorable onto another device (client/lib/encryption.ts). Every ciphertext carries an authentication tag, so an altered record fails loudly rather than being read as plaintext. There is no unauthenticated fallback path.
Photos use a per-image key and hardware AES-GCM through Apple’s CryptoKit (modules/opus-media-crypto). Decrypted images go only to the OS cache, which is cleared on launch, on lock, on sign-out and whenever the app goes to the background. Files are marked NSFileProtectionComplete, and the folders holding Opus data are excluded from iCloud backup at every launch.
If the Keychain entry is ever missing, Opus does not quietly generate a new key and show an empty logbook. A key check value stored with the data means a missing or mismatched key is reported on a recovery screen, never replaced (client/lib/guardedSecret.ts).
Patient identifiers
The local index behind search and duplicate detection stores HMAC-SHA256(identifier) under a per-user key held in the Keychain (client/lib/patientIdentifierHmac.ts). The same NHI gives the same hash on one phone and a different hash on another, so a hash is useless off the device. Full identity lives inside the encrypted record.
2. Device access
The app can be locked with a six-digit PIN or Face ID. The PIN is stored as scrypt with a 16-byte random salt, computed natively, with a lockout ladder that survives force-quit (five failures lock for 30 seconds, rising to an hour). When the app goes to the background, the in-memory caches of names and identifiers are dropped and the app-switcher snapshot shows the Opus mark instead of the last screen.
The lock is a screen lock over storage that is already encrypted: it decides whether the app shows anything, while the data key is protected by the Keychain. Binding the key to Face ID or the PIN as well is the main open item in the MASVS self-check (section 9).
3. Sharing a case with a colleague
An operation has a team, and a trainee’s logbook entry is evidence a supervisor should be able to confirm. The design question was how to do that without the server becoming a repository of patient data.
- Per-device public keys. Each device generates an X25519 key pair on first run; the private half stays in the Keychain (
client/lib/e2ee.ts). - Per-save case key. Each save generates a fresh symmetric key, seals the case with XChaCha20-Poly1305, then seals that key separately to each recipient device with X25519 and HKDF-SHA256. The server holds no key that opens anything.
- Trust on first use, with a safety number. A colleague’s device key is pinned the first time it is seen (
client/lib/keyPinningStore.ts). If it changes, the share fails closed and the user is told whose key changed. Two colleagues can compare a 60-digit safety number before accepting a new device. - Verified email before sharing. Matching by email, phone or registration number is not enough on its own: the receiving account must have a verified email, and a phone or registration number matches only when exactly one eligible account claims it (
server/accountEligibility.ts). This closes the path where an impostor registers a colleague’s address and receives encrypted cases. - Photos travel the same way. Per-image keys ride inside the encrypted case; image ciphertext is uploaded as it is.
- Contact discovery does not disclose the address book. Finding which colleagues use Opus runs a private set intersection (an oblivious pseudorandom function over ristretto255, RFC 9497) under a key the server discards after each request. The server learns nothing about contacts that do not match (
client/lib/psiDiscovery.ts,server/psi.ts). - Recipients are not stuck with a case. Removing a received case deletes it from the device, tombstones the server row and stops later edits reaching them.
A shared case is not de-identified. A colleague on the operating team sees the patient identity the surgeon recorded, because confirming “was I on this case” needs the case. The surgeon chooses the recipients, and the Privacy Policy says so.
4. What the surgeon controls
- What is shared, and with whom. Tagging a colleague who is not on Opus, or whose email is not verified, shares nothing.
- Whether identifiers go into an export or report. Training-body reports default to identifiers off; registry and audit reports default to on. Any export containing patient data asks for confirmation, writes a temporary file, hands it to the OS share sheet and deletes it afterwards.
- Crash reports and usage data. One device-wide switch in Settings. Off means nothing is sent, including anything already queued.
- Deletion. Deleting the account removes the account, profile photo, colleague list, device keys and the encrypted copies of shared cases from the server, and wipes the logbook, photos, inbox and every key from the phone. Copies already delivered to a colleague’s device stay there; that is a property of end-to-end encryption, and the Privacy Policy states it.
5. Blinded assessments
When a trainee and a supervisor both rate the same operation, each rating is worth little if either can see the other’s first. Opus uses commit-reveal: each party uploads only a SHA-256 commitment of its assessment; once both exist, each uploads its encrypted assessment with the nonce inside the encrypted blob; each verifies the other’s commitment after decrypting. The server cannot read an assessment, and neither party can see the other’s before committing (client/lib/assessmentCommitment.ts).
An entrustment assessment is created only where it means something: the trainee was the primary surgeon on that step or procedure. A trainee who assisted gets an exposure record, not a rating.
6. Backups
A surgeon can write an encrypted archive wherever they choose. It is sealed to a Recovery Key: 16 random bytes shown once as 28 characters, of which the phone keeps only the public half. The phone can make backups it cannot open, and Opus never receives the file or the key. Records are authenticated frames and photo ciphertext is copied without being decrypted; tampering, truncation, a dropped frame and a wrong key all fail closed. A lost Recovery Key cannot be recovered; that is the trade for Opus not holding it.
7. The server
Express and PostgreSQL on Railway in the United States, with settings kept as code. Passwords are hashed with Argon2id. Bearer tokens carry a version, so a password change invalidates every session, with a 90-day absolute cap and a server-side logout. Sign in with Apple identity comes only from the verified token, bound to a nonce. Every mutation checks ownership with a composite condition, so an authenticated user cannot reach another user’s row by guessing an id. Per-user rate limits apply on every authenticated route. Helmet headers, HSTS and a hash-only Content Security Policy with violation reporting. An append-only log of sign-ins, failures, verification, device changes and deletions holds an IP prefix and no email addresses, pruned after 180 days. Point-in-time recovery on the database, scheduled backups, an encrypted dump with a monthly restore drill, and schema changes through a migration ledger.
8. What Opus deliberately does not do
- It does not advise. No margin guidance, no suggested antibiotic, no risk score with a traffic light. The one piece of image processing, making a photographed X-ray legible, is labelled “not for diagnostic use”.
- It does not sell or share data. No third-party data recipient, no advertising identifier, no tracking as Apple defines it.
- It does not sync automatically. A case leaves the phone when the surgeon shares, exports or backs it up.
9. Known gaps, stated
- No external penetration test yet. The 2026 audit was internal, with each finding verified against the code or the live system.
- No staging environment. Migrations are proven against a schema-parity check and a scratch database and preceded by a backup, but production is where they are first applied.
- The app lock is a screen lock. Face ID and the PIN decide what the app shows; the data key is protected by the Keychain while the phone is unlocked. Binding the key to Face ID or the PIN is planned.
10. Obligations
- New Zealand: Privacy Act 2020 and the Health Information Privacy Code 2020. Health information stays on the surgeon’s device; the surgeon decides what to share, within their employer’s policy. A page on patients and consent follows legal review.
- Australia: the Privacy Act 1988 and the Australian Privacy Principles.
- Overseas storage is disclosed: account and colleague data and the encrypted copies of shared cases are on servers in the United States.
- Regulatory classification: record-keeping software, which the Therapeutic Goods Administration excludes from medical-device regulation. Opus offers no clinical decision support, which is what the reasoning turns on.
- Terminology licensing: SNOMED CT is used under SNOMED International’s Affiliate licence terms; the required notice and release versions are shown in the app and on Terminologies and references.
- Sharing is paused for surgeons practising in Germany and Switzerland pending a review of consent requirements there. The personal logbook works everywhere.
A September 2026 audit found two serious problems, both fixed the day they were found, with no evidence of exploitation. The whole ledger of findings and their status is in the audit summary, published once legal review of it is complete.