Technical and organisational measures

What we do, and how you can check it

The measures below are implemented, not planned, unless the text says otherwise. Where something is weaker than it sounds, this page says so — a measures document that only contains good news is not evidence of anything.

SCOPE · TABLET APP, SESSION PAGE, STAFF CONSOLE, BACKEND, RELAYSTATUS · PRE-PILOT

Identity and the trust boundary

Four kinds of actor exist, and each one has a real identity that a database policy can be written against. That last part is the point: an anonymous browser with no identity cannot be authorised, only guessed at.

Actors
ActorIdentityHow they get it
StaffAuthenticated accountProvisioned deliberately and mapped to an organisation. There is no self-signup.
TabletAnonymous auth userCreated on first launch, bound to one tablet row when it is paired.
End userAnonymous auth userCreated in their browser when the session page loads, bound to one session on first claim.
Second screenAnonymous auth userSame, in a distinct role, for document-scan mode. Separate device, separate claim.

The anonymous identifier is pseudonymous by construction: it is created in the end user’s own browser and is not linked to a name, an email address or an account. It exists so that a session link can be bound to one device — which is what makes it single-use — and so signaling channels can be authorised rather than merely obscure.

The session link is the capability

End users hold no table access at all. The unguessable session identifier is the token, and it is spent on the first claim. A link that has been opened cannot be opened by a second browser; the attempt is refused and recorded.

Tenant isolation

Every table is protected by row-level security scoped to organisation membership, and every staff-facing policy resolves through the caller’s memberships rather than through a value the client supplies.

We treat row-level security as the only boundary rather than as defence in depth, because in this platform it is: the database roles a real HTTP caller uses hold broad grants, so a table with the wrong policy is directly reachable. The test harness deliberately mirrors those grants, which means an isolation test cannot pass because a permission happened to be missing — it has to pass because the policy works.

Isolation is proved by an automated suite that runs as those same anonymous and authenticated roles, across six test files covering row-level security, roles, session lifecycle, consent, admin oversight and observability. It runs against a plain PostgreSQL, so it runs in continuous integration on every change.

Capability boundaries

Who can reach what
ResourceReachable by
Organisations, tablets, sessions, pairing codesStaff of that organisation
A tablet’s own row and its own sessionsThat tablet only
Audit eventsAdministrators of that organisation, read-only. No insert, update or delete policy exists for anyone.
Member management, tablet rename and deleteAdministrators, with a constraint that prevents removing the last one
Live observation of a sessionAdministrators. Read on the session channel, writes refused.
Pairing attempts, session participants (writes)Nobody. Trusted database functions only.
Session link resolutionAnonymous, for live sessions only — and it returns the session’s status and expiry, not which tablet it belongs to.
Signaling channels and relay credentialsThat session’s tablet and the participants bound to it

Signaling and relay authorisation

Signaling is one private channel per session, with a second channel for document-scan mode. Only that session’s paired tablet and the participants bound to it may join, and only while the session is live. The check is a policy evaluated by the realtime layer against the caller’s token — not application code that a modified client could skip.

Relay credentials are minted per session by a server-side function, expire shortly after the session, and are issued only to the same set of verified participants. No credential is compiled into any client, so a decompiled app yields nothing reusable.

These two rules are kept deliberately in sync: a relay credential and a signaling channel grant comparable access to a session, so they must not disagree about who is allowed in.

Pairing a tablet

  • Eight characters from a 32-symbol alphabet — roughly 1.1 × 10¹² possible codes.
  • Fifteen minutes of validity, single use.
  • Five failed attempts per device in fifteen minutes locks that device out.
  • A system-wide breaker refuses pairing entirely past fifty failures a minute.

Honestly, about the rate limits

The entropy of the code is what actually defeats brute force. The per-device limit is weak on its own, because an attacker can mint a fresh anonymous identity per guess, and the database cannot see client IP addresses — so the system-wide breaker is the real backstop. Genuine per-IP limiting would have to live at the edge, and it does not exist yet. It is listed again under what is not done yet.

There is deliberately no “burn a code after N failed guesses against it” rule. It sounds protective and cannot fire: a code that exists is claimed on the first correct guess, so repeated failures against one string only ever describe codes that do not exist.

Transport and encryption

HTTPS and WSS throughout. WebRTC media and data channels are encrypted end to end with DTLS-SRTP, between the tablet and the browser. When a direct path is impossible the relay forwards that ciphertext; it holds no key and cannot read the stream it is carrying.

The relay listens on a narrow port range, denies relaying to every private address range, and offers TLS on the standard secure port for networks that permit nothing else. Its logging is deliberately quiet: verbose relay logs are a per-session record of client IP addresses with no operational payoff.

Browser hardening

Both web applications send a strict Content Security Policy, refuse to be framed, send HSTS, and carry a permissions policy that denies every browser capability except the camera on the one page that needs it.

