Here is how OneHub360 actually keeps your data safe.
Specifics, not platitudes. The hosts, the headers, the hashes, the audit log, and the audits we have not yet paid for. If you are running diligence on us, this page is for you.
Hosting and infrastructure
Single-tenant by architecture. Your data and our other customers' data run in the same Docker container on the same VPS, but never in the same row, query, or response — tenant isolation is enforced at the application layer (see below). We are not multi-tenant cloud SaaS sharing a Postgres cluster across thousands of accounts.
- Single VPS, dedicated metal
- AlmaLinux 9.7 on 50.62.182.183 (GoDaddy tier), 8-core AMD EPYC, 16 GB RAM. Horizontally scalable if a customer's load demands it; today the architecture is single-host Docker, not a cloud autoscaling fleet.
- Encrypted backups, daily 3 AM CT
- File-level encryption on the SQLite snapshot at /opt/onehub360/data/prod.db. Pre-deploy hot-backup tarballs auto-saved to /opt/backups/auto-pre-checkout-*.tar.gz (rolling last 20). We have restored from one of these in production.
- TLS via Let's Encrypt, auto-renew
- HSTS enforced. HTTP traffic is 301-redirected to HTTPS at the Apache reverse proxy. Certificate renewal runs unattended; we monitor expiry separately.
- Apache reverse proxy, fail2ban on SSH
- Per-route Content-Security-Policy headers. X-Frame-Options: DENY on all non-Shopify-embedded routes (Shopify embeds use frame-ancestors CSP instead so the admin can iframe legitimately). fail2ban watches sshd for brute-force attempts.
Application security
Tenant isolation is the controlling concern. Every API request resolves to a (tenantId, businessId) before any database query runs. The agent literally cannot read or write outside its scope — we made that a property of the auth layer, not a convention developers have to remember.
- Tenant isolation, hard-bound to API key
- lib/api-auth-dual.ts:requireApiAuth resolves tenantId from the API key on every request. Handlers receive an ApiAuthResult with tenantId fixed; no handler can override it. A leaked key is scoped to one tenant only.
- Mutation guards on every write
- lib/api-mutation.ts:withMutationGuards wraps every mutating route. Records an ApiAuditEvent (regardless of outcome), supports Idempotency-Key headers (24h cache, refuses key reuse with a different body), and maps Prisma errors P2002 → 409, P2025 → 404, P2003 → 400 so callers get correct HTTP semantics.
- Plan-tier gating, server-side
- Free-tier requests get a 403 PLAN_UPGRADE_REQUIRED before any tool registers. No way to spoof a plan from the client. The MCP layer renders the 403 as an upgrade CTA; REST callers get the legacy error envelope unchanged.
- API keys: SHA-256 hashed at rest
- Format: nhk_*. Hashed with SHA-256 before storage (see lib/api-key-auth.ts). The plaintext key is shown once at creation and never again. Revocable from /settings/api-keys; revocation propagates within 60 seconds (cache TTL).
- Confirm-flag pattern on destructive actions
- oh360_delete_contact, oh360_send_proposal, and oh360_send_invoice require confirm:true at the MCP layer. Without it, the call is refused before any HTTP roundtrip — agents cannot accidentally delete or send by hallucinating a tool call.
- NextAuth sessions, 30-day JWT
- Email + password (bcrypt) or Google OAuth. Built-in CSRF protection. HTTP-only cookies. Session cookie maxAge: 30 days, refreshed on activity.
- Per-IP and per-key rate limits
- lib/rate-limit.ts. High-volume routes (chat, tracking heartbeat) carry their own bucket sizes (240/min for heartbeat, 600/min for chat). Public form submissions: 5/min per IP, 5/min per email. Daily API-key quotas by plan: starter 100, growth 2,500, pro 10,000.
Data
SQLite on a mounted Docker volume. Boring, durable, fast for the working set we run. The interesting parts are the indexes, the transactions, and what we do not store.
- SQLite at /opt/onehub360/data/prod.db
- File-level encrypted backups daily. WAL-mode for concurrent reads. The volume is mounted from host into container; the database file does not live inside the image.
- 16 added foreign-key indexes (April 2026)
- Catch-up migration covering the FK columns the ORM had not auto-indexed. Tail-latency on contact-search and pipeline queries dropped accordingly. Wrapped critical writes in transactions at the same time.
- Sentry for app-layer errors (April 2026)
- Sensitive fields scrubbed before send: email addresses, phone numbers, and arbitrary PII strings are replaced with sha256:<truncated> hashes (see lib/sentry-scrub.ts) so we can correlate without leaking identities.
- GA4 + Microsoft Clarity, consent-gated
- No analytics fires until the cookie banner is accepted. consent_default is denied for analytics_storage and ad_storage on first paint; consent updates are pushed to GA via gtag('consent', 'update').
Authentication and access
Two auth paths — session (humans) and API key (programmatic). They unify into the same ApiAuthResult so handlers do not branch on auth method. Cross-business access is never implicit.
- NextAuth, JWT-based, 30-day sessions
- Cookie sessions, signed and HTTP-only. Email + password (bcrypt-hashed) and Google OAuth supported. CSRF tokens enforced on every state-changing form.
- Multi-business, explicit join table
- A user can belong to several businesses; each membership is a row in BusinessUser. There is no implicit cross-business access — a user only sees businesses they have a row for. Roles (Owner, Admin, Member) are enforced at the data layer.
- API keys for programmatic access
- Separate auth path. Keys carry a businessId; they cannot be repointed at a sibling business in the same tenant by the API consumer. Keys are minted at /settings/api-keys, revoked at the same place.
Audit log
Every mutation lands in the ApiAuditEvent table. You can see who did what, with what arguments, and what happened. We use this internally to debug; you see the same data on /settings/audit.
- Per-tenant audit trail at /settings/audit
- Schema: tenantId, businessId, apiKeyId, userId, authMethod, method, path, toolName, args (PII-redacted), status, errorCode, errorMessage, durationMs, idempotencyKey, createdAt. Indexed on (tenantId, createdAt), (apiKeyId, createdAt), (businessId, createdAt), and (toolName, createdAt) for fast filtering.
- Tenant-scoped, no cross-team visibility
- You see your tenant's events. Nothing else. Customer support cannot see your audit log without an explicit JIT permission flow (and even then, the access is logged).
- Mutations logged regardless of outcome
- 4xx and 5xx responses generate audit events too — a failed delete attempt is recorded with the error code (NOT_FOUND, DUPLICATE, etc.). You can prove a destructive action did not succeed, not just that it was attempted.
Compliance and privacy
We honor the requests required of us under GDPR, CAN-SPAM, and PCI scope. We do not pretend to be more compliant than we are — see the next section for the audits we have not paid for yet.
- GDPR: data export and deletion
- Export and deletion endpoints exist. Right-to-be-forgotten requests are honored. Data subject access requests handled within the timeframe required by GDPR Article 12.
- CAN-SPAM: List-Unsubscribe on every campaign email
- Every outbound campaign carries List-Unsubscribe and List-Unsubscribe-Post headers (RFC 8058 one-click). Unsubscribed contacts are filtered automatically — campaigns cannot send to them by mistake.
- PCI: cards never touch our infrastructure
- All payments routed through Stripe Checkout. We store stripePaymentIntentId and (where applicable) the last 4 digits Stripe returns. PAN, CVV, and expiration date never enter our codebase or our database.
- Cookie consent banner, default denied
- consent_default is denied for analytics_storage, ad_storage, ad_user_data, and ad_personalization until the visitor accepts. Only essential cookies (session, CSRF) load before consent.
Vulnerability disclosure
If you find a security issue, please tell us. We are happy to coordinate a disclosure timeline, credit you in our acknowledgments, and treat the report with professional courtesy.
- security@onehub360.com
- Email us with the details. PGP key available on request — please ask in the first message rather than CC'ing the whole report unencrypted.
- Response SLA: one business day
- We respond to acknowledge receipt within one business day. Triage and remediation timeline depend on severity; we will tell you what we are doing and when.
- Coordinated disclosure preferred
- We will work with you on a public-disclosure timeline that gives our customers time to be safe. Out-of-the-blue full-disclosure does not help anyone — please give us a chance first.
What we do not do (yet)
Plenty of vendors will imply they have audits they do not. We will not. Here is where we stand on the certifications you might be checking for.
- SOC 2 — not yet, on the roadmap
- We do most of the controls SOC 2 Type II asks for — access logging, change management, encrypted backups, vendor review. We have not paid for the audit. When we do, this page will say so and link to the report.
- HIPAA — no
- OneHub360 is not designed for protected health information. We do not sign BAAs. If your use case involves PHI, we are not the right tool.
- ISO 27001 — same status as SOC 2
- Most of the ISMS controls are in place. We have not paid for the formal audit. On the roadmap; not certified today.
Questions your security team needs answered?
Email security@onehub360.com. Real human, real reply, one business day. We are happy to walk through the architecture, run a questionnaire, or hop on a call.