letssign.now docs

Security

How letssign.now protects documents at rest, in transit, and over their lifetime — with the trade-offs called out.

This page lays out what we do, what we don't yet do, and where the trade-offs sit. If you're a buyer with a security questionnaire to fill in, every answer below is verbatim what our infrastructure does today.

Encryption at rest — envelope encryption

Every document we write server-side is encrypted with a two-layer AES-256-GCM envelope on top of Vercel Blob's vendor-managed AES-256 encryption.

What's encrypted, what isn't: The signing flow's server-generated artefacts — multipart uploads through POST /v1/signing-requests, the PAdES-sealed signed PDF, the DOCX→PDF conversion output — are envelope-encrypted. Browser-direct uploads through the in-app dashboard (where bytes go straight from your browser to Blob without our server seeing them) keep Vercel's vendor encryption only. We're transparent about this because the trade-off is real: the only way to encrypt those would be to add a server-side roundtrip on every upload, doubling latency.

How envelope encryption works

master key (in our Vercel env, never on Blob)
  └── wraps a fresh 32-byte data key per document
        └── encrypts the file bytes (AES-256-GCM)

stored alongside on the documents row:
  encryption_alg = "aes-256-gcm/v1"
  encryption_keymeta = {
    wrapped_data_key, wrap_iv, wrap_tag,
    file_iv, file_tag
  }

Both layers carry GCM auth tags — a single tampered byte fails the tag check on the way back out, loud and immediate. The blob on Blob reads as application/octet-stream (ciphertext); the metadata travels in Postgres.

What the master key does + doesn't see

  • The master key lives in our deployment environment variables. It never touches Blob, never appears in logs, never leaves the serverless runtime.
  • We can decrypt documents when we serve them back to authorised callers — that's how the audit trail and signing flow work.
  • Customer-managed keys (BYOK) are on the roadmap. When that ships, customers in regulated industries can hold the key themselves and we'll decrypt only when they hand it over per request. Today we hold the key.

Key loss

A practical consequence of holding any encryption key: if the master key is permanently lost, encrypted documents become permanently unrecoverable. The codebase rejects unknown algorithm versions explicitly so a future-version cipher can't silently mis-decrypt v1 blobs, which means recovery requires the matching key.

For your side: if you delete a workspace, withdraw payment access, or otherwise lose your account, you are responsible for retrieving any documents you need before doing so. We can't decrypt blobs for an account we can no longer authorise — and even if we could, we wouldn't (privacy + access-control).

Signature evidence

Independent of at-rest encryption, every signed document carries:

  • PAdES-B-T signature — embedded directly in the PDF, verifiable by any compliant reader (Adobe Reader, Foxit, the EU's signature-validator).
  • RFC 3161 trusted timestamp from an independent TSA — proves when the document was signed, by a third-party clock.
  • Cumulative audit trail PDF — tamper-evident, downloadable separately, with cert serial + masked IPs + UA strings.

Together, this stack is admissible as legal evidence under EU eIDAS and Swiss ZertES. Conservative claim, accurate framing.

Encryption in transit

  • TLS 1.2+ on every endpoint (api.letssign.now, signing pages, branded subdomains).
  • HSTS with max-age=63072000; includeSubDomains; preload on every response. The domain is on Chrome's HSTS preload list.
  • All webhook deliveries go out over HTTPS; receivers that don't serve TLS get a delivery error.

Webhook signing

Outbound events are signed with HMAC-SHA256 (X-LetsSign-Signature: t=<unix>,v1=<hmac>) using a per-webhook secret. Per-document callbacks (registered via callback_url on a v1 request) get their own per-document secret returned once at creation. Receivers are expected to verify the signature before trusting the body — see Webhooks → Verifying the signature.

Data residency

Each workspace pins to one Blob region — EU (default), Switzerland, UK, or US. Switching after the fact moves NEW uploads only; historical documents stay where they were created (avoids cross-region copy costs + the multi-minute latency that move would imply).

Not-yet shipped

Honest list:

  • Customer-managed keys (BYOK). Workspace-level, with the master key held in the customer's KMS. Schema sketch is in place (workspaces.encryption_key_id); the integration with AWS KMS / GCP KMS / Azure Key Vault is not.
  • Master-key rotation. When v2 ships, a backfill cron will rewrap v1 data keys without re-encrypting the files themselves. Until then, rotation is a planned outage.
  • HSM-backed master key. Today the master is an env-var; an HSM would prevent operator extraction. Worth doing for buyers in finance / pharma / healthcare.
  • Encryption of browser-direct uploads. See the callout at the top — the in-app dashboard upload path bypasses our server. A client-side encryption shim would close that gap; not currently shipped.
  • FIPS 140-2 / 140-3 validation. AES-256-GCM is current best-practice but the implementation lives in Node's crypto module, not a validated cryptographic module.

Reporting a vulnerability

Email security@letssign.now. PGP key on request. We acknowledge within 24 h, ship a fix or mitigation within 7 days for high-severity issues, 30 days for medium. We don't pay bounties yet but will publicly credit you.