Skip to content

bsns.cc

Security & trust

Last updated August 14, 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

Production application compute runs on Google Cloud Run and production relational data lives in Cloud SQL PostgreSQL 17 in us-west1, behind a global Google Cloud HTTPS load balancer. Preview is isolated in the separate bsns-nonprod GCP project, with passive Cloud Run services and separate Cloud SQL databases in us-west1. Development remains temporarily isolated on Vercel and Neon in us-east-1. The former Neon Production and Preview paths are not live authority. Production data is not copied into a non-production tier without a separately reviewed, redacted refresh procedure.

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. That separation narrows accidental access and recovery scope.

Encryption

In transit

TLS 1.2 or higher on every public endpoint. HSTS is set with a long max-age. Production and Preview terminate at Google Cloud frontends; Cloud Run reaches Cloud SQL through managed connectors, which authenticate and encrypt the network path. Development's Vercel-to-Neon Postgres connection requires TLS plus channel binding.

At rest

Cloud SQL and the managed object-storage providers encrypt their storage at the platform 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 required local fingerprint check. 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 required local invariant (check:rls-coverage) refuses any change 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 by default. 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. A current Google or Apple federated sign-in satisfies this requirement through the identity provider rather than requiring a second bsns.cc factor. MFA verification status rides in the JWT and is checked by every per-app launch endpoint.

A tenant’s own admins may exempt an individual non-admin teammate from the enrollment requirement — the counter or field worker with no smartphone and no security key. Exemptions are per-person (never workspace-wide), require a written reason, and are recorded in the security log. Administrator accounts cannot be exempted, and an exemption never bypasses verification for a user who has enrolled a factor.

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:

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). Google Cloud supplies seven public uptime checks, Cloud Run error-log policies, a monitoring dashboard, and a proved email channel. Alerts also fan out to the operator with PII-safe payloads. A dead-man’s switch tracks every wrapped cron and alarms when an expected run is overdue. Better Stack and Healthchecks.io remain cross-vendor signals so one provider does not own every detection path.

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

Production Cloud SQL retains seven days of point-in-time recovery logs and 14 automated daily backups, with deletion protection. A separate nightly job exports both the business and identity databases, age-encrypts them before upload, and writes ciphertext to Cloudflare R2. A current-source export passed archive, privacy, row-count, and hash guards on August 14, 2026.

Recovery objectives. Target RPO is at most one hour through Cloud SQL, or at most 24 hours if GCP-native recovery is unavailable. Target database-restore/application-rollover RTO is one hour. A pre-migration restore completed in 3 minutes 25 seconds on May 25, 2026. On August 14, the current age-encrypted R2 artifacts passed a full cross-vendor PostgreSQL 17 restore and validation sequence in under 10 technical minutes, excluding human escrow unlock and incident-decision time. Cloud SQL-native recovery was posture-checked, not restored into a second Cloud SQL instance.

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 GCP release command refuses a dirty checkout or anything other than exact origin/master, proves the currently served release is contained by trunk, preserves live activation posture, and audits an image-only infrastructure plan.

Every reviewed release runs the required test and audit suite locally, including check:api-auth (every API route declares its auth posture), check:env (no environment-contract drift), check:rls-carveouts (no new tenant-keyed table without RLS), and check:security-invariants (catch-all), plus dependency advisory and registry-signature checks. GitHub Actions mirrors useful checks when available but is not release authority. A pre-commit hook runs a fast audit subset on staged files.

Database migrations are forward-only via Prisma migrate deploy, run through the Cloud SQL Auth Proxy and a compatibility gate before any new revision serves. Destructive schema changes follow an expand → migrate → use → contract pattern documented in docs/environment-strategy.md.

Data portability & deletion

Tenant admins can export their data from Settings → Data export in each app. The archive contains JSON for included entities, flat CSVs for spreadsheet-friendly entities, a manifest.json with counts and schema version, and a README that names exclusions and redactions. Same envelope across the suite, no proprietary archive format. Some binary artifacts, including executed Pact PDFs, are downloaded separately rather than embedded in the current archive.

A tenant admin can schedule deletion from Settings → Close account. The next renewal is cancelled and the workspace becomes read-only immediately, followed by a 30-day undo/export window. An already-paid period is not prorated. Final deletion removes the Stripe customer and managed object-storage records, releases managed Telnyx numbers, detaches Plaid items and supported Stripe Connect accounts, and then purges business-data rows and customer-identity records. External failures are recorded for reconciliation. User-owned Gmail grants for identities that end with the workspace use the account-deletion revocation path. Google Calendar, Zoom, and native-voice SIP credentials remain documented manual follow-ups. 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:

Contact

Vulnerability reports, security questions, incident inquiries: security@bsns.cc.
Privacy and data-subject-rights requests: privacy@bsns.cc.
General support: /help/support.