bsns.cc
Security & trust
Last updated May 31, 2026
bsns.ccis built around the premise that customer records belong to the customer, not the platform. This page documents the technical controls behind that promise, the gaps we haven’t closed yet, and how to verify either claim.
For the user-facing version of this material with examples and plain-English framing, see Help · Data & security.
Hosting & data residency
Application compute runs on Vercel (Fluid Compute, Node.js runtime) in the us-east-1 region. Persistent data lives in Neon Postgres clusters in the same region, segmented into three independent Neon projects: cent-prod (production), cent-staging (preview/staging), and cent-dev (developer integration). Production data never reaches a non-production environment except via a controlled copy-on-write branch that strips PII before refresh.
The customer-identity database (sign-in, MFA, federation) is a logically separate Postgres database from the business-data database; the two are queried by separate Prisma clients with distinct credentials. A bug in one cannot reach into the other.
Encryption
In transit
TLS 1.2 or higher on every public endpoint. HSTS is set with a long max-age. Internal traffic between Vercel functions and Neon rides on TLS-required Postgres connections (sslmode=require plus channel binding).
At rest
Neon storage is transparently encrypted at the disk layer. On top of that, sensitive columns — date of birth, SSN, EIN, tax IDs, driver license numbers, federation private keys, telematics API keys — are individually encrypted with AES-256-GCM using per-app encryption keys held in 1Password and mirrored to a BitWarden escrow with a fingerprint check enforced in CI. Plaintext of these columns never leaves the single decrypt helper that wraps them.
Key management
Keys are 256-bit, generated with a cryptographic RNG, and rotated on demand using a documented runbook (docs/operations/env-recovery.md). A pre-commit gate refuses to push environment variables that drift from the recorded fingerprint, so a stale escrow can’t silently mask a missing rotation.
Tenant isolation
Every business-data table that carries a tenantId column is under Postgres row-level security with the FORCEattribute. The application connects with a role that doesn’t bypass RLS, and the tenant identifier is set as a server-side session GUC inside an explicit transaction before any read or write. An application-layer bug that forgets to set the GUC returns zero rows — it cannot leak another tenant’s data.
Cross-tenant operations (super-admin views, support tooling, billing rollups) use a separate owner-role client and are gated by the platform-permission system below.
A CI invariant (check:rls-coverage) refuses any pull request that introduces a tenant-keyed table without enabling RLS. A second invariant (check:rls-carveouts) maintains an explicit allowlist of cross-tenant exceptions and flags additions.
Authentication
One sign-in for the whole suite via auth.bsns.cc. Sessions are issued as signed JWTs scoped to the .bsns.cc cookie domain. The portal supports email + password, WebAuthn passkeys, TOTP, and SAML federation for enterprise tenants.
Multi-factor authentication is enforced. New accounts have a 7-day grace window to enroll a passkey or TOTP, after which sign-in routes through MFA verification before launching any app. MFA verification status rides in the JWT and is checked by every per-app launch endpoint.
API access uses two distinct token types: per-user personal access tokens (pat_*) for human-driven scripts, SHA-256 hashed at rest and shown once at creation; and env-scoped service keys for trusted backend traffic. Both run through rate limiters backed by Upstash Redis so a single compromised credential can’t fan out faster than the configured ceiling.
Platform-permission groups (SUPER_ADMIN, BILLING_ADMIN, SUPPORT_ADMIN) gate the super-admin surface. Group → permission mapping is code, not a UI form — a change requires a pull request, not a checkbox.
Audit logging
Sensitive actions write to one of three audit surfaces:
PlatformAuditLog— every super-admin action, including the permission that authorized it, actor, target, and metadata. Surfaced on/admin/audit.ImpersonationEvent— one row per cross-tenant launch via the impersonation flow; viewable by both the super-admin and the impersonated tenant.- pactdocument signing — per-tenant tamper-evident hash chain (each entry holds a SHA-256 of the prior entry) with advisory-locked writes so concurrent signers can’t fork the chain.
Cross-app side effects (notifications, tasks, emails, payment webhooks) carry schema-level idempotency keys so a duplicate write hits a unique constraint instead of double-firing.
Monitoring & incident response
Application errors, slow queries, and cron failures forward to a central observability surface (/admin/observability). Alerts route through ntfy with PII-safe payloads. A dead-man’s switch tracks every wrapped cron and alarms when an expected run is overdue. Uptime is monitored by Better Stack plus a healthchecks watchdog on a separate provider so a provider outage doesn’t blind us.
Live and resolved incidents are posted at /status. For business-impacting events affected customers are also contacted directly. Reporting target: confirm receipt of a vulnerability report within 24 hours, status update within 5 business days.
To report a vulnerability or suspected incident, email security@bsns.cc. The endpoint and policy are also published at /.well-known/security.txt per RFC 9116.
Backups & continuity
Every Neon project takes hourly snapshots retained by Neon. On top of that we run an independent daily pg_dump to encrypted blob storage. The full restore path was last drilled on May 25, 2026: a complete pg_dump → pg_restore against a fresh Postgres cluster completed in 3 minutes 25 seconds. The runbook lives at docs/operations/preview-refresh.md and is exercised weekly via the staging-refresh job.
Recovery objectives. Target RPO: 1 hour (Neon snapshot cadence). Target RTO: 1 hour (restore-and-cutover).
For the “what if the operator gets hit by a bus” question, see Continuity & bus-factor.
Change management
Code ships through a fast-forward-only branch sequence: dev → preview → master. The deploy-production.sh script refuses to deploy unless HEAD equals origin/master and is an ancestor of origin/dev— a hot-fix straight to production is mechanically blocked, not just policy.
Every push runs four CI invariants: check:api-route-auth (every API route declares its auth posture), check:env (no environment-variable drift between manifest and Vercel), check:rls-carveouts (no new tenant-keyed table without RLS), and check:security-invariants (catch-all). A pre-commit hook runs the same audits on staged files.
Database migrations are forward-only via Prisma migrate deploy, run by the production-deploy script before any code ships. Destructive schema changes follow an expand → migrate → use → contract pattern documented in docs/environment-strategy.md.
Data portability & deletion
Every app exposes a one-click tenant data export at Settings → Data export. The archive contains one JSON file per table (verbatim rows), a flat CSV per spreadsheet-friendly entity, a manifest.json with counts and schema version, and a README. Same shape across the suite, no proprietary format.
A tenant admin can delete the tenant via the same settings surface. Deletion purges all business-data rows and the customer- identity records bound to the tenant. Two caveats we’re actively closing: archived blob storage (signed PDFs, uploaded attachments) and downstream vendor records (Stripe customers, Telnyx phone numbers) are queued for cleanup but not yet atomically deleted with the tenant. The runbook is in docs/operations/tenant-delete.md.
For verbatim data-subject-rights requests under GDPR or CCPA, email privacy@bsns.cc. Response target: 30 days.
Sub-processors
Third parties that process customer data on our behalf are enumerated, with purpose, region, and DPA status, at /security/sub-processors. The list is maintained in source control; a change is a pull request.
Security questionnaire
A pre-filled response to the most common prospect security questions (CAIQ-aligned) lives at /security/faq. If you have a questionnaire your team is required to use, send it to security@bsns.ccand we’ll return it with answers cross-referenced to the relevant control on this page.
What we don’t have yet
Trust is built on what is true, not what we’d like to be true. The following gaps are real today and tracked in our risk register:
- Single operator. bsns.ccis currently run end-to-end by one person. Segregation of duties in the strict SOC 2 sense isn’t structurally possible yet; the compensating controls are the audit logs, the dev-first deploy gate, and BitWarden key escrow with documented recovery procedures.
- External penetration test.Engagement scope drafted but not yet contracted. We’ll publish the date here when it’s booked.
- SOC 2 Type 1 / Type 2.Groundwork in progress; no audit firm engaged yet. We’ll publish the auditor and target window here when both are confirmed.
- Database IP allowlist.Architectural constraints with serverless egress make a strict allowlist impractical today. Compensating: per-database credentials, FORCE-RLS, audit logs, and Neon’s own network controls.
- DMARC enforcement. Outbound email is at
p=nonewhile we observe reports; the flip top=quarantineis queued. - Hard-delete completeness.Tenant deletion doesn’t yet atomically remove archived blobs and downstream vendor records; queued for the next release window.
Contact
Vulnerability reports, security questions, incident inquiries: security@bsns.cc.
Privacy and data-subject-rights requests: privacy@bsns.cc.
General support: /help/support.