Trust and security

What protects your evidence, and what we do not claim

C-NDA exists to produce proof that holds without anyone taking our word for it. The same standard applies to this page: below are the measures that are in the code, the places the data actually lives, and, in plain words, the certifications we do not hold.

Where the data lives
European Union (database in Frankfurt)
Access to the database
Row-level security on every table
The proof
Recomputable by anyone, without our software
Certifications
None. We hold none, and we show none.

Where your data lives

Nothing sits “in the cloud” as an abstraction. Every category of data has a place, and a place is a jurisdiction.

Database, sign-in and documents

Supabase, European Union, Frankfurt region. The region is not a setting someone can flip: it is written into the rules of the project as non-movable, because moving it would move the legal ground under every record.

Recordings and screenshots of a session

Cloudflare R2, in a bucket created with EU jurisdiction. On R2 the jurisdiction is chosen when the bucket is created and cannot be changed afterwards (there is no setting to correct it later, only a bucket to rebuild) so our configuration checks the endpoint it is given and refuses to stay quiet when it is not the European one.

The portal and the witness

The portal runs on Vercel. The witness that joins a meeting and records it runs in a container hosted by Railway, in Amsterdam.

Error reports

Sentry, EU region. Authorisation headers, cookies, request bodies, meeting links and one-time codes are stripped before anything leaves the process, and we do not record browsing sessions.

Card details

Never with us. Payment and subscription management happen on Stripe's own pages; a card number never reaches our systems.

The complete list of processors, with what each one receives and why, is in the Privacy Policy. Read the Privacy Policy

How it is protected

Three questions decide the security of a system like this one: who can read what, how we know who you are, and what happens to the things that cross the wire.

Who can read what

  • Row-level security is on for every table. Where a table is reachable by a browser at all, its policies are owner-scoped: another account's query does not return a filtered version of your data, it returns nothing.
  • The table holding participants has no client policy at all: not a restrictive one, none. Invitation tokens and one-time-code hashes are reachable only through server endpoints, and even the owner of a protocol gets zero rows querying it from a browser.
  • On the profile table the grants are per column: a browser cannot write its own plan, its billing identifier or its expiry date, even holding a perfectly valid session.
  • Files (certificates, uploaded NDAs, recordings, logos) live in private buckets. None of them is ever a public URL: every download is a signed link with a short life, issued by the server after it has checked who is asking.

How we know who you are

  • An invitation link carries a 32-byte secret. We store only its hash, it works once, and it expires after seven days.
  • The one-time code sent by email is salted per invitation, stored hashed, compared in constant time, expires in ten minutes and locks after three wrong attempts. That lockout does not depend on any rate limit, which is why it still holds if the rate limiter is having a bad day.
  • Two-factor authentication is optional, and once you turn it on it is enforced in the middleware, not in the interface: a password alone opens a session but not the dashboard, and a password reset is not a way around the second factor.

What crosses the wire

  • Every event the witness sends to the portal is signed HMAC-SHA256 over the raw body, refused outside a five-minute window, and processed exactly once: a replayed event changes nothing.
  • Billing events are verified with Stripe's own signature, stored raw before anyone interprets them, and are idempotent on the event identifier.
  • The public endpoints that could be hammered (invitations, one-time codes, public verification) are rate-limited in the database rather than in memory, because on serverless two consecutive requests may not be the same machine and an in-memory counter would be a limit in name only.
  • What reaches your browser after a failure is a domain code. The database message, the storage message and the provider message stay in the logs, where they are useful and harmless.
  • Responses carry a content security policy that forbids framing outright, plus nosniff, a strict referrer policy, a permissions policy and HSTS, and the server does not announce what it is built with.

The link that does not exist

This is the invariant the whole product is built around, and it is a security property before it is a legal one.

  1. 01

    In a protected meeting the meeting link is encrypted at rest in the database.

  2. 02

    Until a participant has identified themselves and accepted the NDA, that link is not hidden from them, not obscured, not greyed out: it is never sent. It is absent from API payloads, from the HTML, from realtime events, from logs and from error messages. No amount of inspecting the page reveals what was never transmitted.

  3. 03

    The single response that returns it is the one that records the acceptance, and only while the session is still open. Once a session is closed, expired or certified, even a participant who did accept receives nothing.

A browser test opens the invitation page and asserts that the link is absent from the document. It is not a comment in the code: it is a lock, and it turns the suite red.

Integrity of the evidence

