Security

Last updated: 2026-08-21

This page describes how Tenet is actually built and operated. It is written for the person doing the technical evaluation, not for the person collecting the certificate.

It includes a section on what we do not yet have. A trust page without one is a sales page, and you should read it as such — including ours, if we ever remove it.


The architectural claim, and why it is the whole security story

Most identity vendors' security story is we hold your users' most sensitive data, and here is how well we guard it. Ours is different in kind:

We do not keep the sensitive material at all.

Selfies and identity-document images are processed stream-through — held in memory for the seconds a check takes, then gone. Nothing is written to disk, nothing is logged, nothing is backed up, nothing trains a model.

The security consequence is the point: a breach of Tenet cannot leak a face or an ID document, because there is no store of them to breach. Every control below matters, but none of them matters as much as the data that is not there.

The honest limits on that claim

Three, stated here rather than in a footnote:

  1. Age estimation and document checks run at a provider. By default that is our own model on infrastructure we control; a third-party fallback also exists. Both receive a selfie transiently. We do not claim images never leave our infrastructure.
  2. We retain a date of birth for document-based checks. The images are gone; the extracted date of birth is kept in the decision record for seven years. Name, address, and document number are discarded at receipt.
  3. The audit archive is immutable and cannot be deleted — including by us, including on request. That is what makes it worth anything, and it is disclosed in the Privacy Policy § 3.4.

Hosting and infrastructure

Application hostingHetzner Cloud — Hillsboro, Oregon, United States
DatabasesPostgreSQL 16, self-hosted in the same deployment
Audit archiveAmazon S3 with Object Lock, COMPLIANCE mode (us-east-1)
EdgeCloudflare — DNS, and, when proxying is enabled, DDoS mitigation and WAF
Model inferenceModal (our own age model); Yoti as a third-party fallback
Document verificationID Analyzer

Data residency: United States. Application services, databases, and the audit archive are all held in the United States — production runs in Hillsboro, Oregon. Development is a separate, non-production tier in Helsinki holding no real data; if that ever stops being true, this claim is the first thing that breaks.

Backups. Both databases are dumped daily to object storage on a 30-day retention. That copy is deliberately not under an immutability lock — an unalterable seven-year copy of every audit row is a liability rather than a control, and the archive described below already holds what has to survive. A backup nobody has restored from is a belief rather than a backup, so the restore has been rehearsed against the same mechanism.

Services are independently deployable, and the whole deployment is defined as code in version control — compose files and a committed inventory of every configuration key — rather than assembled by hand in a vendor dashboard that nothing can diff or review.

Environment parity is enforced, not aspirational. Development and production run identical code paths with identical required configuration — only the values differ. A continuous-integration check rejects environment-conditional behaviour: no "generate a secret if missing in development", no check that silently no-ops outside production. The reason is that a guard which does not run in development is a guard nobody has ever seen work.


Encryption

In transit. TLS 1.2 or higher on every external connection, with HSTS enforced (max-age two years). Certificates are issued and renewed automatically. Internal service-to-service calls between the orchestrator and a verification vertical are authenticated with a shared secret.

At rest. All databases and object storage are encrypted at rest by the platform.

At the application layer. Two categories get a second layer of AES-256-GCM encryption with keys held outside the database, so that database access alone does not yield plaintext:

  • the opaque subject reference a customer supplies
  • end-user contact details captured for credential delivery

Key material. Signing keys are loaded from the environment, never committed, and never logged. The wallet's holder key is a special case — see below.


Authentication and access control

Customer API access uses rotatable bearer keys (ak_live_…). We store only a SHA-256 hash — we cannot recover your key, only replace it. Keys are rotatable by you, without our involvement.

The hosted verification flow authenticates with short-lived ES256 JWTs scoped to a single verification session, carrying no personal data.

Result delivery happens two ways, neither of which puts results in a URL:

  • Webhooks signed with HMAC-SHA256 with rotation support, at-least-once delivery, idempotency keys, and a bounded retry budget with dead-lettering.
  • A result token on the return redirect that is opaque and carries no result data. It is exchanged over an authenticated back-channel for a binary status. Granular detail is never put in a redirect.

Internal access is least-privilege and multi-factor. No shared accounts. Access is reviewed when someone joins or leaves, and everyone who has it is bound by written confidentiality obligations that survive the end of their engagement.

Tenant isolation is enforced on every query path, not by convention.


Application security

Origin. TLS is terminated at our own reverse proxy with automatically renewed certificates, and a service is reachable only if it is explicitly published — the proxy routes to what opts in, not to whatever happens to be listening.

