Security
Review the production security contract, protections, and explicit limits.
Security contract
This document states the implemented security boundary and the work a buyer must complete before accepting real users. It is not a certification, penetration-test report, privacy policy, DPA, or claim of compliance.
Release security bar
A production release must satisfy all of the following:
- frozen install, static checks, all workspace tests, all builds, and dependency audit pass;
- real Postgres authentication, ownership, billing, migration, and upload negative paths pass;
- environment validation rejects missing production email and partial selected billing/storage provider configuration;
- secrets exist only in the owning server/provider environment;
- every API target runs migrations as a one-shot release primitive, gates traffic on
/ready, and uses the matching trusted proxy profile; - Vercel has only explicitly public
VITE_*values and does not cache authenticated API responses; - backup, restore, rollback, auth email, billing webhook, upload, Sentry, and account-deletion paths are rehearsed for every enabled integration;
- product-specific terms, privacy, retention, deletion, support, and incident contacts are set.
Implemented controls
HTTP boundary
The Hono application applies:
- request IDs and structured request logging;
- an explicit credentialed CORS allowlist;
- security headers;
- a 30-second request timeout;
- a 10 MiB request-body cap;
- stable production error responses without raw stack/database/provider details;
- separate liveness and database readiness endpoints.
Do not expose development error detail in production or route public traffic around the supported proxy.
Authentication and authorization
Better Auth owns password hashing, verification, reset, magic links, Google OAuth, cookies, sessions, and database-backed auth rate limiting. Magic-link tokens are hashed and short-lived; password reset revokes existing sessions; implicit account linking is disabled.
Private product data is authorized through owner-scoped SQL using the Better Auth session identity. The generic disclosing “resource exists but access denied” helper is intentionally absent. Use the same non-disclosing 404 for absent and wrong-owner private resources where existence is sensitive.
See Authentication.
Billing
Billing webhook signatures are verified in the selected provider adapter before payload normalization. Provider event claims and local subscription mutations share a database transaction. Duplicate events do not reapply effects, failed mutations release the claim for retry, and older provider timestamps cannot overwrite newer lifecycle state.
Premium access is derived on the server; a checkout success URL never grants it. Account deletion is blocked until recurring billing is resolved and paid access has ended.
See Diagnose a failed webhook.
Email and logs
Production requires Resend. The zero-key fallback intentionally logs complete auth URLs only in development/test. Those logs are sensitive and must not be published, retained as analytics, or forwarded as console breadcrumbs.
Structured server logs must not include credentials, cookies, raw webhook secrets, provider access tokens, database URLs, or full provider responses.
Object storage
Selected storage-provider configuration is all-or-none. Uploads require an authenticated owner,
explicit upload purpose, MIME allowlist, size limit, owner-shaped key, pending database claim, and
short-lived presigned PUT. Confirmation verifies actual object metadata and atomically consumes
the claim.
The current metadata check does not inspect file magic bytes, run malware scanning, or process untrusted documents. Add those controls before enabling higher-risk upload types.
Browser and PWA
The browser carries the Better Auth HttpOnly cookie with credentials: 'include'. It does not put
an auth token in local storage. The PWA has no runtime API cache because Cache Storage keys do not
vary by session cookie; authenticated responses must never be shared across users on one browser.
Only public values may use VITE_*. A value in a Vite build is not a secret.
Sentry
Sentry is disabled when its browser DSN is absent. When enabled:
- default PII collection is disabled;
- console breadcrumbs are disabled;
- browser envelopes use the API tunnel;
- the tunnel requires an allowed browser origin;
- the DSN hostname must be exactly
sentry.ioor a subdomain boundary ofsentry.io; - the numeric project ID must appear in typed
SENTRY_PROJECT_IDSconfiguration.
Production source maps are generated only when an authenticated Sentry upload is configured, and the
build deletes them from dist after upload. A production build without Sentry credentials emits no
source maps.
The tunnel is not an open proxy. Keep its allowlist limited to the deployed Sentry project and test redaction with production-like errors before launch.
Secrets and environment
apps/server/src/config/env.ts is the only server environment reader. It validates:
BETTER_AUTH_SECRETat 32 or more characters;- complete shared and provider-specific storage configuration when storage is enabled;
- selected-provider credentials plus at least one unique product/price ID when billing is enabled;
- production
RESEND_API_KEY; - one supported trusted proxy profile.
Domain code consumes typed Config rather than reading process.env ad hoc. The optional
Add an environment variable playbook provides the
maintained implementation and verification sequence.
Deployment and data
The production API runs as an unprivileged user in the included image. server migrate fails the
release when the database or migration resources are unavailable. server serve fails before
binding traffic when the database or required email templates are unavailable. Every production
readiness probe queries Postgres.
Postgres access, encryption at rest, network isolation, point-in-time recovery, and regional residency depend on the configured infrastructure plan. Rehearse the included backup/restore scripts against a disposable environment; their presence is not recovery evidence.
Known limits
The v1 foundation does not include:
- organization/role authorization, enterprise multi-tenancy, SSO, SCIM, or admin impersonation;
- a web application firewall;
- automated malware/file-content scanning;
- a generic worker, durable queue, or dead-letter system;
- audit-log UI or compliance export;
- field-level application encryption or customer-managed keys;
- automatic user-data retention/erasure for product-specific domains;
- an independent penetration test, SOC 2 controls, legal compliance mapping, or vulnerability SLA.
Hono endpoint limits use atomic Postgres fixed windows and share budgets across replicas. Capacity planning must reserve at least 20% of database connections outside the combined application and Better Auth pools.
Production checklist
Identity
- Use a newly generated
BETTER_AUTH_SECRET; never reuse the template or test value. - Verify password, verification, reset, magic-link, logout, and session-revocation flows on the exact deployed origins.
- Register the exact Google callback only if Google is enabled.
- Confirm cookie and redirect behavior in every supported browser.
Network and configuration
- Keep the application origin inaccessible around its selected ingress.
- Set
TRUSTED_PROXY_PROFILEto the exact deployed ingress and do not trust a generic forwarding header configuration. - Restrict CORS and Better Auth trusted origins to intended HTTPS origins.
- Keep server secrets out of Vercel and all private values out of
VITE_*. - Leave optional integrations fully unset or configure them completely.
- Send Railway deployment alerts through Railway's direct Slack integration; do not add an unauthenticated application relay for unsigned platform events.
Providers
- Verify the Resend sending domain and send real verification/reset/magic-link messages.
- Verify the selected provider's sandbox/test checkout, signature rejection, duplicate/out-of-order events, portal, cancellation, refund, reconciliation, and account deletion.
- Verify provider CORS, wrong owner, wrong MIME/size, expiry, replay, delete, and lifecycle cleanup.
- Trigger a redacted Sentry error and confirm no email, cookie, auth link, or console content appears.
Operations
- Run
vp check, all tests, all builds, andpnpm audit --audit-level high. - Run migrations
up → down → upagainst a disposable database when schema changes. - Build and smoke the production Docker image, including all rendered email templates.
- Restore a fresh database from backup and record recovery time and data loss.
- Assign security/incident ownership and publish product-specific reporting and support contacts.
- Record dependency, secret-rotation, data-retention, and access-review ownership.
Vulnerability response
Before launch, choose and publish a monitored private security contact. A report should include the affected version, reproducible steps, impact, and safe proof. Do not request public disclosure of credentials or customer data.
For a suspected incident:
- preserve relevant structured logs and provider event IDs without copying secrets;
- contain access by rotating the narrowest affected credential;
- stop harmful provider or deployment behavior without deleting evidence;
- reconcile auth sessions, billing events, uploads, and database state;
- restore or roll back through the rehearsed path;
- document cause, affected data/users, required notices, and a regression test.
The seller's pre-release support policy does not create a guaranteed vulnerability SLA. Buyers own the security and legal operation of products they build from the source.