A certificate is worth exactly as much as the difficulty of changing what it certifies afterwards.

  1. 01

    Canonical form

    The evidence package is serialised in RFC 8785 canonical form. Canonical means deterministic: the same content produces the same bytes, in the same order, on any machine, for as long as the standard exists. Without it, “recompute the hash” would be an invitation rather than a procedure.

  2. 02

    The fingerprint

    SHA-256 over those exact bytes is the package hash. Change one character anywhere in the package and the hash is unrecognisably different.

  3. 03

    The anchor

    The hash is written to Polygon PoS in a public transaction. From that moment the record is outside our reach: we cannot move it, amend it or withdraw it, and neither can anyone else.

  4. 04

    The order, which never varies

    Protected link, identity and agreement, meeting, sealed evidence, verification. No step is skipped, reordered, or run in parallel with the next one: not to make a screen faster, not in testing.

Tamper-evident, with revisions instead of edits

After anchoring the package cannot be edited. A correction issues a new certificate that declares itself a revision of the previous one, and both remain. There are no silent changes, by construction rather than by policy.

Gaps are declared, never hidden

Where the witness could not cover something (a break in the recording, a participant it could not identify) the certificate says so. A declared gap is still evidence; a hidden one would make the whole document worthless.

Every fingerprint can be rebuilt from the data

We keep an audit that rebuilds every certificate's package from the database and recomputes its fingerprint, using the same functions that produced it. It fails the run rather than printing a warning, and a certificate that does not reproduce is reissued as a revision, never edited.

Check it yourself, with tools you already have

Everything above is our account of our own system. This is the section where you stop needing to believe any of it.

01

Recompute the fingerprint

The evidence archive contains evidence.json already in canonical form, so a plain SHA-256 of that file is the package hash. Compare it with the hash printed on the certificate: they match, or the evidence is not what was certified.

02

Read the anchor on the chain

Open the anchoring transaction on a public block explorer and look at the Anchored event. The hash in that event must be the one you just computed. Nothing of ours takes part in this step.

Step one, on your own machine

shasum -a 256 evidence.json

The anchoring contract: Polygon PoS, chain 137

0x5CceC6F5806b7C949eB90D61fB9D13Cb639AC203

Source verified on Polygonscan. It emits Anchored(hash, timestamp, sender) for a dedicated transaction; it also defines BatchAnchored(merkleRoot, …) for a daily batch, not in use yet.

Open the contract on Polygonscan

About the reference verifier

We also maintain a command-line verifier that automates both steps, including Merkle proofs for batched certificates. It is MIT-licensed, and it is not published on a public registry yet, so today there is no way for you to obtain it, and the procedure on this page deliberately uses nothing of ours at all. When the verifier is published this page will say where to get it, and not a day before.

Verifiability does not cost privacy

Public verification reads a dedicated database view that carries no names, no titles and no addresses. A third party can confirm that a package exists and that its fingerprint matches, and learns nothing about who signed what.

How verification works, step by step

The limits, declared

A security page that lists only strengths is an advertisement. These are the boundaries. They are here because the alternative is that you find them out later.

We hold no security certification

We are not SOC 2 Type II. We are not ISO 27001. We are not HIPAA-compliant. No auditor has assessed us against any of those frameworks, and we will not display a badge we do not own. The GDPR is a different kind of thing: a law we are obliged to comply with, not a certificate anyone awards, which is why you will find a data-protection page here and no seal.

The product is not open source; the part that checks us is meant to be

You cannot read our server code, and you should not have to. What has to be trustworthy is the ability to check our output without us, and that ability, today, rests on a published standard and a public blockchain rather than on any promise of ours.

The rate limiter fails open

If the counter itself cannot be reached, the request goes through and the anomaly is logged. It is deliberate: an outage of the counter would otherwise lock out legitimate signers, and the defence that actually matters against code guessing, the three-attempt lockout, does not depend on it.

Deleting your account does not delete issued certificates

It removes your personal data, your drafts and your pending work. Certificates already issued stay verifiable, because they are also the other party's evidence: destroying them at one signatory's request would defeat the reason for having them.

Destroying a recording is not instantaneous everywhere

We delete the files. For up to an hour a storage cache may still serve a copy, and copies already downloaded by people who were entitled to them stay where they are. What is never affected is the proof: the audio hash, the evidence package and the anchor are untouched, so destroying a recording never changes what was certified.

There is no bug bounty

We read every report and fix what is real, but we do not run a paid programme and we would rather you did not expect one.

We would rather you read these here than discover them later. None of them touches the part that matters most, whether a certificate can be verified without us, and that part is above, with the commands to do it.

Reporting a vulnerability

If you have found something, we want to hear it from you before we hear it from anyone else.

  • Write to hub@witup.ai. Tell us what you did, what happened, and how to reproduce it. One line about the impact helps more than a scanner report.
  • Please test against your own account and your own data only. Do not touch other people's protocols, sessions or certificates, and do not run load or denial-of-service tests against the service.
  • We do not publish a response-time commitment, because we are not yet in a position to guarantee one. We would rather say so than promise a number we might miss.
hub@witup.ai

Where to go next