No document content ever leaves your device.
This note is for your IT and security reviewers. It describes how Under Seal processes documents, the one network channel it uses, exactly what that channel carries, and how to verify each claim yourself.
Architecture
- Processing is local. Under Seal is a web application that runs in the user's browser. Files are read with the browser's File API, parsed, scrubbed and rebuilt in memory, and saved back to the user's disk. No server receives document content, ever.
- No third-party code at runtime. Every library (pdf.js, mammoth, SheetJS, JSZip, jsPDF, docx) and every font is self-hosted at a pinned version. There are no CDNs, analytics, advertising, error-reporting or font services.
- The browser enforces it. The app's Content-Security-Policy permits network connections to the app's own origin and to one licence service, nothing else:
default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data: blob:; connect-src 'self' https://underseal-activation-production.up.railway.app; worker-src 'self' blob:; object-src 'none'; base-uri 'none'; form-action 'none'; frame-src 'none'
- The licence service accepts counts only. It validates every request against a closed schema and rejects any unexpected field with HTTP 400. A client that tried to send a file name, a hash or document text would be refused.
Exactly what Under Seal sends
There are three requests, all POST with a JSON body to https://underseal-activation-production.up.railway.app. These are all of the fields:
| Request | Fields | When |
|---|---|---|
| /v1/activate | activation_code · install_id (random UUID created on the device) · app_version · label (optional, ≤ 60 characters, e.g. "Tax room 2") | Once per computer |
| /v1/checkin | licence_id · install_id · app_version · period_key · docs (documents this computer processed this period) | At start-up, every 6 hours, when the computer's share of the allowance is used, and on reconnect |
| /v1/release | licence_id · install_id | When a seat is freed |
A complete check-in body, exactly as sent:
{ "licence_id": "lic_3f9a0c41d2e87b65", "install_id": "6f1c2a3b-4d5e-4f60-8a7b-9c0d1e2f3a4b",
"app_version": "1.3.0", "period_key": "M2027-04", "docs": 37 }
Never sent: document content, file names, file sizes, file hashes or fingerprints, detected values, findings, document metadata, user names, e-mail addresses, and device or hardware identifiers. The installation ID is random and not derived from the hardware. Without activation, the app processes samples only and makes no network requests at all.
What the service stores: the licence (tier, term, allowance, paid-through date, a SHA-256 hash of the activation code), installations (random ID, optional label, activation and last check-in times), per-period document counts, an audit log of licence events, and for billing only the Stripe customer and subscription IDs plus each billing event's ID, type and outcome (never card or bank details). It does not log IP addresses, user agents or request bodies; it logs method, path and status only. The hosting provider's edge infrastructure may process connection metadata.
What the service returns
A lease: a JSON payload signed with ECDSA P-256 / SHA-256 (ES256). The app verifies it against a public key pinned in the app, which is also published at /v1/public-key. The lease states the tier, the allowance period (in Eastern time), the pooled allowance and usage across the firm, this computer's share (quota), seats in use, the paid-through date, and when it expires.
Time, entitlement and offline behaviour
- Server time is authoritative. Periods, renewal and expiry come from the server clock. The app uses its own clock only to measure time since its last renewal.
- The billing term drives entitlement. A licence is active until the end of its paid-through date (Eastern time), plus a billing grace of 7 days on monthly terms or 14 on annual terms. Overdue accounts see a notice in the app from the first day of that grace.
| Situation | What the app does |
|---|---|
| Online | Checks in at start-up and every 6 hours; the lease renews each time. |
| Offline, within the grace | Works normally, up to this computer's share of the remaining allowance. Grace is 7 days on monthly terms and 14 on annual terms. |
| Offline, share used | Pauses new documents on this computer until it checks in. |
| Offline beyond the grace | Pauses new documents until it can renew. |
| Device clock wound back | Pauses until one check-in confirms the time. Moving the clock back cannot extend the grace. |
| Allowance period ended while offline | Pauses until a check-in opens the new period; the old period's count is reported first. |
| Licence revoked, suspended or lapsed | Takes effect at the next check-in (within 6 hours online). |
| Computer unused for 30 days | Its seat is freed; it rejoins automatically at its next check-in if a seat is available. |
Cryptography
- Leases: ES256 (ECDSA P-256, SHA-256), verified with the browser's Web Crypto API. The private key exists only in the licence service's environment. Rotated October 2026; earlier keys are not accepted.
- Vault keys (which map placeholders back to real details for the AI round-trip): AES-256-GCM, with keys derived by PBKDF2-SHA-256 at 310,000 iterations from the user's passphrase. Vault keys are created and stay on the user's device; Phronisi cannot recover them.
- Certificates record SHA-256 fingerprints of the original and the clean copy, plus the categories and counts removed, never the values.
How to verify
- In the browser: open developer tools, Network tab, and process a document. The only requests leaving the page's origin are check-ins to the licence service.
- In the app: "Show what Under Seal sends" lists every request with its full body and response status.
- In code: our regression suite includes a real-browser egress test that processes every sample file and export, and fails on any request off the app's origin, any upload, any security-policy violation, or any check-in field that isn't published here. The licence service's tests confirm it rejects unpublished fields.
Limits, stated plainly
- Under Seal reads text layers. Scanned images without text are flagged, not processed; on-device OCR is on the roadmap.
- Automated detection supports, and does not replace, professional review. Certificates name a human reviewer for that reason.
- Because Under Seal runs on the customer's computer, a technically skilled user who modified the app could bypass client-side controls. Server-side metering, installation caps and short leases keep honest use accurate and revocable; the subscription agreement's audit clause covers deliberate circumvention.
Questions from your security team: support@underseal.app.