Security & architecture

Written for the person doing the assessment.

You assess suppliers for a living, so this page is written the way you would want one written: the actual parameters, the threat model, the things it does not protect against, and how to verify every claim yourself without taking our word for any of it. Veld can be run three ways, and the honest answer differs between them — so each is set out separately rather than averaged into one comfortable sentence.

Two of the six sections on this page exist to tell you where the design stops.

The short version

Three ways to run it. None gives us your key.

You choose where the workspace lives. That choice changes our regulatory position, so it is stated here first rather than buried.

No account, no server, nothing transmitted. The portal is a static page served with connect-src 'none' — the browser itself refuses to let it open a connection. On this setting we are not a processor of your clients' data, because we receive none of it.

Role under UK GDPR
Neither processor nor controller. We receive none of it.
Article 28 contract
Not applicable — no processing on your behalf.
Data location
Your device, and nowhere else.
What we hold
Only the contact form and any email you send us.

Sign in and the encrypted workspace is stored by us so any browser on any device can open it. We hold the ciphertext and cannot read it — the key is derived from your password, which never leaves your machine. On this setting we are a processor, with an Article 28 contract.

Role under UK GDPR
Processor, of encrypted data we cannot read.
Article 28 contract
Required. We provide one.
Data location
Your device, plus our UK/EU region.
What we hold
Your email, sign-in times and the encrypted envelope.

An AI assistant you switch on and pay for. Your records stay where they are — the tools that read them run in your browser, and only a minimised, pseudonymous answer leaves. A model provider will become a sub-processor, named in the DPA before it is switched on. Leave it off and nothing changes.

Role under UK GDPR
Processor. Transmission and consultation are processing, even of minimised data.
Article 28 contract
Required, and covers the model provider as a sub-processor.
Data location
Assistant requests are processed in a UK/EU region and not retained.
What we hold
Token counts with timestamps, and which model answered. No content.
The encryption does not change between them. Same AES-GCM 256 envelope, same key derivation, same slot design on all three. Syncing changes where the sealed envelope is kept and the assistant changes what a question can reach — neither changes how anything is sealed, and a workspace moves between the settings freely.
Which one are you on? Nothing is synced unless you sign in — a workspace opened at /portal.html stays on this device and makes no requests at all. The assistant is off until you turn it on, and it is not available at all without an account. If you already have a workspace here and want it synced, the account page offers to move it, and leaves the copy on this device exactly where it is. Moving a synced workspace back to device-only is not built yet; export and re-import does it in the meantime.
01

How the encryption actually works

The workspace is encrypted with a random 256-bit data key. That data key is never derived from your passcode directly — it is generated once, at random, and then wrapped (encrypted) separately by each thing you use to unlock: your passcode, and each passkey you add. Each of those is a slot.

This is why adding a passkey does not re-encrypt your workspace, why you can hold several passkeys at once, and why removing one does not lock out the others.

ComponentWhat is used
Workspace cipherAES-GCM, 256-bit, fresh 96-bit IV per write
Data key256 bits from the platform CSPRNG
Passcode slotPBKDF2-HMAC-SHA-256, 250,000 iterations, 128-bit random salt
Passkey slotWebAuthn PRF extension → HKDF-SHA-256 → AES-GCM wrapping key
Passkey policyResident key required; user verification required
Evidence logSHA-256 hash chain, each entry committing to the previous
StorageBrowser local storage, as an encrypted envelope — and, if you enable syncing, the identical envelope held by us
CryptographyWeb Crypto (crypto.subtle) — the browser's own implementation, no third-party crypto library

All of it runs through the browser's native Web Crypto implementation. We ship no cryptographic code of our own, which is deliberate: hand-rolled crypto is how this goes wrong.

If you turn syncing on

Your password does two jobs, derived separately so that neither can be worked back into the other. This is the part worth checking, because it is what makes "we cannot read it" a structural claim rather than a promise.

DerivedHowWhere it goes
Master keyPBKDF2-HMAC-SHA-256, 600,000 iterationsNever leaves your device
Auth verifierHKDF-SHA-256 of the master keySent to us, stored only as a slow hash
Wrapping keyHKDF-SHA-256 of the master key, different contextNever transmitted, ever

