For vendor-risk and security teams · answers as of 23 September 2026
Security questionnaire, answered
The questions a bank’s or insurer’s vendor-risk team sends, answered before you send them. Every answer links to something you can check: a page, a live endpoint, a published file, or a test in our repository. Where the answer is no, it says no. If your own questionnaire asks something that is not here, send it to anil@riskrouter.eu and the answer will be added here for the next team too.
Read this first
Three facts that frame every answer
- RiskRouter is a validation build operated by one person. There is no legal entity yet, no staff, and no contract yet. Answers below describe what is in place now, not what a larger company would have.
- It holds almost nothing worth stealing. Quotes carry no personal data; the v2 evidence log holds only SHA-256 digests; API keys are stored only as digests. The one table with personal data is contact-form enquiries, deleted after twelve months.
- Your evidence does not depend on our security. A ledger export or proof you hold verifies offline, against a key we published and heads anchored in Bitcoin. If we were breached, a rewrite of history would be detectable by you, without us. How.
Evidence marked repository lives in our source repository, which is private today. Any file named here is shown to a prospective customer on request.
1 · Data
What data, where, for how long
| Question | Answer | Evidence |
|---|---|---|
| Do you process personal data on our behalf? | No, by design. A quote is a vertical and a set of true/false options. Anything else in a request is dropped before storage. The evidence log accepts only a 64-character digest and a short tag. | /trust; repository: tests/pii.test.mjs sends personal data in every field and asserts none is stored |
| What personal data do you hold at all? | Contact-form enquiries (name, email, message), and the administrator’s own email address in the admin audit log. | /privacy |
| Retention | Enquiries are deleted after 12 months by a scheduled database job, running daily. Ledger and evidence entries are kept permanently; that is their purpose, and they hold no personal data. | /privacy; repository: schema.sql, purge_expired_contact_enquiries() |
| Where is data stored? | Supabase, eu-central-1, Frankfurt, Germany. | /trust, /continuity |
| Where is it processed? | Not only in the EU. Requests are handled by Cloudflare at the location nearest the caller, which may be outside the EEA. Nothing is stored there. EU-only processing would need Cloudflare’s regional services, which we do not have. | /trust, /privacy |
| Encryption in transit | Yes. HTTPS only, with HSTS on the site. The Workers reach the database over HTTPS. | Response headers of any page (Strict-Transport-Security) |
| Encryption at rest | Provided by the database host, Supabase, under its own security documentation. We add no application-level encryption, and have not independently verified theirs. | Supabase’s documentation |
| Can you delete our data on request? | Enquiries: yes. Ledger and evidence entries: no, never, because an audit trail that can be deleted is not one. That is why they may hold no personal data, and why a self-serve key must acknowledge it before recording. | /privacy, /integrate |
2 · Access control
Who can reach what
| Question | Answer | Evidence |
|---|---|---|
| Who has administrative access? | One person, the operator. No one else holds production access. | /continuity |
| How is the admin console protected? | Cloudflare Access in front, restricted to one email address; the Worker then verifies the Access signature itself rather than trusting a header, and checks the address against its own list. Writes are accepted only from the console’s own origin. | Repository: admin-worker.js, tests/admin.test.mjs |
| Is multi-factor authentication enforced? | Not claimed. The minimum configuration is a one-time code sent to the allowed address. Stronger factors depend on the identity provider linked to Access, and we do not claim one here. | — |
| What can an administrator do? | Five things and no more: create a distributor, suspend or reactivate one, issue a key, revoke a key, set an enquiry’s status. There is no path from the console to a quote, a premium, a ledger entry or the schema. Every action is one database transaction that also writes an append-only audit row. | Repository: CLAUDE.md rule 8, schema.sql, tests/admin.test.mjs |
| How are your customers authenticated? | A per-distributor API key, shown once, stored only as its SHA-256, revocable immediately. An unknown, revoked or suspended key is refused with 401 and nothing is written. | /docs; repository: tests/worker.test.mjs |
| Can one customer read another’s records? | No. An export carries the caller’s own entries in full and only the hashes of anyone else’s, which is what lets the chain be checked; an evidence proof for another distributor’s entry answers exactly like a missing one. Row-level security is forced on every table and the public roles have no grants. | Repository: tests/evidence.test.mjs, tests/schema.test.sql |
| Single sign-on for our staff? | No, and there is nothing to sign in to. Customers integrate with keys; there is no customer console. | — |
3 · Integrity
Can records be altered?
| Question | Answer | Evidence |
|---|---|---|
| Can a record be edited or deleted? | No. Database triggers refuse UPDATE and DELETE on the ledger, the evidence log and the admin audit log, for every role including our own service role; the evidence log also refuses TRUNCATE by trigger. | Repository: schema.sql, tests/schema.test.sql |
| Would a rewrite of history by you be detected? | Yes, by you. Entries are hash-chained (v1) or Merkle-logged (v2); heads are signed with a published key and anchored in Bitcoin through OpenTimestamps. A rebuilt history produces a different head than one you or a witness saved. | /spec, /verify |
| Can we verify without you? | Yes. Two independent verifiers, JavaScript and Python, standard library only, plus an in-browser checker that sends nothing. | /spec, Python verifier |
| Do independent parties co-sign? | Not yet. The witness tool exists; no independent witness runs it today. | /spec#witnesses |
4 · Development and change
How the code is built and shipped
| Question | Answer | Evidence |
|---|---|---|
| How do changes reach production? | Every push to main runs the unit, schema, browser and integration-kit test suites; the deploy job runs only after all of them pass. The running commit is published, so you can see whether production came from that path. | /status, GET /api/v1/health (build.commit) |
| Is every change reviewed by a second person? | No. There is one developer. Review is by automated tests, several of which enforce the security rules mechanically (for example, that each Worker can reach only its named destinations). | Repository: tests/isolation.test.mjs |
| Third-party code in production | None. The Workers import only our own files; the build fails if that ever changes. Build and test tools are listed separately and never deployed. | /sbom.json (CycloneDX) |
| Dependency updates | Dependabot watches the build tools and CI actions weekly. The deploy tool is pinned to one version. | Repository: .github/dependabot.yml, package.json |
| Web application protections | A hash-based Content-Security-Policy on every page (no inline script runs unless its hash is listed), HSTS, frame denial, no third-party scripts, per-address rate limits on the API, and database-enforced write ceilings that hold even if the API is bypassed. | /security; response headers |
| Secrets management | Credentials live only in Cloudflare Worker secrets and GitHub Actions secrets. None is in source code; tests fail if a Supabase URL or key appears in a Worker, or reaches a response. | Repository: tests/isolation.test.mjs, tests/worker.test.mjs |
| Penetration test | None. If you need one before a pilot, it is a reasonable condition to agree. | /security |
| API change policy | The formats evidence depends on are frozen permanently. A written deprecation policy with notice periods is in draft and not yet published. | /spec, /changelog |
5 · Logging, incidents, continuity
When something goes wrong
| Question | Answer | Evidence |
|---|---|---|
| What is logged? | Cloudflare’s request logs for the Workers, and our own error lines when a write is refused: the HTTP status and the database’s error code and message, never a credential and never the rejected row. (Checking this answer found that a refused contact-form write logged the database’s whole error body, which can contain the sender’s name and email. Fixed on 23 September 2026, with a test.) Every administrative action is in an append-only audit table. | Repository: worker.js, tests/pii.test.mjs, schema.sql (admin_events) |
| Monitoring and alerting | No external uptime monitor and no 24/7 on-call. The API reports its own state honestly (it probes the database rather than trusting its configuration), and /status shows it live. | /status |
| What happens when the database is unavailable? | Quotes are still priced; nothing is recorded, and every response says recorded: false. A failed write is never reported as a success. | /docs; repository: tests/worker.test.mjs |
| Incident response | Written runbooks for incidents, leaked credentials (each secret, with what its holder could and could not do), key rotation and restore. Our runbook sets notification of affected distributors within 24 hours of a critical incident; that becomes a contractual commitment only in a contract. | Repository: docs/runbooks/ (shown on request) |
| Backups | No managed backups. The database runs on a plan that keeps none. We take our own and store them off the service. | /continuity |
| Has a restore been tested? | Yes, 23 September 2026, passed. All entries restored; the restored head matched the head anchored in Bitcoin; the restored ledger still refused edits and deletions. | /continuity; repository: docs/drills/2026-09-23.md, tests/restore-drill.test.mjs |
| Recovery time and point objectives (RTO / RPO) | None committed. Loading a backup takes under a second at today’s size; recovery time is dominated by detection and repointing. Data recorded after the last backup would be lost with the database. | /continuity |
| Exit plan if you stop | Documented: what you keep, what stops, the steps to leave, and facts for a DORA register of information. | /continuity |
6 · Third parties
Sub-processors and suppliers
| Question | Answer | Evidence |
|---|---|---|
| Sub-processors | Cloudflare (runs the site and API), Supabase (database, Frankfurt), Microsoft (the mailbox behind our published address). That is the complete list. | /privacy |
| What can the API contact? | Exactly one destination: the database. It contacts no carrier, bank, payment processor, analytics or advertising service. A test fails if a new destination is added. | /trust; repository: tests/isolation.test.mjs |
| Certifications | None. No ISO 27001, no SOC 2. Our providers publish their own; that does not make us certified, and we do not say it does. | /security |
| Insurance (professional indemnity, cyber) | None yet. | — |
| Regulatory status | Not a registered insurance intermediary and not an insurer. RiskRouter is software for licensed distributors and sells no cover. | /compliance |
Report something
Found a problem, or an answer that is wrong?
Security reports: /security and security.txt. An answer on this page that is no longer true is a bug of the same kind; tell us and it will be corrected here and on the changelog.