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: at least 10 characters 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
-
Answers carry a reference into a hash-chained audit ledger; altering a past entry is detected, and a reference can be looked up publicly at /audit.
In place- Test: tests/test_e2e_matrix.py::Z_DataOnDisk::test_audit_ledger_chain
- Test: tests/test_audit_r72.py::LedgerTests::test_chain_and_verify
- Test: tests/test_audit_r72.py::MiddlewareTests::test_stamp_added_and_ledgered
- Test: tests/test_s3b_ledger.py::Ledger::test_2_altered_content_can_never_be_attested
-
The ledger's head is anchored to write-once storage that cannot be altered or deleted for 7 years.
In place- Infrastructure template: audit store with write-once retention in compliance mode
- Live check after each deploy: the audit anchor store is configured and write-once (compliance mode)
Verified against the live service on 24 September 2026.
-
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
-
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.
