Trust
Security at Punctilio
Punctilio holds the documents your clients least want leaked: deal files, agreements out for signature, financial records collected for a closing. This page describes the controls that protect them, exactly as built. Where a protection has limits, we say so plainly.
Every control on this page is included in the one plan, $29.99/mo flat after a 7-day trial. Security is never an upsell.
Access control
Member authentication. Sign-in is handled by Supabase Auth (email and password, with email-based password reset). Punctilio stores no credentials of its own. Sign-in and sign-up attempts are rate limited server-side, per IP, before authentication is ever attempted, enforced by a dedicated edge function.
Row level security on every table. Postgres row level security is enabled on all 59 application tables, covering rooms, files, share links, viewer sessions, events, signing envelopes, audit records, and billing. A guardrail migration sweeps the schema and re-enables RLS so no table can silently drift out of policy.
Storage never trusts the client. File objects are protected by storage RLS policies that derive organization membership from each object’s room path. Anonymous viewers can never read storage directly; every byte they receive is authorized first.
Bearer secrets stay server-side. Column-level grants deliberately exclude the token columns on share links and signer records, so link tokens are unreadable through the client data API. Share-link URLs are resolved by a security-definer RPC on the server, so raw tokens never reach member browsers.
Analytics can’t be forged. Viewer sessions and analytics events can only be created or modified by service-role edge functions. Anonymous and authenticated database roles have their direct write privileges revoked.
Viewer protections
Server-side gates on every link. Each share link can require email capture, email allow or deny lists (exact address or domain, including subdomains), a password, click-through NDA acceptance, and an expiry date, and can be scoped to specific files or folders. Every gate is evaluated on the server, not in the browser.
Verified email, done properly. Viewer email verification uses one-time codes generated with a cryptographically secure random source. Codes are stored only as salted hashes bound to the session, expire quickly, and allow a small number of attempts. A verified link and email pair is remembered for 30 days.
Hashed link passwords. Share-link passwords are hashed server-side with a strong, salted key-derivation function. A migration proactively upgraded every legacy hash.
Instant revocation via access epochs. Every viewer session carries a monotonic access epoch. Any settings change, password change, token rotation, or change to a link’s file scope bumps the epoch by database trigger, which forces every existing session back through the full gate chain before any further content is served. Tighten a link and it tightens for everyone, immediately.
One authorization path by construction. All public viewer traffic is served by exactly two edge functions that share a single session-guard module, so the decision to show a file list and the decision to serve file bytes are made by the same code.
Session hygiene. Viewer session tokens expire, and each session carries coarse anti-hijack fingerprints derived from one-way hashes of network and client signals. Raw IP addresses are not stored on the session record.
Dynamic watermarking. Optional watermarks tile viewer-identifying text (by default the viewer’s email plus date and time, with configurable opacity, rotation, and size) across every document page, image, and video in the viewer. To be clear about scope: the watermark is a viewer overlay. If you enable downloads on a link, the downloaded file is the original, unmodified bytes.
Screenshot and print deterrence, honestly framed. An optional setting blocks the context menu and the print and save shortcuts, and records PrintScreen and print attempts as security events in the audit trail. No software can stop OS-level screen capture, so we build this as deterrence plus tracking, not prevention, and we tell you when it triggers.
Downloads enforced at the server. When downloads are off, the file-serving edge function returns 403 for download requests. It is a server-side rule, not a hidden button.
No lingering copies. Viewer file responses are marked Cache-Control: private, no-store. Video streams through signed URLs that expire after 15 minutes; all other file bytes are proxied through the edge function rather than exposed as storage URLs.
Visitor upload scanning
When outside visitors send you files through a collect link, those files are staged in a quarantine bucket where staged bytes are never served to any visitor. No upload can be approved or filed into a room until a malware scan returns a terminal clean verdict. Infected files stay quarantined with a 30-day expiry, and the workspace owner is alerted.
Scanning runs on a private ClamAV service. The scan endpoint must match an explicit host allowlist and present an API key, and the scan worker fails closed if the scanner is misconfigured or unreachable: no verdict, no approval.
One scope note: this pipeline covers visitor submissions through collect links. Files uploaded directly by authenticated workspace members do not pass through the scanner.
E-sign integrity
Signing links are protected credentials. Signing links are bearer tokens stored only as SHA-256 hashes and expire after 30 days. Senders can additionally require per-signer access codes, which are hashed, expiring, and attempt-limited.
A complete chain of custody. Every signing ceremony produces an append-style audit trail recording each action with actor, IP address, and user agent, plus downloadable audit-certificate PDF and CSV artifacts whose SHA-256 hashes are stored alongside them. The trail is tamper-evident chain of custody, written only by service-role functions and protected by database triggers.
Signed values are locked. Once a signer completes signing, database triggers block any modification of their field values, and envelope status transitions are constrained by dedicated migrations.
Sealed documents, described accurately. Completed documents are sealed into flattened PDFs. The disclosure text embedded in each sealed PDF states that it was sealed with typed-text rendering and does not contain a cryptographic digital signature. The audit trail, not an embedded certificate, is what establishes who signed what and when.
Infrastructure and encryption
Encryption in transit. All traffic is HTTPS. Production sends Strict-Transport-Security with a 2-year max-age, includeSubDomains, and preload, plus a CSP upgrade-insecure-requests directive. TLS termination is provided by Vercel and Supabase as platform infrastructure.
Encryption at rest. At-rest encryption for the database and file storage is provided by Supabase’s managed platform, and by Vercel for build and runtime assets. It is a platform capability rather than application code; see those providers’ documentation for algorithm specifics.
Optional client-side encrypted rooms. For your most sensitive rooms, file bytes are encrypted in the browser with AES-256-GCM using a unique key per file. File keys are wrapped with a per-room key using AES key wrapping, viewers receive the room key only through the URL fragment (which is never sent to the server in request paths), and the viewer-side key import is non-extractable. To be precise about the design: this is real client-side encryption, not a zero-knowledge system. The room key is stored server-side in a dedicated secrets table with no direct read path; access goes exclusively through a security-definer RPC that re-runs the canonical room-authorization check. The URL-fragment key is the one key that stays off the server entirely.
Production and preview are isolated. Separate Supabase projects back production and preview. Middleware refuses to serve the production host from non-production deployments, and preview deployments are marked noindex.
Secrets stay out of the repo and the browser. Environment files are gitignored, only publishable keys and the Turnstile site key are exposed client-side, and server secrets live in Vercel environment variables and Supabase edge function secrets. A repository script audits that required production secrets are present and that production and preview use separate Supabase projects.
Application hardening
Security headers. Production responses carry a Content-Security-Policy that defaults to same-origin, denies framing entirely, blocks plugins, restricts form targets to the app, limits scripts to the app plus Cloudflare Turnstile, and limits connections to Supabase, Vercel, Turnstile, and Punctilio’s own domains. Alongside it: X-Frame-Options DENY, X-Content-Type-Options nosniff, Referrer-Policy strict-origin-when-cross-origin, a Permissions-Policy denying camera, microphone, geolocation, payment, and USB, Cross-Origin-Opener-Policy same-origin, and Cross-Origin-Resource-Policy same-origin.
Bot defense in depth. Vercel BotID runs deep-analysis bot checks in middleware on login, password reset, invite, viewer, and signing routes. Auth-critical routes fail closed when verification errors; blocked bots receive 403. Cloudflare Turnstile adds step-up challenges on risky public viewer actions, with tokens verified server-side against Cloudflare’s API using a server-held secret, and verification fails closed if that secret is unconfigured.
Rate limiting everywhere it matters. Public endpoints (viewer bootstrap, email capture, one-time-code request and confirm, password verification, access requests, invite redemption, preview enqueue, and auth attempts) are rate limited with database-backed fixed windows, keyed by hashed coarse IP fingerprints rather than raw IPs.
Verified webhooks. Stripe webhooks are signature-verified with the webhook signing secret before any event is processed, and email webhooks carry their own configured secrets. Unsigned or invalid payloads are rejected.
Privileged code with explicit checks. Privileged edge functions run with the service-role key and rely on explicit in-code authorization checks, and most JWT-backed mutating functions additionally require a CSRF token header.
No redirected side channels. The document-conversion service and the malware scanner may only be called on explicitly allowlisted hosts with credentials, so conversion and scan traffic cannot be redirected to arbitrary hosts.
Everything is on the record. Viewer activity, including link opens, email capture, NDA acceptance, file opens, page views, downloads, dwell time, and security events, is captured in an audit and analytics stream writable only through service-role edge functions.
Responsible disclosure
Punctilio publishes a vulnerability disclosure policy in the SECURITY.md file of its source repository. If you believe you’ve found a vulnerability, report it privately through GitHub private vulnerability reporting or by contacting the maintainers directly, and never post secrets, bearer links, or customer data in public threads. Fixes target the latest code on main and the current production deployment.
Subprocessors
Punctilio runs on a small set of infrastructure providers:
| Subprocessor | Purpose |
|---|---|
| Vercel | Application hosting, edge middleware, BotID bot detection, web analytics |
| Supabase | Authentication, Postgres database, file storage, edge functions |
| Stripe | Billing and payments; webhook-driven subscription state |
| Resend | Transactional email: viewer one-time codes, invites, signing notifications, reminders |
| Railway | Hosts the private malware-scanning and document-conversion services |
| Cloudflare | Turnstile bot-challenge verification |
Punctilio is built by PumpKin Baby Inc. Questions about anything on this page are welcome through the disclosure channels above.