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.
Isolated at the database
FORCE row-level security on every customer table. Wrong tenant, or no tenant, returns nothing.
Keys hashed at rest
SHA-256 before storage, plaintext shown once, each key bound to a single business.
Every action traceable
A per-tenant audit trail on API and AI-agent changes, in Settings. The same data we use to debug, so you can prove what happened.
Hosting and infrastructure
Your data and our other customers' data live in one PostgreSQL database, but every tenant's rows are walled off from every other tenant by the database itself (see the next section). The app runs in Docker on a dedicated VPS behind an Apache reverse proxy. We are deliberate about the perimeter and the backups.
- Dedicated VPS, containerized app
- The application runs in a Docker container on a single dedicated server, with PostgreSQL in its own container reachable only over a private Docker network. Horizontally scalable if a customer's load demands it; today the architecture is single-host Docker, not a cloud autoscaling fleet.
- Encrypted backups, nightly
- A logical database dump runs nightly, is gzipped, size-checked (a truncated dump is treated as a failure), and rotated. Backups are encrypted and mirrored off the host. We have restored from a backup in production, so we know the path works.
- 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; administrative access is key-based SSH only.
Tenant isolation, enforced by the database
This is the control we lead with. Tenant isolation in OneHub360 is not a convention developers have to remember or a WHERE clause they might forget. It is enforced inside PostgreSQL by FORCE row-level security on every application table. Even if application code asked for the wrong tenant's rows, the database would refuse to return them.
- FORCE row-level security on every table
- Every application table runs FORCE ROW LEVEL SECURITY in PostgreSQL, governed by hundreds of policies. The application connects as a role that cannot read or write a tenant's row unless the current request has set per-request, transaction-local identity (the user, tenant, and business). A query with no principal set, or the wrong one, returns nothing and a write is rejected by the database. Misses fail closed.
- Request identity is transaction-scoped, not ambient
- On every authenticated request we open a transaction and set the tenant and business identity as transaction-local settings before any query runs. Those settings are what the row-level-security policies check. They cannot leak between requests, and a background job or webhook must explicitly run under a named system principal to touch data at all.
- Tenant isolation, hard-bound to API key
- API authentication resolves the tenant from the key on every request. Handlers receive a result with the tenant fixed; no handler can override it, and the same row-level-security policies still apply underneath. A leaked key is scoped to one tenant only.
- Mutation guards on every write
- A shared guard wraps every mutating route. It records an audit event (regardless of outcome), supports Idempotency-Key headers (24h cache, refuses key reuse with a different body), and maps database constraint errors to correct HTTP semantics (409 on conflict, 404 on missing, 400 on bad reference).
- 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
- API keys are hashed with SHA-256 before storage; we never persist the plaintext. The key is shown once at creation and never again. Revocable from Settings, and revocation propagates within about a minute.
- 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
- Sign-in is email + password (bcrypt). Built-in CSRF protection. HTTP-only cookies. Session cookie maxAge: 30 days, refreshed on activity. Google is supported as an integration (connect Gmail and Google Ads from Settings), not yet as a login method.
- Per-IP and per-key rate limits
- High-volume routes (chat, tracking heartbeat) carry their own bucket sizes. Public form submissions are throttled per IP and per email. API keys carry daily quotas by plan tier.
Data
PostgreSQL, with row-level security enforced on every table and indexes and transactions tuned for the working set we run. The interesting parts are the isolation guarantees, the indexing, and what we do not store at all.
- PostgreSQL with row-level security
- The database is PostgreSQL running in its own container, reachable only over a private Docker network and never exposed to the public internet. The database file does not live inside the application image. Every application table enforces row-level security so a query can only ever see the requesting tenant's rows.
- Indexed and transactional writes
- Foreign-key and lookup columns are indexed so contact-search and pipeline queries stay fast as a book of business grows. Critical multi-step writes run inside transactions so a partial failure rolls back cleanly rather than leaving inconsistent data.
- Error monitoring with PII scrubbed
- Application errors are reported to our monitoring with sensitive fields scrubbed before they leave the server: email addresses, phone numbers, and arbitrary PII strings are replaced with truncated SHA-256 hashes so we can correlate an error without leaking identities.
- Analytics consent-gated
- No analytics or advertising technology fires until the visitor accepts the cookie banner. Consent defaults to denied for analytics and ad storage on first paint; consent updates are pushed through only after the visitor opts in.
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. Sign-in is email + password (bcrypt-hashed). CSRF tokens enforced on every state-changing form.
- Multi-business, explicit membership
- A user can belong to several businesses; each membership is an explicit record. There is no implicit cross-business access - a user only sees businesses they have a membership for, and the database's row-level security enforces that scope too. 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 a per-tenant audit trail. You can see who did what, with what arguments, and what happened. We use this internally to debug; you see the same data in Settings.
- Per-tenant audit trail in Settings
- Each event records the tenant, business, the user or API key that made the call, the authentication method, the method and path, the tool name where applicable, PII-redacted arguments, the outcome status and error code, the duration, and any idempotency key. The trail is indexed for fast filtering by tenant, key, business, and tool.
- 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 by request
- To export your data or delete your account and its associated data, email security@onehub360.com. We fulfill verified requests, including right-to-be-forgotten requests, within 30 days. Self-serve export and deletion controls are on the roadmap; until they ship, every request is handled by a human.
- 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.