Built defensively from day one.
We're a small team, but we don't ship security as a future problem. Here's exactly what's live in production today, what's on the roadmap, and how to reach us if you find something off.
TLS everywhere
Every request is served over HTTPS with HSTS preloaded. Cloudflare terminates TLS at the edge; the origin enforces TLS 1.2+ and sends a strict-origin-when-cross-origin Referrer-Policy on every response.
Per-workspace data isolation
Every row in the database carries a workspaceId foreign key. An ensureWorkspace middleware on every authenticated route checks that the caller actually owns the workspace they're operating on. This is enforced at the route boundary, not just the UI.
Password + token hardening
Passwords are stored with bcrypt at cost 12, and every new or changed password is checked against the Have I Been Pwned breach corpus via the k-anonymity API (only the first 5 chars of the SHA-1 hash leave the server, and we ask HIBP to pad responses so traffic analysis can't infer the suffix). Email-verification, password-reset, and deletion-cancellation tokens are stored as SHA-256 hashes — the raw token only exists in your inbox. The login endpoint is rate-limited per IP at 10 attempts per 15 minutes, with explicit error codes for password-too-long / email-too-long so we don't leak which one matched.
Email authentication (DKIM + DMARC + SPF + RFC 8058)
Outbound mail goes through AWS SES (v2 API), signed with DKIM and aligned for DMARC under our verified sender domain. Every outreach email carries List-Unsubscribe + List-Unsubscribe-Post headers per RFC 8058 — Gmail / Outlook / Yahoo render the native one-click Unsubscribe widget next to the sender name. The unsubscribe endpoint is split into a GET that renders a confirmation page (no DB write) and a POST that performs the action, so email-provider link scanners (Outlook Safelinks, Google's link-safety crawler, antivirus inspection) can't silently unsubscribe legitimate recipients during message inspection. Per-IP rate limit (30/min) protects the POST against abuse. Hard bounces, complaints from SNS webhooks, and one-click unsubscribes all auto-add the recipient to the workspace's suppression list.
Bot + abuse protection on public forms
Every unauthenticated state-changing form gates submissions with Cloudflare Turnstile — sign-up, password-reset request, and the /support contact form. Bot challenge runs FIRST in the request pipeline (before email-format and rate-limit checks) so it stops bot traffic at the gate regardless of which downstream branch the request would have taken; the server fails closed on Cloudflare outage. Per-IP rate limits sit on top of Turnstile as defence in depth, and Cloudflare's bot management runs at the edge for the entire site.
Defensive HTTP headers
X-Frame-Options: DENY (no clickjacking), X-Content-Type-Options: nosniff (no MIME sniffing), Referrer-Policy: strict-origin-when-cross-origin, Strict-Transport-Security in production, Cross-Origin-Opener-Policy: same-origin (no cross-origin window.opener access — closes tabnabbing), Permissions-Policy denying camera/microphone/geolocation/etc., and an enforced Content-Security-Policy that restricts scripts to self + Cloudflare Turnstile + Google Tag Manager, locks object-src + base-uri down, and sets frame-ancestors to 'none'. The X-Powered-By: Express response header is explicitly disabled so we don't broadcast server fingerprint to attackers scanning for CVE-targeted exploits. The /api/* surface returns JSON 404s instead of leaking the SPA shell.
Session cookie hardening
The session cookie is httpOnly (no JavaScript access — kills the XSS-steals-session path), sameSite=Lax (no CSRF on state-changing routes), Secure in production (TLS-only), and signed with a SecureString session secret loaded from AWS SSM at boot. Password reset and account deletion both invalidate every active session on success.
Minimal analytics, no ad pixels
Aggregate traffic is measured with Google Analytics 4 (gtag.js) only — no advertising/retargeting pixels, no Mixpanel- or Hotjar-style session replay, no Sentry. The only other third-party content is Cloudflare Turnstile (lazy-loaded only on the signup, forgot-password, and /support contact forms); webfonts are self-hosted, so no font CDN sees your visits.
Encrypted data at rest
Workspace data lives in AWS RDS PostgreSQL with default encryption at rest using AWS-managed KMS keys. Daily automated backups are retained for 7 days with point-in-time recovery.
Stored XSS hardening
All user-controllable fields rendered into HTML emails or HTML responses (workspace names on the unsubscribe page, the outreach footer, any rich-text body) flow through a single escapeHtml helper. Every server route enforces explicit length caps on every operator-controllable field.
Authorisation tests on every workspace-scoped route
Cross-workspace authz checks live in the route handler, not just the storage layer. PATCH, DELETE, and bulk-update routes all include workspaceId in their WHERE clause to prevent ID-confusion attacks across workspace boundaries.
SSRF defence on URL ingestion
When you paste a URL for ingestion or configure an SMTP host, the fetcher rejects localhost, RFC-1918 private ranges, link-local + carrier-grade NAT, AWS metadata, IPv4-mapped IPv6, and 0.0.0.0/8 at parse time. DNS is resolved separately and re-checked to defeat DNS-rebinding attacks. Redirects are followed manually so every hop gets the same validation — not just the initial host.
Responsible disclosure
Found something? Email [email protected] with subject [security]. We respond within one business day, do not pursue researchers acting in good faith, and credit reporters publicly if they want.
SSO (SAML, OIDC)
Email + password authentication is the only option today. SSO providers (Google Workspace, Okta, Azure AD, generic SAML/OIDC) are on the roadmap and ship in a follow-up phase.
SOC 2 Type II
We're currently pre-revenue and have not begun the SOC 2 audit process. We are committing to start the readiness assessment once we cross our first paid-customer threshold and will publish updates here.
Sub-processors
We rely on these third parties to operate the service. Each is named in the privacy policy and accessed only over TLS.
We will give 30 days notice via email on file before adding a new sub-processor.