Security
Security
You are about to hand us a real company's customer records.
This is what happens to them.
No seals, no badges, no assurance that we follow industry standard practice. 8 plain answers about where your rows go, who can reach them, and how to get them back out — each one describing what the product does today, and each naming the thing that makes it true.
- 1way in — a file you export and upload
- 2factors on every password sign-in
- 0of your rows in anyone else's benchmark
Check us from your own terminal
These headers are set on every route of this site. They are on the response you are reading right now, so you do not have to take the table's word for it.
| Header | Value | What it stops |
|---|---|---|
Strict-Transport-Security | max-age=63072000; includeSubDomains; preload | Two years. A browser refuses to talk to this domain in plaintext at all. |
Content-Security-Policy | frame-ancestors 'none'; object-src 'none'; base-uri 'self'; form-action 'self' | No page may frame us, load a plugin, rewrite our base URL, or post our forms elsewhere. |
X-Frame-Options | DENY | The same framing block for anything that predates CSP. |
X-Content-Type-Options | nosniff | A response is what it says it is, never what a browser guesses. |
Referrer-Policy | strict-origin-when-cross-origin | Off-site links never carry the path you were on. |
Permissions-Policy | camera=(), microphone=(), geolocation=(), payment=() | Switched off at the browser. Nothing here has any use for them. |
Cross-Origin-Opener-Policy | same-origin-allow-popups | Cross-origin pages cannot reach into this one; sign-in popups still hand their result back. |
curl -sI https://loupenow.com
One thing that policy does not yet do.The script and style directives still permit inline code, because the framework's own hydration and our theme script are inline. That is a real gap, not an oversight: moving to per-request nonces is the next hardening step, and it is written here rather than left for you to discover in a header you were told was strict.
- What comes in
There is nothing to connect.
You export CSV or Excel from the tools you already run, and upload the files. Today that is the only path your records take into Loupe: no login to your CRM, no key to your accounting system, no background job reaching into anything, and nothing pointing the other way — Loupe reads, and has no ability to write back. Live connectors were descoped on purpose. You do not need another integration.
Check
One import endpoint, and no scheduled sync of any kind — wrangler.jsonc declares no cron trigger.- What is actually stored
The columns you mapped. Not your file.
Import shows you a preview and asks you to map your columns onto the fields a detector needs. Only those fields are written — every other column in that spreadsheet is dropped at the door, and the file itself is never kept, only its name. When you re-upload a record type, the newest import is the one your reports read; older ones of that type are kept as a recovery tail and deleted beyond what your plan holds — one import on the free plan, up to 24 on the largest. There is no time-based retention period: an import stays until newer ones replace it, or until you delete the workspace.
Check
There is no file store attached to this product — no bucket, no object storage.- In transit, and at rest
TLS you cannot opt out of.
Every request is served over HTTPS, and the response carries Strict-Transport-Security for two years, so a browser that has visited once will not use plaintext again. Two things that header does NOT do, because the page that prints it should say so: the domain is not on the browser preload list, so the very first visit is not covered, and http:// is currently answered rather than redirected. At rest your rows sit in Cloudflare D1 and are encrypted by the platform. We do not add a second, application-level encryption layer over your records — we would rather write that down than let a padlock icon imply it
Check
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload- One workspace, one wall
Your workspace id is inside the query, never a filter after it.
D1 is SQLite and has no row-level policies, so isolation lives in application code — which means one forgotten clause would be enough. Because that is true, it is pinned by a test that reads every prepared statement in the codebase and fails any that touches a workspace-scoped table without constraining by workspace, then seeds two workspaces into a real database and proves nothing crosses. Nothing is pooled either: you are measured against your own history, never against other companies.
Check
src/lib/tenant-isolation.test.ts — structural sweep, then a behavioural two-tenant run.- Who can open it
Two factors on the door, four roles behind it.
A password never opens an account on its own — every password sign-in also needs a six-digit code emailed to the address on file, the code is spent the moment it is used, and an unverified address gets the verification screen instead of the product. Inside a workspace, four roles decide what each person may do; the table fails closed on a role it does not recognise, and nobody can grant a role above their own. There is no support-impersonation route and no staff view of your findings. Direct database access exists, as it does at every hosted company, and it is limited to named people — we would rather say that than imply a barrier the code does not have.
Check
src/lib/auth/rbac.ts — a capability table, not a role check at each call site.- What gets written down
That something happened. Never what was in it.
Sign-ins, role changes, invitations, imports and deletions are recorded as an event name and an account id. No record contents, no email addresses, and never a whole IP — addresses are truncated to a /24 before they are stored. Rows are pruned after 365 days. One honest limit: that trail is not yet readable from inside the portal, so today you would have to ask us for it.
Check
src/lib/audit/log.ts — the fields it refuses to accept are listed at the top of the file.- What the AI sees
No model sees your records.
Every figure is computed by deterministic software from your own rows — run it twice on the same records and you get the same answer, which is what makes the arithmetic something we can show you. The explanations around each figure are written templates filled with the computed numbers, not model output. Nothing about your business is sent to a model provider, because there is no such call anywhere in the product.
Check
No model provider SDK, endpoint or binding exists in the codebase.- Leaving
One button, one transaction.
Deleting your account runs as a single database transaction: all of it goes, or none of it does. If you were the last person in the workspace it takes the workspace with it — imports, finding state, invitations, audit rows and rate-limit counters. You type the words to confirm, and the screen tells you how many imports are about to disappear before you do.
Check
src/app/api/account/route.ts — the delete is one db.batch(), which D1 commits atomically.
What we do not claim
We hold no SOC 2 report and no ISO certificate, and there is no badge on this page pretending otherwise. A SOC 2 Type II audit is on the roadmap; when it is done you will be able to read the report rather than look at the logo. We also publish no backup or restore commitment, because there is no restore we have rehearsed and can point you at. What is true instead: every row you upload originated in your own system of record, so it can be re-exported and re-uploaded, and your history is never solely in our hands.
Two promises rather than controls. We will tell affected customers within 72 hours of confirming an incident, and we will acknowledge a security report within one business day. Those are commitments about how we will behave — no line of code enforces them, which is exactly why they are written down where you can hold us to them. Reach us through the contact form.
Satisfied? The two ways in are both free and both covered by everything above: an instant estimate from a few facts you already know, or a workspace of your own where one export becomes real findings.
Last updated: September 22, 2026