Security
What we can show, line by line. Each line says whether it is in place, planned, or not held. Every "In place" line names its evidence: a test that runs before every release, a setting in our infrastructure template, a code path, or a check run against the live service after each deploy. If we cannot point to evidence, the line says so.
In place with evidence Planned with the plan item that delivers it Not held we do not have this
Governance
-
Every release passes an automated gate (the full test suite on a throwaway database) before an image is built; a failure stops the release.
In place- Build specification: the gate runs before the image build
- Release gate script: scripts/run_tests.sh
-
This page is generated from one reviewed file; a test fails the release if any 'In place' line has no evidence or its evidence no longer holds.
In place- Test: tests/test_security_claims.py::test_every_in_place_claim_resolves
-
A written risk register, published.
Planned (S10.8)
People and access
-
A written access policy. The service has a single operator; production access is limited to the owner.
Planned (S10.8) -
Operator sessions expire after 12 hours, and administrative actions need a sign-in within the last 15 minutes.
In place- Test: tests/test_e2e_matrix.py::C_Roles::test_admin_action_needs_fresh_sign_in
- Test: tests/test_security_claims.py::test_operator_session_expires_after_12_hours
- Code: app/auth.py operator session lifetime
-
Staff security training and background-screening programme (there are no employees besides the operator).
Not held
Operations and resilience
-
Automated database backups are kept for 14 days.
In place- Infrastructure template: database cluster BackupRetentionPeriod: 14
- Live check after each deploy: database backup retention is at least 14 days
-
If the database is ever removed, a final snapshot is taken first.
In place- Infrastructure template: database cluster DeletionPolicy: Snapshot
-
Application logs are kept for 90 days.
In place- Infrastructure template: application log group RetentionInDays: 90
-
A public status page shows live figures only; no uptime percentage is claimed until uptime is measured.
In place- Code: app/status.py (status page source)
- Test: tests/test_e2e_matrix.py::A_RouteMatrix::test_matrix
-
Daily database copies to a separate backup vault: stated here once a completed copy has been verified on the live service.
Planned: verification pending -
Operational alarms that notify the operator: stated here once verified on the live service.
Planned: verification pending -
Measured uptime history on the status page.
Planned (S10.10) -
A restore drill from backup, with its date and result published.
Planned (S10.10) -
A copy of backups in a second region, and a standby database in a second zone.
Not held
Encryption
-
The managed cloud database is encrypted at rest with a customer-managed key.
In place- Infrastructure template: database cluster StorageEncrypted: true, with the customer-managed key
- Live check after each deploy: database storage encryption is on
-
Stored files are encrypted at rest with the same customer-managed key, versioned, and closed to public access.
In place- Infrastructure template: application file store encrypted with the managed key
- Infrastructure template: application file store versioning enabled
- Infrastructure template: application file store public access blocked
-
The data encryption key rotates automatically every year.
In place- Infrastructure template: data key EnableKeyRotation: true
-
All traffic is served over HTTPS; plain HTTP is redirected.
In place- Live check after each deploy: the public load balancer has an HTTPS listener with a certificate
- Live check after each deploy: plain HTTP is redirected to HTTPS
Verified against the live service on 24 September 2026.
-
Minimum TLS version: TLS 1.2.
In place- Live check after each deploy: minimum TLS version read from the listener security policy
Verified against the live service on 24 September 2026.
-
Browsers are told to use HTTPS only for this site (HSTS, 180 days).
In place- Test: tests/test_security_claims.py::test_security_headers_are_sent
-
Passwords are stored only as salted bcrypt hashes.
In place- Test: tests/test_security_claims.py::test_passwords_are_stored_as_bcrypt_hashes
-
API keys are shown once and stored only as hashes; expired and revoked keys are refused.
In place- Test: tests/test_s9_apikeys.py::S9::test_01_key_shown_once_and_only_hash_stored
- Test: tests/test_e2e_matrix.py::I_ApiKeys::test_no_plaintext_key_is_stored
- Test: tests/test_s9_apikeys.py::S9::test_02_expired_and_revoked_keys_get_401_others_keep_working
-
Database and session secrets are generated into a managed secrets store and injected at start-up, never kept in code.
In place- Infrastructure template: database secret generated (32 characters)
- Infrastructure template: session secret generated (48 characters)
Your data
-
Accounts and stored data are held in UK data centres in London. The one exception is Check the news: the claims it checks (never your name or account details) are sent as search queries to a processor that may handle them outside the UK, as the privacy notice explains.
In place- Deploy script: London region
- Privacy notice: the search-query exception
- Live check after each deploy: the service stack runs in the London region
-
Workspaces are isolated: one customer cannot read another's saved answers, maps, chats or keys.
In place- Test: tests/test_e2e_matrix.py::C_Roles::test_tenants_cannot_read_each_other
- Test: tests/test_s9_chats.py::S9Chats::test_04_other_tenant_cannot_read
- Test: tests/test_s9_apikeys.py::S9::test_04_cross_tenant_isolation
-
Chat history is private to the person who wrote it, even inside a team.
In place- Test: tests/test_s9_chats.py::S9Chats::test_03_same_tenant_other_person_cannot_read
-
Erasing an account blanks its chats and revokes every API key.
In place- Test: tests/test_s9_chats.py::S9Chats::test_09_erasure_blanks_chats
- Test: tests/test_s9_apikeys.py::S9::test_07_erasure_revokes_every_key
-
You can export your own chats; an export never includes anyone else's.
In place- Test: tests/test_s10l_docs.py::S10LDocs::test_03_chat_export_only_own_chats
-
Retention periods set per workspace, with every deletion recorded.
Planned (S10.7) -
A data processing agreement you can sign online.
Planned (S10.2)
Application security
-
One password rule everywhere (NIST SP 800-63B): at least 15 characters, and common, guessable or breached passwords refused - at sign-up, reset, invitation and change.
In place- Test: tests/test_e2e_matrix.py::D_Abuse::test_one_password_rule_everywhere
-
Repeated failed sign-ins lock the account for that address (5 in 5 minutes), and 10 across all addresses.
In place- Test: tests/test_security_claims.py::test_repeated_failed_sign_ins_lock_out
-
Session cookies are signed, HttpOnly and SameSite=Lax; a tampered cookie is refused.
In place- Test: tests/test_security_claims.py::test_session_cookie_is_signed_and_tamper_refused
- Test: tests/test_security_claims.py::test_session_cookie_flags
-
Session cookies are sent over HTTPS only (Secure flag).
In place- Live check after each deploy: the running service marks session cookies Secure
Verified against the live service on 24 September 2026.
-
Per-form anti-forgery tokens. Cross-site form posts are currently limited by SameSite=Lax session cookies only.
Not held -
Every route has an access rule, and the gate tries every route as every kind of user; a team seat can never become a platform administrator.
In place- Test: tests/test_e2e_matrix.py::A_RouteMatrix::test_every_route_has_an_access_rule
- Test: tests/test_e2e_matrix.py::C_Roles::test_team_seat_can_never_become_a_platform_admin
-
Rate limits on the support assistant (not bypassable with forged addresses), password resets and chat writes.
In place- Test: tests/test_e2e_matrix.py::D_Abuse::test_support_bot_rate_limit_cannot_be_dodged_with_forged_ips
- Test: tests/test_reset_r71.py::ResetTests::test_rate_limit
- Test: tests/test_s9_chats.py::S9Chats::test_07_writes_are_rate_limited
-
Every answer carries a reference that anyone can look up at /audit; a past record that was altered is detected.
In place- Test before every release: stored answer records are complete and intact
- Test before every release: a reference can be looked up and an altered record is detected
- Test before every release: every answer is stamped with a reference and kept on record
- Test before every release: an altered record can never be passed off as intact
-
Answer records are kept securely as a permanent record for at least 7 years.
In place- Infrastructure template: the answer-records store keeps every record for the stated period
- Live check after each deploy: the answer-records store keeps every record for the stated period
Verified against the live service on 24 September 2026.
-
A workspace's owner and managers can prove that any one of its answers is in the permanent record, without revealing any other customer's records.
In place- Test before every release: each answer can be proven to be on record, revealing nothing else
- Test before every release: a proof for an altered record always fails
-
An API key can be exchanged for a 15-minute access token, so the key itself can stay in a vault; a token cannot mint another and stops working when it expires or its key is revoked.
In place- Test: tests/test_s27_proofs.py::AccessTokens::test_mint_use_expire_revoke
-
Security headers on every response: no content sniffing, strict referrer policy, framing limited to this site.
In place- Test: tests/test_security_claims.py::test_security_headers_are_sent
-
The service refuses brokerage or exchange credentials in any request.
In place- Test: tests/test_s10m_watchlists.py::S10M::test_12_body_carrying_an_exchange_key_is_refused_400
-
The application runs as an unprivileged user inside its container.
In place- Container build file: USER monarch
-
Every release scans the pinned dependencies for known vulnerabilities; a known critical vulnerability blocks the release, and lower severities are reported in the release evidence.
In place- Release gate: dependency scan step
- Test: tests/test_security_claims.py::test_critical_vulnerability_blocks
-
Responsible disclosure address (/.well-known/security.txt): being set up.
Planned: verification pending -
Independent penetration test. Annual independent testing is planned; this line changes only when its summary is published.
Not held
Network
-
A managed firewall sits in front of the service: a common-attack rule set and per-address rate limits block traffic, with a tighter limit on sign-in and checkout.
In place- Infrastructure template: managed firewall attached to the load balancer
- Infrastructure template: sign-in and checkout rate limit (100 per address per 5 minutes)
- Live check after each deploy: the managed firewall is attached, with blocking rules
-
Blocking of known-bad input patterns at the firewall: stated here once verified on the live service.
Planned: verification pending -
The database sits in private subnets and accepts connections only from the application.
In place- Infrastructure template: database subnet group uses the private subnets only
- Infrastructure template: database accepts port 5432 from the application only
-
Application servers accept traffic only from the load balancer.
In place- Infrastructure template: application accepts traffic from the load balancer only
Suppliers
-
Card details are entered on the payment provider's hosted checkout and never reach our servers.
In place- Code: app/payments.py hosted checkout session
-
Suppliers are described by category on public pages; a test fails the release if any public page names one.
In place- Test: tests/test_e2e_matrix.py::D_Abuse::test_no_supplier_is_ever_named_to_a_customer
-
A named sub-processor schedule, released to customers under a signed data processing agreement.
Planned (S10.2) -
Formal security reviews of each supplier.
Not held
Certifications and commitments
-
Cyber Essentials certification (IASME) for the whole organisation, renewed every year; the certificate can be checked on the certification body's public register.
In place- Code: app/trustmarks.py certificate record
-
Registered with the Information Commissioner's Office (ICO) as a data controller.
In place- Code: app/trustmarks.py registration record
-
Cyber Essentials Plus (independently tested) certification.
Not held -
SOC 2 report.
Not held -
ISO/IEC 27001 certification.
Not held -
Uptime service-level agreement (SLA).
Not held -
Paid bug bounty programme.
Not held -
HIPAA compliance. Do not use the service for US protected health information.
Not held
Reporting a vulnerability
A dedicated disclosure address is being set up. Until then, please use the contact form (topic "Report a problem") and do not include exploit details there; we will reply with a private channel. There is no paid bug bounty.
Also see the privacy policy, the trust page and the status page.
