Skip to content

Security

Your PDFs stay in your browser — with two exceptions we'll name.

The PDF bytes are parsed, edited, redacted, and signed inside the browser tab you have open. For every tool but two, the server never sees them — and most of the time it isn't even contacted. The exceptions are the flows that exist to hand a document to someone else: Send for signature and the QR phone handoff. Both upload the document, encrypted in your browser, and we store the ciphertext for 7 days. The table below and the section after it spell out exactly what that means, including where the key goes.

This page documents what runs where, what we encrypt, and exactly how you can verify our claims yourself.

What stays local, what touches our server

The honest, two-column truth. The left column is the work you do on your PDF; the right column is what (if anything) leaves your browser to do it.

What you're doingWhat leaves your browser
Filling a form (manually)Nothing. PDF + your typed values stay in-page.
Signing yourself (one-click)Nothing. Signature image is rendered + flattened into the PDF locally. (Finishing on your phone instead is the QR handoff — its own row below.)
Signing on your phone (the QR handoff)The whole document, encrypted in this tab, is uploaded so your phone can fetch it — same 7-day storage as Send for signature. The key travels to your phone in the QR code, off the network, but it is also sent to us with the upload. We never save it and never email it on this path.
Redacting (manual selection)Nothing. We draw black rects locally and re-save.
Merging, splitting, rotating pagesNothing. pdf-lib in your browser.
OCR (making a scan searchable)Nothing. Tesseract.js runs in your browser; the trained language data is a static asset served once from our CDN, not your scan.
Auto-fill from profileForm-field names + types (not PDF bytes) and your saved profile data, to compute the mapping. We do not log the request or response. Sent to a third-party AI provider. e.g. a field named "first_name" of type text, and the matching name from your profile
Scan for PIIExtracted text spans (not PDF bytes) from the redact tool, to find the PII worth redacting; the coordinates stay client-side. Sent to a third-party AI provider. e.g. "123-45-6789", which comes back tagged as an SSN to black out
AI clause reviewThe extracted document text you submit for review — no coordinates, no PDF bytes. Sent to a third-party AI provider. e.g. "This agreement renews for successive 12-month terms…", which comes back flagged as an auto-renewal
Draft a documentThe text you type (the parties, terms, and details you describe), to generate the draft. No PDF bytes are sent. Sent to a third-party AI provider. e.g. "Mutual NDA between Acme Inc and a contractor, 2-year term"
Field helpA form field's name and type, plus nearby labels, for a one-line explanation of what the field wants. Sent to a third-party AI provider. e.g. "spouse_ssn" / text, plus the nearby label "Part I — Taxpayer Information"
Library cited answers (Max)Your question plus the excerpts that matched it — each excerpt's extracted text and file name. The file name is sent so the answer can cite its source; the embeddings and search index themselves stay in your browser. Sent to a third-party AI provider. e.g. "What's our notice period?", the paragraph that matched it, and the file name "acme-msa.pdf" so the answer can cite it
Ask AI about this documentThe open document's extracted text — tagged page by page, so an answer can cite the page it came from — plus your question, when you use Summarize, Key points, Ask, Explain, or Translate on it. When you summarize a document's reviewer notes instead, the notes and their author/page labels are what get sent. No PDF bytes are sent. Sent to a third-party AI provider. e.g. "Who are the parties?", plus the document's text carrying the "[[page 3]]" tag the answer cites back
PDF agentThe extracted document text and your instruction, plus the page geometry, the form-field names (the names only — never the values you entered), the positions of text on the page, and the shape of any table your instruction refers to, all derived in your browser. The agent uses them to plan the edits; the plan then runs on your device and no PDF bytes are sent. Sent to a third-party AI provider. e.g. "Black out the account number on page 1", plus that page's text and where each line sits on it
Translate documentThe document's extracted text and the language you picked — page by page, or line by line when you keep the original layout. The translated PDF is built in your browser from the returned text; no PDF bytes are sent. Sent to a third-party AI provider. e.g. "Rechnungsdatum: 14.03.2026", with the target language "Spanish"
Local-AI mode (the account setting)Whatever the AI feature you used would normally send — it still comes to our server. What changes is which model answers it, not whether a request happens: where the deployment has a self-hosted model wired in (OLLAMA_BASE_URL), we hand the text to that instead of the third-party provider. That model is the operator's, not one on your machine. Where the deployment has no such model, the setting does not quietly send you to the cloud instead — the features that check it decline, so its whole effect there is to switch those features off. It is also not all-or-nothing — the PDF agent and Translate refuse to run rather than fall back to the cloud (their text never leaves the page), while features that don't check the setting still reach the provider exactly as above. We deliberately don't print the per-feature list: it has been written down wrong twice. Watch DevTools → Network, which is authoritative for the build you are actually using.
Counsel Mode (in the sign + draft tools)Nothing — on the flows that offer it, the request never reaches us at all. Your browser posts straight to an Ollama you run (localhost by default), so neither our server nor a third-party provider is in the loop. Toggled per-device inside those tools — not in account settings, and not the same thing as the row above, despite both having been labelled "Local AI mode" in the UI. In the sign tool it is a Max feature and adds a ceremony on top: that window is wrapped in a network sentinel asserting no outbound call happened, and the signed PDF carries a WebAuthn-signed attestation recording that. It does not cover every AI feature in the product.
Send for signatureCiphertext only — we store no key. But by default the key rides in the link, and because we email that link for you it does pass through our server and our email provider on the way to your recipient. See below.
Cloud import (optional — Drive / Dropbox)Your OAuth / authentication interaction + a file listing go to the provider you pick; the file is then fetched directly from the provider into your browser. Off by default — only present when the operator configures it, and the bytes never route through our server.
Signing in / payingEmail (Better Auth), payment metadata (Stripe). Standard.

