Security model
Authentication
Google Firebase Authentication, shared across the Swyftscale product family so one account works in MedJot and ACTrack. Entitlements are per-application and do not carry across.
- Email and password. Password policy, hashing and credential storage are Firebase's, not ours — we never see or store a password.
- Self-service password reset by emailed link. The response is identical whether or not the address is registered, so the endpoint cannot be used to enumerate accounts. It is rate limited per client IP.
- Federated Google sign-in has been removed and will not be reinstated. It could not be made to work in an installed iOS web app.
- Email verification is required for certain account functions.
Sessions are held by the Firebase SDK in browser storage and refreshed with a refresh token. This is the reason the TLS inspection exemption matters: those tokens are the credential.
Authorisation
Every privileged operation is checked server-side, regardless of what the client believes:
- Entitlement is re-checked server-side on every AI request and every report-start check. A client cannot grant itself Premium.
- Attribution — role, sector, entitlement, organisation, region — is derived server-side from the account record and never read from the request body.
- Administrative functions are server-mediated and gated on a server-verified administrative flag. The browser cannot write privileged fields directly.
- Organisation membership and manager rights are verified server-side on every organisation endpoint.
- Every administrative mutation is written to an append-only audit log. Sensitive values are recorded as "changed" rather than copied into the log.
Transport
- HTTPS only, TCP 443. HTTP/3 advertised over UDP 443.
- Certificates from Let's Encrypt, rotated roughly every 60 days, chaining to ISRG Root X1.
- HSTS with a one-year max-age,
includeSubDomainsand preload. .appis an HSTS-preloaded TLD, so browsers refuse plaintext and refuse to bypass certificate errors. This is a security property, and it is also why interception produces a hard block.
Browser security headers
| Header | Position |
|---|---|
Strict-Transport-Security | 1 year, includeSubDomains, preload |
Content-Security-Policy | Deployed report-only while being validated against real traffic, with violations collected centrally. Directives restrict script, connect, frame, object and base-uri to an explicit allow-list. |
frame-ancestors | Restricted to our own origin and the Swyftscale portal |
X-Content-Type-Options | nosniff |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | Camera permitted (QR transfer scanning) and payment; geolocation and microphone denied |
X-Powered-By | Removed |
We describe the CSP accurately: it is report-only pending a clean observation window, then moves to enforcement. Shipping an enforcing CSP blind on a clinical tool that uses the camera, a payment element and a service worker would risk breaking it mid-report for no security gain over a staged rollout.
Application security
- No clinical data server-side, which removes the entire class of risk associated with a central patient data store. There is no clinical database to breach, exfiltrate or subpoena.
- PII redaction on the AI path, client-side and again server-side — see below.
- Closed-catalogue analytics with no free-text field anywhere in the schema, revalidated server-side.
- Rate limiting on sensitive unauthenticated endpoints, including password reset and device-transfer code generation. The proxy's forwarded client address is honoured so limits key on the real client.
- Payment card data never reaches us. Stripe-hosted checkout and payment elements; we receive subscription status by signed webhook.
- Webhook signature verification on all inbound payment events.
- One-time, short-lived tokens for device transfer (10 minutes, in memory) and organisation invitations (7 days, single use).
- Ticket access links carry their token in the URL fragment and are sent to the API in a header — never as a query string or path segment, so a working credential is never written to a proxy or server access log.
PII redaction on the AI path
The only route by which clinical content leaves a device is a clinician pressing the button on an AI feature. That path is defended as follows.
Two passes, deliberately duplicated. Redaction runs in the browser before the request is made, and again on the server before the prompt is assembled. The first means identifiable text never crosses the network; the second exists because a server must not trust a client's assurance that it already ran. The two copies of the ruleset are held identical by an automated test that runs a shared corpus through both and fails on any difference.
Structured identifiers are removed unconditionally — on every AI route, in every mode: emails, UK phone numbers, NHS numbers, UK postcodes, and dates of birth in both numeric and written form. Names introduced by a title (Mrs Margaret Ellis), by "named"/"called", or by a relationship word from an explicit list (next of kin son Peter) are reduced to initials.
The whole request object is swept recursively, rather than a maintained list of fields being cleaned. This is an architectural choice: a per-field approach fails silently the first time someone adds a field and forgets the list.
Name-collapsing strength varies by route, identifier stripping does not. Narrative routes additionally reduce any capitalised word pair to initials; diagnostic routes do not, because collapsing clinical terminology measurably degrades output and drives clinicians toward whichever feature protects least. Observation blocks keep their clinical wording and still have identifiers stripped.
Model output is redacted too on the safeguarding route, and the prompts on that route forbid producing or requesting a full name.
Inputs are bounded before they reach a prompt: free-text and conversation-history lengths are capped, category fields accept only values from a closed list and drop anything else, and every AI route is rate limited per account.
It is tested, including adversarially. Coverage asserts identifier removal in both modes, zero false positives on a standard set of vital signs, the specific parsing traps the rules were written for, and linear-time behaviour on adversarial input so redaction cannot itself be used to hang the app.
We describe this accurately: it is defence in depth, not a guarantee. Pattern matching cannot reliably catch a name typed into free text with no surrounding cue. The primary control is that patient-identifiable information should not be entered at all, and the product says so to clinicians. The full rule-by-rule account is in What gets sent, and what doesn't.
Anti-abuse
Registration and sign-in are assessed for indicators of fraudulent or disposable accounts, using hashed device and IP signals only — no fingerprinting, no third-party trackers. See Data flows.
No account is suspended by automated decision alone. Flagged accounts are queued for human review, and suspended users retain a session specifically so they can read the recorded reason and appeal to a person.
Client-side storage
Clinical data is in localStorage and IndexedDB on the device, protected by the browser's origin isolation and by the device's own security — screen lock, disk encryption, MDM controls.
This is the correct threat model to apply: on a lost or compromised device, in-progress reports are exposed to whoever holds the device, exactly as with any locally-stored document. It is also why the device policy guidance matters, and why clinicians are instructed to keep patient-identifiable data out of MedJot in the first place.
Clinical working data is cleared when a different user signs in on the same browser.
Availability and updates
- UK-hosted infrastructure with automated deployment.
- The app is offline-capable, so a server outage does not stop a clinician documenting a patient — which is an unusual and genuine resilience property for a clinical tool.
- Updates are delivered as web assets. Users are prompted rather than interrupted, except where a fix must reach everyone at once; in-progress work is preserved either way.
Reporting a vulnerability
Email hello@swyftscale.com with the detail. We will acknowledge, investigate and keep you informed. Please give us a reasonable opportunity to fix an issue before disclosing it publicly.