Because both are one-way derivations under different context strings, the verifier we hold reveals nothing about the wrapping key. A total breach of our infrastructure would expose verifier hashes and sealed envelopes, and neither decrypts anything.

What we can see on a synced account. Your email address, when you signed in, the size of your encrypted workspace and how often it changes. Envelope size is a rough proxy for how much work you are carrying. We would rather write that down than let you discover it.
02

Passkeys without a login

A passkey normally proves who you are to a server. Veld never authenticates you to anything — even on a synced account it is your password that does that, and the passkey is doing something else entirely. It uses the other capability WebAuthn offers: the PRF extension, which asks the authenticator to produce a stable high-entropy secret for a fixed salt.

That secret never leaves the authenticator, is not stored anywhere, and cannot be produced without the device plus a user gesture — Face ID, Windows Hello, or a PIN. HKDF turns it into a wrapping key for one more slot.

  • Add several — a laptop, a phone, a hardware key — each works independently.
  • A passkey is bound to the origin it was created on, so one made on veldprivacy.co.uk will not open a copy of the portal hosted elsewhere.
  • The passcode slot cannot be removed. It is the recovery route if every device is lost, which is also why a passkey does not let you forget it.
  • Older browsers and authenticators without PRF support simply do not get the option. The portal works regardless.
03

The assistant, and what it is shown

Veld has an optional AI assistant. It is off unless you turn it on, it is part of a paid plan, and it is the one feature where something leaves your device to a company that is not us. So this section is longer than it would be if we were only selling it to you. What the assistant does is a separate page; this one is about what it is shown.

Not switched on yet, and this page is here first on purpose. The assistant is built, no model provider is engaged, and nobody can use it today. We are publishing how it works before it ships rather than after, so you can assess it while your answer can still change ours. Where this section describes contractual terms, those are requirements we have committed to meet before launch — six of them, each pass or fail — and not terms already in force. When one is signed, the provider is named here and in the privacy notice, and this box goes.

The workspace does not go to the model

The usual way to build this is to put your records in a database the model can query. We cannot: your records are on your machine, sealed with a key we do not hold. That constraint turned out to be the useful part of the design.

Instead the model is given a set of tools it may ask for by name — "what is overdue", "what is the compliance score". Each tool runs in your browser, against your open workspace, and returns a deliberately narrow answer. The model sees the answer. It never sees the workspace, and there is no server-side copy for it to reach, because there is no server-side copy.

Stays on your deviceGoes to us, in transit onlyReaches the model provider
The workspace and every record in itYour questionYour question
Client and data subject namesThe minimised tool resultThe minimised tool result
Notes, correspondence, documentsToken counts, for billing—
Your encryption key——

What a "minimised tool result" actually contains

This is the part to check rather than take on trust. Ask "what is overdue" and the tool does not send the request — it sends this:

A. Okafor — Harborview Housing Association Subject access · due 14 July 2026 · 12 days over
{ "kind": "dsar", "ref": "7f3a91c4", "dueOn": "2026-07-14",
  "daysOver": 12, "clientRef": "c2b80e15" }

No subject name. No client name. No notes, no free text, nothing about what the request is for. ref and clientRef are your workspace's own internal identifiers — random strings that mean nothing outside your browser. The model reasons about "request 7f3a91c4"; your screen shows "A. Okafor — Harborview Housing Association", because the substitution happens back on your device after the answer arrives.

Each tool declares in code exactly which fields it may emit, and our gateway independently rejects any result carrying a field outside that list. A mistake in that code fails your request rather than quietly sending more than it should.

The model cannot change anything

Every tool is read-only. Not "we instruct it not to write" — there is no tool that writes, so there is nothing to instruct. Anything the assistant suggests is a suggestion you act on yourself.

This is also the honest answer to prompt injection. A document in a disclosure bundle could contain text addressed to the model rather than to you, and no one has a complete defence against that. What contains it here is that a successful injection can mislead a human, and cannot act.

What we keep from an assistant session

Nothing, other than counts. Our gateway holds no conversation between one turn and the next — the transcript travels with each request and lives in your browser. We write no prompts, no answers and no tool results to any log, trace or error report; what we record is an account id, token counts, and which model answered.

