Security
Claims you can check yourself.
PriorAuth Guard evaluates prior authorization packets on the device. That decides the threat model: there is no server holding your packets, so what has to be defended is a stolen laptop and a tampered local record, not a cloud breach. Everything below is stated so a security reviewer can verify it in a browser rather than take our word for it.
- AES-256 at rest
- Key derived from a passphrase
- Ed25519-signed updates
- Enforcing CSP
Last verified September 3, 2026
Against the deployed build at priorauthguard.com/app/
Measured, not asserted
Two claims this page proves as you read it.
The rest of this page is prose, and prose is easy to write. These two boxes run in your browser and print what they actually find. If either one disagrees with the text below it, believe the box.
Requests made by this page
Measuring.
The claim is zero requests to any other origin: no fonts, no analytics, no tag managers, no CDN. The list is this page's own requests, with the origin trimmed off. One of them is the check on the right, reading the policy header.
Content-Security-Policy header
Checking.
A report-only policy logs violations and permits them. The claim is that this site is served enforcing, so the browser blocks anything the policy forbids instead of merely noting it.
How the data is protected
Four cryptographic pillars, all offline.
Each one addresses a specific way a device holding patient documentation can be turned against the team using it.
Pillar 01
Encryption at rest, with the key derived from a passphrase
Every local store, packets, documents, audit log, receipts and rule packs, is opened through an AES-256 cipher. Where that key lives is the part that matters. On a native build the operating system keychain holds it. In a browser there is no keychain, and the storage plugin falls back to the same store the encrypted data sits in, which would put the key and the ciphertext side by side under adjacent names.
So the key is not kept. A random 256-bit master key encrypts the data, and that master key is itself sealed under AES-256-GCM with a key derived from the operator's passphrase. Only three things are written to the device: a random salt, the derivation cost parameters, and the sealed envelope. None of them decrypt anything without a passphrase that exists solely in someone's head. Changing the passphrase re-seals the envelope and rewrites no packets.
Because no key exists until the passphrase is typed, the app stops at an unlock screen before it opens a single store. There is also deliberately no fast verifier written to disk: the AES-GCM authentication tag is the verifier, so an attacker with the device gets no cheap way to test guesses offline.
Pillar 02
Tamper-evident audit chain
Every evaluation appends an entry to a hash chain, where each entry's hash covers a canonical form of the record plus the hash before it, keyed by a device secret. Editing, deleting or reordering any entry breaks the chain from that point forward.
The integrity check recomputes the whole chain and names the first entry that no longer matches, so a log cannot be quietly adjusted after the fact to hide a missing document or a billing omission. Exports carry the verified chain head so a third party can re-check them outside the app.
Pillar 03
Signed rule updates
Payer rules change, so the app can fetch updated rule packs. That fetch is the only code in the app that touches the network, and it is one-way: a GET, with nothing uploaded.
Rule packs arrive as Ed25519-signed envelopes and are rejected unless the signature verifies against the public key built into the app, before the schema validator ever sees the contents. A hostile network can withhold an update or serve an old one. It cannot inject altered clinical constraints.
Pillar 04
Signed completion receipts
When a packet review completes, the receipt is Ed25519-signed with a per-device key rather than merely hashed, which makes it verifiable by someone who did not generate it.
Alongside this, the app locks itself after five minutes idle or when backgrounded, clearing the in-memory pointer to the open packet. Integrity verification for the chain and the receipts can be run on demand from Settings, under Security.
Parameters
The primitives, with their actual settings.
Nothing here is a custom construction. These are standard primitives at published parameters, listed so a review does not have to ask.
| Purpose |
Primitive |
Parameters |
| Data at rest |
AES-256 |
Every local store, with a one-time migration of any store written before encryption was in place |
| Sealing the master key |
AES-256-GCM |
Random nonce per seal; the authentication tag doubles as the passphrase verifier, so no separate verifier is stored |
| Passphrase derivation |
PBKDF2-HMAC-SHA256 |
600,000 iterations, 256-bit output, random per-device salt. Runs through the browser's native WebCrypto, about 450 ms on a current laptop |
| Audit chain |
HMAC-SHA256 |
Append-only, keyed by a device secret, over a canonical serialization of each record |
| Receipts and rule packs |
Ed25519 |
Receipts signed per device; rule packs verified against a public key embedded in the app, with the private key never on a device |
| Hashing input |
Canonical JSON |
Deterministic key order and encoding before any hash or signature, so the same record always produces the same digest |
On the derivation cost: Argon2id is the stronger choice on paper and was built first, then measured and removed. The library available here has no browser-native Argon2, so in a browser it runs as compiled Dart, where a 2 MiB memory parameter took 24,957 ms in testing, a cliff rather than a curve. At the recommended parameter the app would have appeared frozen for half a minute on every launch. PBKDF2 goes through the browser's own WebCrypto implementation instead, which is how 600,000 real iterations cost under half a second. The stored parameters include which algorithm produced them, so a future build can move without stranding an existing device.
What leaves the device
One outbound request, and it carries nothing of yours.
- No patient data, document content, extracted fields, evaluation results or audit logs are transmitted, at any point, for any purpose
- One outbound request in the app: a one-way GET for signed rule updates
- No analytics or crash reporting: no Firebase, Mixpanel, Amplitude, Crashlytics or Sentry
- No accounts, no login, no registration, so no credential to breach
- Text extraction runs on the device; no document is sent to a cloud OCR service
- Typefaces are bundled with the app, not fetched, and the renderer is served from this origin rather than a CDN
Two details a careful reviewer will find, stated here rather than left to discovery. The rendering engine asks for a fallback font whenever text contains a character no bundled typeface covers, which real patient names can trigger; that request is pointed at this origin, so the character renders as a blank box instead of becoming a request to Google. And the access request form on the marketing pages emails an address you type into it. That form is not part of the app, it never sees packet data, and its policy forbids a native form submit precisely so an address cannot end up in a URL.
Boundaries
What this does not claim.
A security page that lists only strengths is not much use in a review. These are the limits, in the same detail as the claims.
-
The passphrase is optional, and until it is set the key is stored on the device.
Encryption at rest is always on, but deriving the key from a passphrase is something an operator turns on in Settings, under Security. Before that, the key sits in device storage beside the data it protects, which on a browser install means anyone holding the laptop holds both halves. Turning it on is the single highest-value thing a team can do on day one.
-
It is not protection against a running, unlocked app.
Between unlock and closing the tab, the master key is in memory, as it is in every full-disk encryption product. Someone at an unlocked workstation is inside the boundary; that is what the idle lock reduces, not eliminates.
-
A short passphrase is still a short passphrase.
600,000 iterations cost the operator about 450 ms and cost an attacker with a GPU far less per guess. Six digits falls in minutes and eight digits in hours, while eight mixed characters does not. The minimum is eight characters, digits are not required, and the app says plainly that length is what protects the data.
-
A forgotten passphrase is unrecoverable.
There is no reset, no recovery key and no back door, because any of those would be a second way in. The unlock screen says so and offers an explicit erase-and-start-over instead of leaving the app unusable. Local data is lost in that case, which is the honest cost of the guarantee above it.
-
If a device offers no secure storage at all, storage degrades rather than crashes.
In that situation the data is written unencrypted and the app reports that state instead of implying protection it does not have.
-
This is a completeness check, not a coverage determination.
The app compares a packet against payer documentation rules. It does not decide medical necessity, guarantee an authorization, or constitute a compliance certification for any framework. It is one control in your environment, not a substitute for your own risk assessment.
Verification
How to check every claim above.
None of this requires our cooperation, a demo environment or an NDA. A reviewer with a browser can confirm the whole page in about ten minutes.
-
Confirm nothing goes to a third party.
Open /app/ with the Network tab recording, then work through a packet. Sort by domain. Every request should be to this origin.
-
Read the policy header yourself.
Request any page here and read the response headers. Content-Security-Policy should be present with default-src 'self', and there should be no report-only header alongside it.
-
Look for the key.
With a passphrase set, inspect local storage for the app's origin. The vault entries are the salt, the cost parameters and the sealed envelope, in keys ending kv_salt, kv_kdf and kv_sealed_master (the storage plugin prefixes its own name). There is no key material and no password hash. Before a passphrase is set you will also find the device key, which is the state the previous section describes.
-
Check a rule pack signature.
Fetch /rules/payer-rules.signed.json. It is public on purpose. Verify its Ed25519 signature, then alter one byte and watch the app reject it.
-
Break the audit chain on purpose.
Run a few evaluations, edit or remove a stored audit entry, then use Settings, Security, Verify Data Integrity. It should name the first entry that no longer matches.
-
Pull the network.
Go offline and reload /app/. The app is installable and precaches its own shell, so a review can confirm it works with no connection rather than trusting the word offline.
If any step comes out differently than described, that is a defect and we want to hear about it. Mail James@execlayer.io with what you ran and what you saw.
Bring it to your security review
Run it against a real packet path.
Open the app in a browser and check the claims on this page as you go, or schedule a pilot conversation to work through rule ownership, signed updates and where this fits in your existing controls.