There is no edge. Our domains use Cloudflare for DNS only: requests reach our origin directly, so there is no web application firewall, no managed DDoS mitigation, and no origin-secret gate in front of us. All three are built and configurable in our stack; none is switched on. We would rather say that than describe a control that is not enabled — see What we do not yet have for what it means and what we do instead.

Rate limiting. Both services enforce token-bucket limits at the origin: one budget per client address, and a second per tenant once a request carries an API key. A caller may burst up to its budget and thereafter sustains the refill rate; exhausting it returns 429 with a Retry-After header. Liveness endpoints sit outside the limit deliberately, so a health probe is never throttled. This is the half of the problem an edge cannot solve for us, because an attacker who resolves our origin address talks past every edge rule — and it matters here because abuse does not cost us bandwidth. Every accepted verification spends a paid vendor call and writes a record to a seven-year immutable archive, so a flood is money and permanent storage rather than load. This is live, and was confirmed against production on 2026-08-21 rather than assumed: a concurrent burst was allowed up to the budget and then refused, on the orchestrator and the age vertical alike.

Security headers. X-Content-Type-Options: nosniff, frame-deny, a restrictive Content-Security-Policy, and cross-origin opener and resource policies.

Log redaction. Structured logging with a redaction list applied at the logger, covering images, document payloads, authorisation headers, tokens, and secrets. A developer cannot leak an image by logging a request object carelessly, because the layer that would print it strips it first.

Audit integrity. Decision records are append-only, enforced by database triggers that reject DELETE outright and permit UPDATE only on the two export-tracking columns, and only once. Enforcement is structural and role-independent, so it holds against a compromised application. This half is live.

Records export nightly to S3 Object Lock in COMPLIANCE mode, where no credential — including the account root — can delete or shorten retention. The lock is applied per object at write time for exactly seven years, so it is a property of each record rather than a bucket setting somebody could relax later.

Third-party-verifiable results. Each concluded verification can be fetched as a signed attestation — an ES256 JWS that anyone can verify against our published did:web key at /.well-known/did.json, with no participation from us. This matters because the alternative is worse than it looks: a shared-secret signature on a webhook proves authenticity to the recipient and to nobody else, so it cannot help a customer who has to demonstrate something to a regulator. An attestation can.

This was verified end to end against production on 2026-08-20, because a claim about what a third party can check is worth nothing until a third party has checked it. A concluded verification's attestation was fetched, then verified with no API key, no database access and no shared secret — only the JWS and the DID document at https://api.choosetenet.com/.well-known/did.json, signed by did:web:api.choosetenet.com#key-1 over evidence schema 2026-08-17. The verifying script is about a hundred lines and depends on nothing but node:crypto, so it can be handed to a tenant, a regulator or opposing counsel as reference code, or rewritten in any language in an afternoon.

The payload carries what this page says it carries and nothing else: the outcome, the method, the model version, the threshold, a one-way subject pseudonym, and the version and timestamp of the biometric notice the person was shown. No image, no document, no date of birth, no name.

Supply chain. Every change runs dependency and secret scanning in continuous integration. Secret scanning has no path-based exclusions — including for test files, which is a common way live credentials reach a repository. There is deliberately no scanner configuration file: an exemption outlives the situation that justified it, so findings are fixed at the source instead.

Automated policy enforcement. A set of mechanical checks blocks merge on violations of our engineering principles — banned analytics dependencies, cross-service import boundaries, environment-parity violations. These are not code review conventions; they fail the build.


The wallet: non-custodial by construction

The Tenet wallet holds credentials on a person's own device.

The private key is generated inside the device's secure element and is non-exportable. Every signature is the operating system asking the security chip to sign. The key never appears in application memory, in storage we control, or on any server.

The consequence is deliberate and worth being blunt about: we cannot produce a user's key. Not for them, not for us, not under a court order. There is no escrow and no backup. If a device is lost, the user re-verifies and is issued a fresh credential — recovery is re-issuance, never key restoration.

Related properties:

  • No product analytics in the wallet. No Amplitude, Mixpanel, Firebase Analytics, or Segment, and no fingerprinting attribution SDKs. This is enforced by a dependency deny-list that fails the build. Diagnostics are PII-free and IP-dropped.
  • Device policy. Devices without hardware-backed key storage are refused. Devices without a biometric or passcode are required to set one up. Rooted and custom-ROM devices are warned, not blocked — a user who has taken control of their own device is not our adversary.