The same discipline applies to this marketing site, for a reason that is more than tidiness. It loads no font from a content delivery network, runs no analytics, embeds nothing, and its policy permits connections to its own origin only — so “this domain sets no foreign cookie and makes no third-party request” is enforced by the browser rather than promised in a paragraph. There is no cookie banner because there is nothing to consent to.

Input safety

Injected touch coordinates are clamped to the tablet’s physical display and the input queue is bounded and rate-limited on the device, so a faulty or hostile client cannot dispatch gestures off-screen or flood the gesture pipeline. The clamp is on the tablet, which is the side that cannot be modified by whoever holds the link.

Integrity and the audit trail

The session state machine is enforced in the database by trigger, not by convention: a finished session cannot be restarted and cannot be moved to another tablet.

The audit trail is append-only. No application role holds insert, update or delete on it; only trusted server-side functions write to it. Refusals are persisted alongside successes — rejected session claims, failed pairing attempts, denied administrative actions.

Those refusals are returned rather than raised as exceptions, which is a small implementation detail with a large consequence. PostgreSQL has no autonomous transactions: raising would roll back the very record written on the failure path. A lockout counter that raises can never fire, and a rejected claim that raises is never audited. Both were real bugs here, found by the test suite rather than by review, and the rule is now written down.

Data minimisation and retention

No recording, by construction. There is no code path in the tablet app, the session page or the backend that writes a frame, screenshot or camera image to disk, or uploads one. Camera stills exist only as in-memory blobs in the receiving browser and are released when replaced. The one function that turns a captured frame into bytes exists solely in a developer test screen that is compiled out of production builds.

Retention is enforced in code. A nightly job purges sessions after 90 days, audit events after 12 months and pairing attempts after 30 days. It is covered by tests.

Minimal disclosure. Resolving a session link returns the session’s status and expiry, and nothing else — not the tablet it belongs to.

Because nothing about a session’s content is stored, an access or erasure request concerns metadata only: session records and audit events tied to an anonymous identifier.

Availability

Sessions expire in the database, so a session cannot stay open because a client went away. A dropped connection is restarted in place a bounded number of times, so a network blip on a mobile connection does not end the session.

Tablets report health on a short heartbeat — application version, operating system version, battery, capture readiness and whether the accessibility service is enabled — and the console shows a tablet as offline when that heartbeat goes stale, rather than trusting a clean shutdown that may never have happened.

We publish no uptime figure, because we do not have one to publish. External uptime monitoring, alerting and backup-restore testing are on the pre-launch checklist and are not in place yet.

Subprocessors and regions

Subprocessors
SubprocessorRoleRegion
SupabaseDatabase, authentication, realtime signaling, edge functionsEU · Frankfurt
VercelHosting for the console and the session pageEU regions
HetznerThe self-hosted relayEU · Nuremberg / Falkenstein

No screen content or camera image reaches any of these except the relay, which forwards encrypted media it cannot read. No transfer outside the EU is intended; every component is EU-hosted by design, which is precisely why the relay is self-hosted rather than rented from a global provider.

There is deliberately no error-tracking vendor in this list. One would have to be EU-hosted, have a data processing agreement, and be written into the processing record before customer sessions ran through it — and an error tracker with session replay would record the exact thing the consent notice promises is never recorded. Diagnostics report into the same EU database instead.

Contract statusData processing agreements with each subprocessor are a prerequisite for the first customer deployment and are tracked on the deployment checklist. Our privacy notice, terms and Impressum are currently engineering drafts pending legal review — which is why the legal pages on this site are marked as unfinished rather than filled with plausible text.

What is not done yet

Named so they are decisions rather than oversights. Each is tracked, and each carries a risk we have accepted for the pilot stage.

  • Session list scoping is a convenience, not a boundary. The audit log is a hard, policy-level boundary. The list of sessions is not: its underlying policy remains organisation-wide so the console can watch it live, so a staff member can read raw session rows for their own organisation through the API. Tightening it is a one-line policy change plus a console change.
  • No per-IP rate limiting anywhere. The database cannot see client addresses; real per-IP limiting has to live at the edge and does not exist yet.
  • Realtime authorisation policies can only be fully verified against a hosted project. The underlying authorisation function is covered by tests, but that the policies are wired correctly is verified as a deployment gate rather than in continuous integration.
  • External uptime monitoring, alerting and backup-restore testing are not in place.
  • Organisational procedures are incomplete. A documented data-subject-request procedure, a deletion procedure for a departing customer, and named responsibility for the signing keys and the relay secret are written down as required and not yet finished.
  • No certifications. We hold no SOC 2 and no ISO 27001. This page is what we have instead, and it is meant to be checked rather than trusted.
If you are evaluating thisWe will walk a security reviewer or a data protection officer through any claim on this page, against the implementation, on a call. Ask for the technical measures document and the processing record: get in touch.