Even on these AI paths, the PDF bytes never leave your machine — only the text and values named above. (Drafting is the one row where the text isn't extracted from a PDF at all: it's the prompt you type.) You can confirm what gets sent by opening DevTools → Network.

How send-for-signature is encrypted — and where the key goes

Send-to-sign is the only flow where your PDF visits our server at all, and it arrives as ciphertext: the encrypting and decrypting happen in the two browsers, never here. The honest complication is the key, which rides in the link we email for you. The whole flow, including that part:

The trade-off, stated plainly. Anyone holding the full link (including the fragment) can decrypt and sign. Treat the link like a password — share via a secure channel, don't paste in public chats, and let pending requests expire when they're no longer needed.

Cryptography in use

Compliance posture (the honest version)

We're pre-launch and don't yet have third-party attestations. Here's what we do have, and what we don't:

Trust, but verify

Don't take our word for it. Every claim above is something you can check yourself in a few minutes:

1. Open the network tab while you work

DevTools → Network → load https://attachkit.com/app/sign → drop a PDF. Filter by "Fetch/XHR." You'll see the page loads and the PDF.js worker; you will not see your PDF being uploaded. Click Sign, type, save — still no PDF upload. This is the simplest one-minute proof.

2. Inspect the request bodies on AI paths

For Auto-fill, click the API call to /api/autofill → Payload tab. You'll see fields + profile as JSON — no PDF bytes. Same for /api/redact (text spans, no bytes), /api/clause-review (document text, no bytes, no coordinates), and /api/library-answer (matching excerpts + file names, no bytes).

3. Pull the page offline and watch it still work

After loading /app/sign once, open DevTools → Network → throttle to "Offline" → drop a PDF → sign → download. It works. (The AI features will fail, since they need the network; everything else doesn't.)

4. There's no upload request to find

The three checks above are the proof, and they don't need our source: across every flow the Network tab shows no request carrying your PDF, because parsing and editing run in your browser (pdf-lib / pdfjs-dist, fetched once as static assets and then cached). Our AI endpoints accept only the JSON shapes shown above — field/profile data or extracted text — and none accept raw PDF bytes. The wire, not the repo, is the thing you can verify.

Reporting a vulnerability

Found something? Use our contact form and pick “Security vulnerability.” We acknowledge inside one business day and work the fix with you. No formal bug bounty yet, but we'll credit responsibly-disclosed findings if you want.

For pre-launch we don't have a separate security alias, PGP key, or HackerOne program — when those exist, this page is where we'll publish them.

Privacy · Terms · DPA · Contact