Security built into the database, not bolted to the service layer.
MetaERP enforces tenant isolation in Postgres with Row-Level Security on every business table. Auth flows pin JWT algorithms, rotate refresh tokens with reuse detection, and lock accounts after five failed attempts. We publish what we do and we mean it.
Four guarantees we hold ourselves to.
Tenant isolation, auth hygiene, audit trail, and edge hardening — defined, implemented, and documented. Not marketing claims.
Row-Level Security at the database
159 business tables RLS-enforced. Two PostgreSQL roles — app role (no BYPASSRLS) for tenant queries through pgbouncer, app_bypass_rls for pre-auth lookups and migrator. Tenant isolation is enforced at the database, not the service. A bug in the service layer cannot leak data across tenants.
- 159 RLS-protected tables
- Composite (tenant_id, fk_col) indexes on every FK
- pgbouncer transaction-mode pool
- BYPASSRLS only for system tasks
Auth that respects modern threats
JWT with pinned algorithm + enforced iss/aud/exp/iat claims. Family-based refresh token rotation with reuse detection (a replayed refresh kills the entire family). Account lockout after 5 attempts. Reset tokens are JTI-denylisted in Redis, 1h TTL, single-use.
- PyJWT alg-pinned
- Refresh family rotation w/ reuse detection
- Account lockout 5/15min
- Reset JTI denylist in Redis
Audit trail per row
Every business row has created_at, updated_at, deleted_at, tenant_id, company_id. Soft delete by default. The core_audit_logs table captures every state-changing action with the actor, before/after diff, and request ID. Searchable via the audit module.
- UUID v4 PKs (no exposed sequential ints)
- Soft-delete everywhere (no hard deletes)
- Audit log with actor + diff + request id
- BRIN index on created_at for fast time-range queries
Edge + transport
Caddy reverse proxy with CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy headers. HttpOnly + Secure + SameSite=Strict cookies. Rate limits on /auth endpoints. Per-tenant API caps by subscription tier.
- CSP + HSTS + 4 other headers
- HttpOnly+Secure+SameSite=Strict cookies
- /auth/login 10/min + /auth/forgot 3/hr
- Per-tenant rate caps by tier
Aligned with how SMBs are actually audited.
No vague claims. Each framework gets a specific status — live, in progress, on request, or planned — and an honest description of what it covers.
SOC 2 Type II
In progressAudit in progress; report available under NDA. Controls cover security, availability, confidentiality.
GDPR
LiveData processing addendum on request. EU + US data residency choice. DSAR workflows in the audit module.
CCPA
LiveRight to access + delete handled via audit module DSAR workflow.
HIPAA-ready
On requestBAA available for healthcare-adjacent customers. Encrypted at rest + in transit, audit log per access.
ISO 27001
PlannedRoadmap 2026 Q4.
PCI DSS
Out of scopeTokenized payments via Stripe — MetaERP never stores card numbers.
How a single API request is secured, end to end.
Five enforcement points between client and row. Each one is independent — defense in depth means a slip at one layer is caught by the next.
- ClientBrowser or mobile · access token in HttpOnly cookie
- Caddy edgeTLS termination · CSP + HSTS · rate-limit
- FastAPIJWT validate (alg-pinned) · Pydantic schema validate
- RLS context setSET LOCAL app.tenant_id · app.company_id
- PostgreSQLRLS-filtered query · through pgbouncer transaction pool
The edge is the first filter. Caddy terminates TLS, applies the full set of response headers, and rate-limits unauthenticated endpoints. A flood on /auth/login never reaches FastAPI.
The service layer enforces shape. PyJWT validates the access token with the algorithm pinned and the iss/aud/exp/iat claims required. Pydantic validates every request body and query param. Reject early, reject loud.
The database enforces ownership. Before any query runs, the request's tenant_id and company_id are written into the Postgres session as RLS context. Every SELECT, UPDATE, and DELETE is then filtered by the per-table policy. A bug in service code cannot return a row from another tenant — the row never comes back.
Built to survive bad days, too.
Continuous backup, tested recovery, residency choice, and customer-controlled encryption. The operational floor you need before signing the MSA.
Backups
Continuous WAL archiving + nightly base backups. 30-day point-in-time recovery on Growth+. 90-day on Enterprise.
RTO / RPO
Recovery time objective: 1 hour. Recovery point objective: 15 minutes. Tested quarterly.
Data residency
EU (Frankfurt) or US (Virginia). Customer choice at provision. No cross-region replication without explicit consent.
BYOK
Bring-your-own-key encryption available on Enterprise. AWS KMS or GCP KMS, your account, your rotation policy.
Found something? Tell us. We'll respond.
We run a private bug bounty with a 30-day SLA on triage. Critical issues get same-day response. Hall of fame for credited researchers.
Email security@metaerp.com. Please give us reasonable time to remediate before public disclosure. We do not pursue legal action against good-faith research.
- 30-day triage SLA, same-day for criticals
- Acknowledged in our security hall of fame
- Safe harbor for good-faith research
security@metaerp.com
One human reads every inbound. Encrypted reports welcome — GPG key available on request, replied with the fingerprint inline.
Want a security review walkthrough?
Our CTO will walk your team through the RLS implementation, JWT flow, and audit architecture. 45 minutes, live.