Content-free logging is a claim with a test behind it. Our logging function takes a fixed list of permitted fields and silently drops everything else, including error objects — which is where request content usually escapes. There is a test that hands it a question, a transcript and a tool result and asserts none of them appear in the output. It runs with every other test we have.

You set how much the scan flags

The document scan that finds personal data in a bundle runs entirely on your device and involves no model at all. You choose how much guesswork it shows you, in Settings: everything including weak guesses, or only the stronger matches. More candidates means more reviewing; fewer means a greater chance something is missed. That is a judgement about your practice and it is yours to make.

One part is not adjustable. An identifier that passes its own check digit — an NHS number against modulus-11, a National Insurance number against the allocation rules, a card number against Luhn — is not a guess, and no setting hides one. A control that let you configure your way out of seeing an NHS number in a disclosure bundle would only ever do harm.

04

What this does not protect against

Any security page that only lists strengths is marketing. Here is the other half.

We cannot recover your data. Not on either setting. The key is derived from your passcode and we never receive it. On a synced account you can reset your password by email — that gets you back into the account, and it decrypts nothing. Only your recovery key or a registered passkey can reopen the workspace itself. Lose the passcode, the recovery key and every passkey, and the records are permanently unreadable by anyone including us.
  • A compromised device compromises the workspace. Malware with access to your browser profile, a malicious extension, or anyone who sits down at your unlocked machine is inside the trust boundary. Local-first moves the risk to your endpoint; it does not remove it.
  • Clearing site data destroys an on-device workspace. "Clear browsing data", a profile reset, or an over-enthusiastic cleanup tool will take it with no warning and no undo. On a synced account it removes only the local cache — sign in again and the workspace comes back.
  • Losing the machine loses an on-device workspace unless you exported a backup. This is the single most likely way to lose your records, and it is the main reason syncing exists.
  • Syncing means we hold a copy. Encrypted, unreadable by us, but held. If your risk appetite says the records must never sit on a third party's infrastructure in any form, stay on the on-device setting — it is not a lesser tier and never will be.
  • Concurrent editing on two machines is not supported. If the same workspace is changed in two places between syncs, Veld detects it and asks you which version to keep. It will not merge them, and it will not guess.
  • The evidence log is tamper-evident, not tamper-proof. The hash chain detects casual editing and accidental corruption. It cannot stop someone with full control of the device from recomputing the whole chain — genuine immutability needs a server-side vault, which by definition this design does not have.
  • The document scan is not exhaustive. It is a reviewer's assistant. A clean scan means nothing matched its rules, never that a bundle contains no personal data. Raising the sensitivity setting in Settings makes that gap wider, which is why the safest setting is the default.
  • The assistant can be confidently wrong. It is told to answer only from what the tools return and to say so when it cannot, and every answer cites the records it used so you can check them. None of that makes it reliable enough to act on unchecked. Treat it as a colleague who is fast, well-read and occasionally mistaken — the citation is there to be followed, not admired.
  • Using the assistant will mean a third party processes a request about your work. Pseudonymous, minimised, and to be covered by a contract requiring zero retention — but a request all the same. If your risk appetite says nothing about a client's matter may reach a model provider under any conditions, leave the assistant off. Everything else in Veld works without it, and always will.
  • Prompt injection has no complete defence. Text in a document you upload can be written to address the model rather than you. We deliver document text as data rather than as instructions, and the model has no tool that writes — so the realistic worst case is that it tells you something wrong, not that it does something wrong. That is containment, not immunity.
  • We can see the rhythm of your work. Assistant usage is metered for billing, so we hold token counts with timestamps per account. That reveals when you are busy and roughly how much, in the same way envelope size does. It reveals nothing about what you asked or what came back.
  • Invite codes are not enforcement. They are signed and verified offline, which stops casual sharing and lets us hand someone a workspace cleanly. The check runs in your browser, so anyone willing to edit the JavaScript can bypass it. A lock on a door, not a vault.
  • Browser storage is not infinite. Very large disclosure bundles belong in your own document management system, with Veld holding the record of the decisions about them.

If your risk appetite requires central custody, escrowed recovery and server-enforced immutability, Veld's architecture is the wrong shape for you, and we would rather say so on this page than three months into a deployment.

