Skip to content
FirmWorks

Security

What we actually ship

A plain list of the security and tenancy controls FirmWorks runs in production — no marketing-grade certifications we haven’t earned, no vague promises.

Data residency & storage

  • Application and primary database hosted in Singapore (ap-southeast-1) on Vercel and Neon. All transit is over TLS 1.2+.
  • Postgres is encrypted at rest by Neon. Vercel Blob (file uploads) sits in the same region.
  • Daily backups taken by Neon, retained for 30 days (encrypted), with point-in-time recovery available within the retention window.

Authentication

  • Passwords stored as salted hashes via Better Auth — we never see plaintext.
  • Database-mode rate limiter on credential endpoints — 5 attempts / 15 min on /sign-in, /sign-up, and /reset-password; 3 / 15 min on /forget-password.
  • Vercel BotIDwired on every auth endpoint — blocks credential-stuffing, sign-up spam, and password-reset spam without a captcha gauntlet for real users.
  • Optional magic-link sign-in for invited members, scoped to their email.

Tenancy & isolation

  • Every database query is filtered by your organization id server-side. No shared-database permission tricks.
  • Cross-tenant isolation is locked in by an integration test in tests/cross-tenant.test.ts — it round-trips real rows through Postgres in two orgs and asserts a query in org A cannot see org B’s data.
  • Optional email-domain allowlist per org, so invitations can only go to addresses on your approved domains.
  • Per-org audit logof administrative actions — captures actor, IP address, user agent, and entity touched. Visible to org owners and admins.

Observability

  • OpenTelemetry instrumentation via @vercel/otel, auto-collected on Vercel deploys. For self-host or Datadog/Honeycomb forwarding, set OTEL_EXPORTER_OTLP_ENDPOINT and the SDK forwards traces to it.
  • Errors and security-relevant events surface to the audit log; org owners can read them at /app/settings/audit.

Privacy posture

  • No third-party analytics or advertising cookies on marketing surfaces.
  • We do not use workspace data to train AI models.
  • One first-party session cookie (firmworks.session_token) is the only auth state stored on a browser.

What we don’t claim

We don’t hold a SOC 2 Type II report. We don’t hold ISO/IEC 27001. If your procurement process requires either, ask us — we’ll be honest about whether the trade-off makes sense for your size and ours, and what would unlock starting the journey.

We don’t run a paid bug-bounty program yet. Vulnerability disclosures go through the responsible-disclosure flow below.

For procurement teams: our Data Processing Addendum covers GDPR Art. 28 / Singapore PDPA / Thailand PDPA obligations — sub-processor list, 72-hour breach notification, data-subject rights flow, and audit rights are all documented there. Counter-signed PDF is available on request from legal@firmworks.com.

In flight

Security and tenancy work the team is actively shipping. None are blocked on a customer ask — if any of these gates a procurement decision, email security@firmworks.com and we’ll prioritise.

  • Drizzle migrations checkpoint. Today we sync schema with db:push; switching to checked-in migrations under lib/db/migrations/ before the first paying customer ships. Adds reproducible dev/staging/prod schema history.
  • Audit-log retention extensions. Today the audit log retains 1 year per the privacy policy. Optional longer retention windows (3 / 7 years) for Business-plan customers with compliance obligations are being scoped.
  • Per-org API keys. Schema, plaintext issuance + revoke flow, and a verified-test path are live; the public-API entry path is being wired so customers can build automations without re-using their session cookie.

Recently shipped

Defence-in-depth on the billing + upload paths landed in the last week. Each item is live in production today; no environment toggle, no opt-in.

  • Stripe webhook idempotency. New stripe_event(id text PK) table de-duplicates Stripe’s at-least-once delivery contract: a retry of the same event.id short-circuits with a 200 + { deduplicated: true } before the event handler runs, so half-applied side-effects can’t compound on retry.
  • Cancellation revokes paid features. The customer.subscription.deleted branch now writes plan = 'trial' on cancel — churned customers can’t keep paid-tier access after their Stripe subscription ends.
  • First-paid-checkout creates the row. The checkout.session.completed branch is now an INSERT … ON CONFLICT (orgId) DO UPDATE instead of a bare update — previously the first paid checkout could silently no-op when no subscription row existed yet, leaving the customer paid-but-unseated.
  • Hardened file uploads. /api/upload now enforces a MIME whitelist (PNG, JPEG, WEBP, PDF), a 25 MB size cap (trusts the larger of file.size and Content-Length), an RBAC authorize(role, 'docs', 'attach') check (viewers get 403), and a UUID path so two uploads in the same millisecond can’t collide. Closes the prior gap where a member could upload an SVG with inline JS to a public Blob URL.

Responsible disclosure

We don’t run a paid bug-bounty programme today, but we welcome responsible disclosure and will respond within 5 business days of receipt.

  • Email security@firmworks.com with reproduction steps, affected URL/endpoint, and the impact you observed.
  • A PGP keyfor encrypted reports is available on request from the same address — reply with “PGP please” and we’ll send the public key.
  • Please don’t open public GitHub issues, post on Twitter, or run intrusive scans against the production environment until the issue is resolved — we’ll credit researchers who report responsibly when the fix ships.
  • Out of scope: missing security headers on marketing pages, denial-of-service via volumetric attacks, social engineering of staff, and findings that require root access on a customer’s own device.

Need to dig deeper?

Our privacy policy covers sub-processors, retention, and rights under PDPA / GDPR. The Data Processing Addendum covers our processor obligations. For security questionnaires, email security@firmworks.com.