What this page is
What is built into the software, and what depends on how you run it. No certification is claimed. How the parts are deployed is on Running it; the data's separation is on Data and tenancy.
Identity and sessions
- No passwords. Sign-in is a six-digit code sent to a registered address: valid five minutes, three attempts, a 60-second cooldown before another, no more than three codes for one account from one address in 15 minutes. Codes are stored only as an Argon2id hash. Repeated wrong codes pause sign-in in that browser window for fifteen minutes and are recorded; an account is never locked by someone else's guessing (Signing in).
- No account enumeration. A stranger's request for a code is answered exactly as a request for a real account is (a challenge, the same words, the same cooldown); only the audit ledger records why no code was sent.
- The browser holds a sealed cookie, never a token it can read. The web application keeps the access and refresh tokens inside an encrypted,
httpOnly,sameSite=laxcookie (securein production), split into chunks when it is long, and calls the API on the person's behalf. Script on the page cannot read it. - Session lifetimes. 15 minutes idle and 12 hours absolute; an access token lasts 15 minutes and a refresh token 7 days. A person can list and end their own sessions (Manage your settings and sessions).
- Refresh with reuse detection. A refresh rotates the token; presenting an already-used one ends the session.
- A person holds one role on a study, the same in UAT and in Live, and the roles on different studies are never merged: what they may do is decided by their role on that study, in the workspace they are in.
- No single sign-on and no API keys. There is one way in, the code (API and integrations).
Authorization
- Fails closed. A screen that is not registered is unreachable, not merely ungated.
- Checked on the server. Hiding a button is presentation; the refusal is made by the API, so a script cannot do what a screen refuses (API reference).
- Separation of duties is held in the role editor and again at the moment of the act: the person who raised a query cannot answer it, three different people sign a release, the lock needs two, a person cannot sign their own delegation (Who can and cannot).
- No blanket administrator override. The clinical and signing operations use a strict check with no administrator bypass; the role editor's hard guardrails are refused for an administrator as for anyone.
- Not found, never forbidden. A study the caller is not a member of answers 404, so the system does not confirm that data it will not show exists.
- Refusals are audited as access decisions.
Tenant isolation and the workspace wall
Organizations share one deployment and are separated in the database: 66 of the 72 tables carry an organization id and a row-level security policy keyed to the organization on the connection, which the application sets from the authenticated session on every checkout and clears on release. With no tenant set, a query matches zero rows. A second wall separates a study's UAT data from its Live data on the clinical tables. The application connects as a least-privilege role that does not own the tables, cannot change a ledger, and is checked at boot: in production a deployment that got this wrong refuses to start (Data and tenancy, The database).
Application hardening
- Headers. The API sends Helmet's headers (
X-Content-Type-Options,Referrer-Policy: no-referrer, a same-site cross-origin resource policy) and strict transport security in production for two years with subdomains and preload. The web application addsX-Frame-Options: DENY,nosniff, a strict referrer policy, a permissions policy that turns off camera, microphone and location, strict transport security, and a content-security-policy with a nonce on every page, set for each request. - CORS is locked to the configured origins.
- Rate limits, counted by the viewer's address as the web application vouches for it: 600 requests a minute for ordinary routes and 30 for sign-in routes by default, and a production check refuses an authentication limit over 100.
- Validation. Every request body, query and path parameter is checked against a schema before it reaches business logic, and a request body over 2 MB is refused.
- Errors use one envelope; in production the message of an unexpected error is replaced by a generic one.
- Logs are structured and correlated by request id, and redact the authorization and cookie headers and any field named password, token or secret.
- Shutdown is graceful: the probes flip before connections drain.
- Configuration is validated at boot, and production and staging refuse unsafe combinations (a development code shortcut, a single database role, a long idle timeout, a placeholder secret, no anchoring of the audit ledger without an explicit waiver) (Running it).
Against the OWASP Top 10
| Risk | Position |
|---|---|
| Broken access control | Fail-closed permissions, site confinement, cross-tenant row-level security, the workspace wall, audited refusals |
| Cryptographic failures | HTTPS with HSTS; codes hashed with Argon2id; SHA-256 fingerprints on signatures, documents and the audit chain; files encrypted at rest in S3 |
| Injection | Queries bind their values as parameters; every request is schema-validated first |
| Insecure design | Separation of duties, the two-person lock, append-only ledgers, immutable released versions |
| Security misconfiguration | Configuration validated at boot; production refuses a privileged runtime database role |
| Vulnerable components | The client's and the control plane's release gates fail on a high or critical advisory in production dependencies; the server repository's gate (npm run verify) does not run a dependency audit, so checking its dependencies is an operational task |
| Identification and authentication failures | Passwordless codes, limited attempts, rotation with reuse detection, short sessions |
| Software and data integrity failures | The hash-chained audit trail with anchors, signature and record binding, sealed configuration versions, file checksums |
| Logging and monitoring failures | A comprehensive audit trail and operator alerts; log shipping, SIEM integration and metrics are yours |
| Server-side request forgery | The server makes outbound calls only to destinations that are configured (the control plane, storage, the mail relay, the AI provider), never to an address a user supplies |
What depends on your deployment
These are real controls that the application cannot provide:
| Control | Owner |
|---|---|
| Database encryption at rest | Your database provisioning |
| Backup, restore testing, disaster recovery | Your infrastructure (the tooling and the stale-backup alert are provided) |
| Retention periods and archival | Your policy and infrastructure |
| TLS certificates, key management and rotation | Your hosting and key service |
| Network segmentation, WAF, DDoS protection | Your network |
Intrusion detection, SIEM, alert routing (ALERT_EMAILS) | Your security operations |
| Penetration testing and code security review | Your programme |
| Physical security | Your hosting provider |
| Data residency | Your region choices |
| Patching of the host and the database | Your operations |
| Secrets management | Your secret store |
Operational maturity, stated plainly
Audit events, mail, exports and the dashboards' summary tables are carried by database tables and the worker, so they survive a restart. What does not: notifications and the in-process event bus. The API's rate-limit counters are in the process's memory, so they are per process; there is one API process and one worker, a deploy is in place with a brief outage, and the generic job queue is an unused stub. No metrics are emitted and there is no distributed tracing. The probes, alerts and logs are what exist (Running it). For a pilot this is workable; for a production trial at scale, plan external observability and redundancy.
No certification is claimed
Trialion is not certified against ISO 27001 or SOC 2. The controls above map onto many of their requirements (access control, cryptography, logging, change management, segregation of duties), but a certification covers an organization's management system, not a piece of software. If you need one, it is the hosting and operating organization that would be certified.
Compliance Q&A
In part. The worker's one-minute sweep raises an alert by e-mail to the configured recipients when the audit ledger no longer verifies, events are not being recorded, mail is not leaving, a backup is stale or exports stall, and clears it when the condition recovers; refusals and sign-in failures are in the audit trail.
Limits: an alert with no recipient only logs, so setting ALERT_EMAILS is part of going live. There is no intrusion detection or SIEM feed, successful reads are not recorded, and the incident procedure (who assesses, who reports, how fast) is the organization's.
Evidence: server/src/infrastructure/alerting (audit.chain_integrity, audit.anchor, audit.partitions, audit.outbox, mail.delivery, mail.relay_idle, summary.lag, summary.drift, backup.stale, exports.stalled)alert e-mail to ALERT_EMAILS
In part. Nightly logical dumps to versioned storage, a weekly restore verification that also checks the audit chain in the restored copy, a record of each run, and an alert when a backup is more than 26 hours old or a verified restore more than 8 days old.
Limits: the application cannot schedule these itself: the jobs must be installed, the storage and the database's own backups configured, and the recovery rehearsed by the operator.
Evidence: scripts/backup (pg-backup, pg-verify-restore)backup_runsworker alert backup.staledocs/operations/backup-and-recovery.md
Related
Data privacy · Audit trail and evidence · Running it · Technical overview