SECURITY

Specifics, not reassurance.

Responses are often the most sensitive thing a team collects, so this page describes what CanvasFlow actually does to protect them — with real numbers where there are numbers, and a list of what we haven’t built yet.

Last updated 31 July 2026

Who can read a response

The question that matters most, so it gets checked in three independent places rather than once at the door.

The session, on every request
Every authenticated endpoint revalidates the session server-side before it runs. There is no trusted client state — a request with a stale or forged cookie is rejected at the boundary, not deeper in.
The role, per form
Before any read or write touching a specific form, we check that you own it or hold an explicit collaborator role on it. Viewer, editor, and owner are distinct, and the check runs on the request path — knowing a form's ID is not access to it.
What the redirect is not
Visiting /dashboard while signed out bounces you to sign-up. That redirect only looks for a cookie's presence, so treat it as navigation convenience rather than a security control. The enforcement that matters is the three checks above, and they run whether or not the redirect did.

Accounts and sessions

Passwords
Stored only as a hash, produced by our authentication library. We cannot read your password, and a database dump would not reveal it. If you sign in with Google or GitHub instead, we never see a password at all.
Session cookies
Marked Secure, so a browser will only send them over HTTPS. They are set by the API rather than readable application state.
Cross-origin requests
The API answers credentialed requests only from an explicit allowlist of origins. A request from any other site is refused before it reaches a handler, which is what stops another page from making authenticated calls with your cookie.
Session records
Each session stores its own expiry, plus the IP address and browser it was created from — the detail you would need to recognise a session that isn't yours. Expired sessions stop working on their own.
Browser-level protections
Every page is served with a Content-Security-Policy restricting which origins scripts, images and network calls may come from, and refusing to be embedded in a frame anywhere. Alongside it: HSTS in production so browsers refuse to downgrade to HTTP, nosniff, a referrer policy that keeps form URLs out of third-party logs, and a permissions policy denying camera, microphone, geolocation and payment access outright.

Ceilings on abuse

A public form has to accept requests from strangers, so the limits are what keep that from becoming a liability.

Public writes
Submitting a form, recording an answer, and sending a bug report are capped at 60 requests per minute per IP address. These are the endpoints reachable without an account, so they get the tightest ceiling.
Signed-in requests
Capped at 300 requests per minute, counted per session rather than per IP so that several people behind one office connection don't throttle each other. IPv6 addresses are grouped by prefix so an attacker can't sidestep the count by walking through addresses.
Request size
Request bodies are capped at 200 KB. A form submission is small; anything far larger is either a mistake or an attempt to exhaust memory.
Report flooding
The feedback endpoint is deliberately open, so it carries two independent guards: the per-IP limit above, and a per-reporter cap. One attacker rotating IP addresses is caught by the second; anonymous floods are caught by the first.

Keeping the data honest

Most of these are correctness guarantees. They are on this page because a response set you can’t trust is its own kind of failure.

Validated at the edge
Every endpoint declares a schema for what it accepts and what it returns, and both are validated. Input that doesn't match the shape is rejected before any handler logic runs, so malformed data can't reach the database.
Parameterised queries
Database access goes through a query builder that parameterises values. Answers are never concatenated into SQL, which is what makes a form field a poor place to attempt injection.
Duplicate submissions
Each submit carries an idempotency key, and forms enforce one response per visitor. Both are backed by unique constraints in the database rather than an application check alone, so two requests racing each other collapse into one row instead of both slipping through.
Concurrent edits
Forms carry a version counter, compared and set on every update. If two people edit the same form at once, the second write is refused with a conflict rather than silently overwriting the first person's work.
Deletion that deletes
Removal cascades at the database level: deleting a form removes its questions, submissions, and partial answers; deleting an account removes the forms beneath it. There is no detached copy left behind to leak later.

The strongest control is not collecting it

  • 01No IP address is recorded for people answering a form. Data we never collect cannot be exposed.
  • 02No cookies are set on respondents, and no third-party analytics or advertising scripts load on public form pages.
  • 03The per-form duplicate-submission identifier lives in the respondent's own browser storage and is scoped to a single form, so it cannot correlate anyone across forms or across sites.
  • 04OAuth providers are only registered when both their client ID and secret are present, so a half-configured provider fails closed instead of exposing a broken sign-in path.

The full inventory of what is and isn’t stored is in the privacy policy.

What we haven’t done

A security page listing only strengths tells you nothing, because every product has gaps. Here are ours, so you can decide with the real picture.

Strict CSP

Our Content-Security-Policy allows inline scripts, because the framework injects its startup script inline. That means the policy restricts where code can be loaded from but won't stop script injected into the page itself. Tightening it to a per-request nonce is planned.

Access logging

We don't currently keep a per-form audit trail of which collaborator viewed which responses. Add collaborators deliberately, since removing someone doesn't tell you what they already read.

External audit

CanvasFlow has not been independently penetration-tested and holds no compliance certification. We would rather say that than imply otherwise.

If one of these is a blocker for the data you had in mind, it’s better to know now than after you’ve collected it.

Found something?

We’d genuinely rather hear it from you than find out the hard way. Report what you found, how to reproduce it, and what you think the impact is. We’ll confirm we received it, keep you posted while we fix it, and credit you if you’d like to be credited.

Please test only against your own account and your own forms, don’t run automated scans or load tests against the service, and don’t access, alter, or retain anyone else’s responses. Report it and stop there — that’s enough to prove the point, and we won’t pursue researchers who stay within those lines.

Use the feedback option inside the app to reach us — it routes straight to us. Mark it as a bug and mention it’s security-related so we prioritise it.