Skip to content
docivault

Security

Security — how a document is actually protected

How does docivault keep documents secure?

Files sit in private object storage with no public URL, reached only through short-lived signed links issued per request. Postgres row-level security decides who can read a row, sign-in supports passkeys and authenticator-app two-factor, and idle sessions end themselves. Below is how a document request actually moves, and nothing we cannot back.

The problem

Most document sharing has no access control after you hit send

An email attachment can be forwarded by anyone who receives it. A 'share this link' from a general-purpose drive often means anyone holding the link, forever, with no record of who actually opened it. docivault is built the other way: access is checked on every request, not once at send time.

  • A link outlives the reason you sent it

    A public share link usually keeps working long after the exchange it was made for is over, because nothing expires it and nothing checks who is using it.

  • Forwarding is invisible

    Once a file or a link leaves your hands over email or chat, there is no way to know who it ends up with next, or to cut off access after the fact.

  • Access control lives in application code, not the database

    If the only thing standing between a request and a private file is a check inside a page or an API route, one missed check in one code path is the whole gap.

Storage

A file never has a public URL, so a leaked link is not enough

This is the path a file request takes through the platform today — the storage, signing and access-control layers are all live and are what document delivery will run on. Documents themselves are next up; the mechanism below is not waiting on them.

How a document request is protected, from browser to storage and backA browser asks the app, running on Vercel, for a document. The app checks Postgres, whose contents are encrypted at rest, and a row-level security policy in the database — not application code — decides whether this user may read that row. If allowed, the app asks Cloudflare R2, the private file storage, for a signed URL. R2 has no public URL for any file, and its contents are encrypted at rest. R2 returns a signed URL that expires within minutes. The browser then downloads the file directly using that link, over an encrypted connection, and the link stops working once it expires.requests the documentin transit2. Postgres checks: may this user read this row?3. allowed → the app asks Cloudflare R2 for a signed URL4. a short-lived signed URL is returnedin transit1. Browser

A request for a file reaches the app.

2. App (Vercel)

Receives the request over an encrypted connection.

Postgres + row-level security

The database decides who may read this row — not the app code.

encrypted at rest
Private R2 bucket

No public URL exists for this file, or any file, ever.

encrypted at rest
5. Browser

Downloads the file directly, using a link that stops working once it expires.

  1. 1. Your browser asks the app for the document

    The request reaches the app, running on Vercel, over an encrypted connection.

  2. 2. Postgres decides whether you may read that row

    A row-level security policy in the database checks your identity and your role against the document, before any file is touched. The app cannot bypass this by forgetting a check, because the check is not the app’s to forget.

  3. 3. If allowed, the app asks Cloudflare R2 for a signed URL

    The file itself lives in a private R2 bucket. There is no public URL for it, or for any file in the bucket — that is not a setting we turned off, it does not exist.

  4. 4. R2 returns a link that expires in minutes

    The signed URL is scoped to that one file and that one request, and it stops resolving once it expires. Files are encrypted at rest in the bucket.

  5. 5. Your browser downloads the file directly, over an encrypted connection

    The bytes never need a second, longer-lived link to exist — the whole exchange is re-checked the next time you ask.

Access control

What you see depends on who you are, enforced in the database

The same workspace looks different depending on your role, and that difference is not just a hidden menu item — it is what Postgres will and will not return.

  • Admins see the whole workspace: every member, every contact, every document.
  • Members see only the contacts they are assigned to, and only the documents that belong to those contacts.
  • External contacts — the people you exchange documents with — see only their own space, never anyone else's.

Sign-in

Getting in is harder to get wrong than typing a password

  • Two-factor authentication

    Available now

    Time-based codes from any authenticator app, with backup codes. An admin can require it for the whole workspace.

  • Passkeys and biometric sign-in

    Available now

    Sign in with Face ID, Touch ID or a hardware key instead of typing a password.

  • Idle sign-out

    Available now

    An unattended session warns, then signs itself out, and every open tab follows.

  • Sign-in abuse protection

    In progress

    Rate limiting is live on every sign-in route. Owner notification on repeated failures is being added.

Not built yet

A complete audit trail is not here today

This page only states what is running in production. A contact opening a document in their portal is recorded, and so is workspace activity — but a colleague opening a file, or a share link being opened, is not. One complete record of every view and every download is still to be built.

Looking for the compliance position, or where your data is actually stored?

Read the compliance roadmap

For the full list of vendors that touch your data and the region each one runs in, see subprocessors.

Report a vulnerability

Found a way in? Tell us before someone else does

There is no bug bounty program and we will not promise a response time we have not earned. What we can tell you is that a report to this address is read and answered by a person, not a ticketing queue.

Email security@docivault.com directly, with as much detail as you can about how to reproduce it. Please give us a reasonable window to fix an issue before disclosing it publicly.

Questions

Security questions, answered directly

Does docivault have an "anyone with the link" sharing mode?

No, and it will not — this is a design decision, not a gap. Every document request is checked against Postgres row-level security, so a link on its own is never enough to read a file. Sharing is always with a named person who signs in.

What happens if a signed URL leaks?

It stops working. Signed URLs are generated per request and expire within minutes, so a link copied into the wrong chat or logged somewhere it should not be is only useful for a short window, and only for the one file it was issued for.

What does "row-level security" mean in practice?

It means the database itself, not the application code, decides whether a query is allowed to return a given row. A bug in a page or an API route cannot leak a document that Postgres has already decided this user may not read.

Is there a record of who opened a document?

Partly, and not enough to call it an audit trail. When one of your contacts opens or downloads a document through their portal, that is recorded against the document. A workspace activity feed records actions across the workspace. What does not exist is a complete log of every view and download by every kind of user — a colleague opening a file, or someone opening a share link, is not recorded that way. If your requirement is a provable access record for every reader, docivault does not meet it today.

How do I report a security issue?

Email security@docivault.com directly. There is no form and no bounty program — a plain report to a real inbox that a person reads.

See the access model for yourself

Create a workspace and add a contact. You will see exactly what they see, because that is what the database, not a settings toggle, decides.

  • No credit card
  • Free plan, no expiry
  • The people you exchange with are free