Skip to content

Security overview

Clausery's security model is short because its architecture is short: there is no server side. This page is written for the person at your firm who has to sign off on new tools.

Architecture

  • The product is a set of static files (HTML, CSS, JavaScript) served from a web host. There is no backend, database, API or account system operated by us.
  • All processing (reading .docx templates, evaluating answers, generating documents, encrypting storage) happens in the browser's JavaScript engine.
  • Data at rest is in the browser's IndexedDB, scoped to the site origin and the browser profile.
  • The app only requests its own static files (code, sample templates, the intake-form runtime) from the site that serves it; it never uploads or posts anything, and it makes no request to any third party. The service worker caches those files for offline use and only handles same-origin requests.

What we can see

Nothing about your documents. The host serving the static files (GitHub Pages for the public instance) sees ordinary web-server traffic: the IP address and browser of whoever loads the app. The app has no analytics, cookies, tracking pixels or third-party scripts; the marketing website only counts anonymous page views (see the privacy policy). Fonts are system fonts.

Cryptography

  • Encrypted workspace: AES-256-GCM with a random 96-bit nonce per record; key derived from the passphrase with PBKDF2-SHA256 (600,000 iterations, 128-bit random salt). Implemented with the browser's WebCrypto API only.
  • License keys: Ed25519 signatures over the license payload, verified with a public key embedded in the app. No license server.

Browser hardening

  • Content Security Policy: default-src 'self', no inline scripts, no remote scripts, connect-src 'self' https://api.lemonsqueezy.com (the second origin is used only to check a license key bought online), object-src 'none', form-action 'none'.
  • Referrer policy no-referrer in the app. The app page is marked noindex.
  • Exported intake forms carry a CSP of default-src 'none' with inline-only script and style, so they cannot make network requests even when hosted.
  • Expressions in templates are evaluated by a purpose-built interpreter, never by eval; identifiers resolve only against the answers object.

Threat model

ThreatPosition
Breach of the vendorNot applicable: we hold no customer data.
Interception in transitNot applicable to documents: they are never transmitted. The app itself is served over HTTPS.
Compromised or shared deviceThe primary risk. Mitigations: encrypted workspace with auto-lock, backups stored on managed storage, standard device security.
Malicious templateA .docx is parsed as XML by an open-source library; no macros run. Templates cannot execute code or make requests.
Supply chain (the app's code)Dependencies are pinned and bundled into the repository; the deploy is the repository content. No CDN scripts at runtime. Self-hosting lets you freeze a reviewed version.

Verify it

  1. Open the app, then open the browser's developer tools → Network. Import a template, draft and generate. Every request is a GET for one of Clausery's own files on the same site (for example samples/…docx when you pick a sample); none carries your data, and none goes to another domain. After the app is installed these are answered by the service worker cache.
  2. Disconnect from the network. Everything continues to work.
  3. Read the source: it is served unminified except for two built files: the bundled document library (vendor/docs.js, upstream versions listed in Settings → About) and the intake-form runtime (vendor/intake-runtime.js, built from src/intake/runtime.js and the app's own modules), which is what exported intake forms contain.

Reporting a vulnerability

Report it privately with GitHub's private vulnerability reporting, not in a public issue. We aim to acknowledge within two business days. Please do not test against other people's deployments. The full policy is in SECURITY.md.