05

Verify it yourself, in about five minutes

Every claim above is checkable from your own browser. Please check it.

  1. Watch the network, on-device. Open the portal without signing in, press F12, go to the Network tab and use the product — add a client, log a request, generate a document. After the initial page and assets load you will see no further requests at all. Nothing is sent because nothing can be.
  2. Watch the network, synced. Sign in and do the same. You will see requests, and you should open one and read the body. It is base64 ciphertext. Search it for a client name you just typed — it is not there, and cannot be.
  3. Read the policy header. Inspect the response headers for portal.html. On the on-device build the Content-Security-Policy sets connect-src 'none', which instructs the browser to block every fetch, XHR and WebSocket the page might attempt. With syncing available it reads connect-src 'self' — the page may talk to us and to nobody else, still enforced by the browser rather than promised by us.
  4. Look at what is stored. Application → Local Storage. The workspace is there as an encrypted envelope. Lock the workspace and try to read it — it is ciphertext.
  5. Read the source. The portal is unminified, unbundled JavaScript with the reasoning written into the comments. assets/store.js holds the encryption, assets/passkey.js the PRF unlock. No build step stands between what we wrote and what you run.
  6. Watch what the assistant sends. With the assistant on, open the Network tab and ask it something about your caseload. Open the request to /api/ai/tool-result and read the body — it is not encrypted, because it has to be readable by the model, which is exactly why it is worth reading. Search it for a client name, a data subject's name, or anything you typed into a notes field. None of it is there. What is there is a list of record references, dates and day counts.
  7. Pull the plug. Load the portal, go offline, and keep working. Everything continues, because there was never anything on the other end of the connection. The assistant is the one part that stops, and it says so rather than answering from memory.
06

For your supplier assessment

The answers you will need, in the form the form asks for them.

QuestionOn this deviceSyncedAssistant on
Role under UK GDPRNeither processor nor controller. We receive none of it.Processor, of encrypted data we cannot read.Processor. Transmission and consultation are processing, even of minimised data.
Article 28 contractNot applicable — no processing on your behalf.Required. We provide one.Required, and covers the model provider as a sub-processor.
Data locationYour device, and nowhere else.Your device, plus our UK/EU region.The above. Assistant requests are processed in a UK/EU region and not retained.
International transfersNone.None — storage stays in region.None intended. Region pinning is a condition we require of a provider before launch, not a setting we can change later.
Sub-processorsNetlify, static hosting and the contact form only.Netlify, plus our hosting and database provider. Named in the DPA.The above, plus one model provider once engaged. To be named in the DPA before launch, with a change notified to you in advance.
Encryption at restAES-GCM 256, key derived on your device from your passcode, never transmitted. Identical on every setting — the assistant does not change how anything is stored.
Encryption in transitWorkspace data is never in transit.HTTPS with HSTS. The payload is already ciphertext before it is sent.HTTPS with HSTS. Assistant traffic is not ciphertext — it is minimised, pseudonymous data the model has to be able to read.
Access controls at supplierNo store to access.Staff can reach ciphertext and account metadata. No one can decrypt it — we hold no key.The above. Assistant content is not stored, so there is nothing for staff to reach after a request completes.
Breach notificationWe hold no client personal data.A breach exposes ciphertext and verifier hashes. We would tell you, and it would not expose readable client data.A breach of the gateway exposes requests in flight, not a corpus — nothing is retained to breach. We would tell you.
Retention & deletionDelete it and it is gone; we hold no copy.Delete your account and the envelope is deleted with it.Nothing to delete: no prompts, answers or tool results are stored. Usage counts go with the account.
Data portabilityFull export in open JSON, plus PDF, Word and Excel, on every plan.
What we do holdOnly the contact form and any email you send us.The above, plus your email, sign-in times and the encrypted envelope. See the privacy notice.The above, plus token counts with timestamps, and which model answered. No content. See the privacy notice.
One caveat, stated plainly. The contact form on this site does post to our host, and email you send us lands in an ordinary mailbox. That is us processing your data as a prospect or customer — never your clients'. The portal remains entirely separate and entirely silent.

Questions this page didn’t answer?

Send them. Technical questions get technical answers from the person who wrote the code.