Payments

Tenet does not process, transmit, or store cardholder data. Customer invoicing runs on Stripe, out of band from the platform: an invoice is issued and paid on Stripe's own pages, so card details are entered there and never reach us. There is no checkout, no card form, no stored payment method and no payment SDK anywhere in the Tenet platform, and that is what makes the sentence above true rather than aspirational — the way to break it would be to build a card form of our own, and we will not.

No customer has been billed yet. Stripe is named here as the decided mechanism, and it is listed as a sub-processor below before it processes anything, rather than after. It receives business contact details — who to invoice and where — and never verification data: no image, no document, no decision, and nothing about a person being verified.


Sub-processors

The authoritative list, with what each one actually sees:

Sub-processorPurposeLocationReceives verification images?
Hetzner Online GmbHApplication and database hostingUnited States (Hillsboro, Oregon)No
Amazon Web Services, Inc.Immutable audit archiveUnited StatesNo
Cloudflare, Inc.DNS, and edge protection when enabledUS / global edgeIn transit only
Google LLCBusiness email (Google Workspace) — the mailboxes on this siteUnited StatesNo
Stripe, Inc.Customer invoicing and payment. Decided, not yet in useUnited StatesNo
Modal Labs, Inc.Tenet's own age-estimation modelUnited StatesYes — transient
Yoti LtdThird-party age estimation (fallback)United KingdomYes — transient
ID AnalyzerDocument verification, face matchingUnited StatesYes — transient

We give at least three days' notice before adding or replacing a sub-processor, by updating this page and emailing every active customer at the address given during onboarding. There is nothing to subscribe to. That notice period is also your window to object, and the DPA sets out what happens if we cannot resolve it.


Incident response

We commit to notifying affected customers within 48 hours of becoming aware of a security incident affecting their data — including what we know, what we do not yet know, and what we are doing. We would rather send an incomplete notification quickly than a polished one late.

Post-incident, we publish what happened and what changed.


Reporting a vulnerability

Email security@choosetenet.com. That mailbox receives mail from outside our organisation and is monitored — verified with a real message on 2026-08-17.

Our commitments: acknowledgement within 2 business days; an assessment within 10; credit if you would like it; and we will not pursue legal action against good-faith research that respects user privacy, avoids degrading the service, and gives us reasonable time before disclosure.

Please do not test against real users' data, run automated scanners at volume against production, or access an account that is not yours. If you need a test environment, ask.

There is no paid bug bounty. What is on offer is the response above — acknowledgement, an assessment, credit if you would like it, and no legal action against good-faith research. Said plainly, because a disclosure policy written well can be mistaken for one.


What we do not yet have

We are a pre-launch company. Here is what an evaluator would reasonably ask for and what the honest answer is today.

Status
A web application firewall, and edge DDoS mitigationNone. Cloudflare serves our DNS and nothing more, so traffic is not inspected or filtered before it reaches us. Turning it on is not a switch: proxying alone is bypassable by resolving the origin address, so it only becomes a defence together with an origin-secret gate and a narrowed firewall — and a half-configured edge is worse than none, because it looks like protection. We would rather do it deliberately, when there is traffic worth the attempt, than in time for a launch
Rate limiting that survives more than one instancePartial. The origin limits described under Application security are live and enforced as of 2026-08-21. Each budget is held in the process that serves the request, so it is a per-instance budget — we run a single instance of each service, which makes that the same thing today and stops making it the same thing the moment we scale out. The store sits behind an interface for exactly that reason, so a shared one replaces it without changing how the limits behave
RedundancyNone. A single host, deliberately — a cost decision at current volumes. This is not a resilience posture and should not be read as one
SOC 2 Type IIIn progress. No target date — we will not name one we cannot hold
ISO 27001No. Not planned near-term
Independent penetration testIn progress. No target date, for the same reason
Third-party audit of non-retentionNo for Tenet's own pipeline. Our third-party age-estimation fallback maintains its own external audits
Bug bountyNo. Responsible disclosure only, per above
Cyber insuranceNone. Not carried today
Formal security training programmeNone at current headcount
Background checks on personnelNone at current headcount — two people, both founders
Status pageNone. Incidents reach affected customers by email; see Incident response above

We would rather tell you this now than have you discover it in a questionnaire. If a control here is a blocker for you, tell us — knowing which one is what tells us where to spend first.


Questions

security@choosetenet.com for anything on this page, including security questionnaires. We will complete a reasonable questionnaire rather than insist you accept this page instead.