How responses move
Collecting answers from other people means those answers must pass through a server. They are encrypted in the respondent’s browser first; the server stores ciphertext only, and the survey owner decrypts responses in their own browser through their management link.
How the encryption works
- When you publish, an ECDH P-256 key pair is generated in your browser. The public key goes into the link and the QR code; the private key never leaves your device.
- When someone submits, their browser generates a one-time key pair, derives a session key with your public key (HKDF-SHA256), and encrypts the whole response with AES-GCM-256.
- What reaches the server is
one-time public key + IV + ciphertext. The server cannot decrypt it — not “we haven’t built that yet,” but by design. - You open the results page with your admin link and everything is decrypted, counted and charted inside your own browser.
- Ciphertext is padded to a multiple of 512 bytes, so its length cannot reveal whether someone wrote an essay or skipped every question.
- Keys and tokens live after the
#in the URL. Browsers never send a fragment to the server, and it appears in no access log.
Why asymmetric rather than “one shared key in the link”: the fill-in link is meant to be projected and forwarded, so anyone who has the link would have the key. With a symmetric scheme, any respondent could decrypt everyone else’s answers — and that is exactly the person a survey most needs to protect against.
What end-to-end encryption does not protect you from
This has to be said plainly: the JavaScript that does the encrypting is served by this same server. If the server were compromised, it could ship a version that leaks keys. So this design protects against “the database is stolen, the cloud provider looks, the traffic is intercepted.” It does not protect against “the site itself is compromised.”
- It also cannot stop respondents from knowing what they themselves answered.
- The admin link is the key. Forwarding it to someone hands them the data.
- Lose the private key and the answers are gone for good. There is no recovery path — that is what end-to-end means, not a malfunction.
The mitigations are a strict CSP, third-party libraries served from our own origin only, and open source you can diff. None of that adds up to “perfectly secure,” and we will not write that it does.
What the server actually stores
- Stored: survey title, intro, questions (respondents have to see these anyway), public key, ciphertext, submission time, expiry time.
- Stored as a hash: the admin token and the fill-in passcode. The originals never reach the server.
- Not stored: any readable answer, any respondent account or identity, any raw IP address.
- Rate limiting has to tell requesters apart, so an IP is stored only as a hash salted with the current date. It changes daily, so it cannot accumulate into “what this IP did over a week.”
- The server still sees submission times and counts. That is metadata; encryption cannot hide it, and you should know it exists.
How long answers are kept
- Ordinary surveys: you choose 7 / 30 / 90 days. Collecting only a name, or only a phone number, still falls in this band.
- Once the survey collects enough to pin down a specific person: 7 days maximum, a hard cap — there is no “I understand the risk, keep it for 90 days” escape hatch. Each identity field carries an identifying-power score; scores are deduplicated by kind and added up, and 2 points triggers the cap:
- Name 1 point, phone number 1 point — on its own, neither pins down one student in a class; together they do.
- National ID number: 2 points — it is by definition a unique personal identifier, no less identifying on its own than name + phone, so collecting it alone triggers the cap too.
- Deduplicated by kind: asking for two phone numbers in one survey (the student’s and a parent’s) still counts as one kind.
- Cross the threshold mid-edit and retention tightens automatically, with a message saying so. Removing a field later does not quietly raise it back up.
- Changing retention only affects later submissions. Answers already collected are deleted on the schedule fixed when they arrived — extending retroactively would mean keeping data longer than the promise the respondent saw. Shortening takes effect immediately.
- Expiry is a hard delete: no backup, no recovery. Reads also filter out expired rows, so even if the cleanup job never ran, expired data is still never served.
How far the masking goes
Masking and encryption solve two different problems. Encryption defends against the server side. Masking defends against your own side — the results page projected on a classroom screen, the screenshot forwarded to a group chat, the CSV that gets passed around after export.
- Names show as
Zh*, phone numbers as138****5678, ID numbers as3301**********1234. - Summary and detail views are masked by default. Seeing a full value takes one deliberate click per row. There is no global reveal switch.
- Projection / fullscreen mode has no reveal control at all, and anything printed is always masked.
- CSV export is masked by default. Exporting full values requires a second confirmation, and the filename says it contains complete personal information.
- Masking, the mandatory purpose field, and the “this survey collects information that identifies you” notice are all triggered by a single identity field. The scoring only ever decides retention.
- Identity fields never appear in charts — they are identifiers, not a dimension to count.
If you are the one filling it in
If a survey asks for a name, phone number or ID number, the top of the page says so plainly: “this survey collects information that can identify you.” Each such field also shows the purpose the survey’s author wrote for collecting it. You can decide from there whether to answer.