> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vorel.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Security overview

> How Vorel protects your customer data: multi-tenant isolation, encryption, append-only audit, rate limiting, compliance posture.

Vorel handles customer PII on your behalf. This page documents the technical controls that protect that data: what's in place today and what's on the roadmap. For the formal compliance picture (DPA, certifications, regulator-facing language), see [Compliance](/security/compliance).

## Multi-tenant isolation

Every Vorel customer ("tenant") shares the same Postgres database. **Cross-tenant data leakage prevention is a 4-layer defence:**

<Steps>
  <Step title="Postgres Row-Level Security (RLS)">
    Every tenant-scoped table has a policy: rows are visible only when the row's `tenant_id` matches the current request's tenant context. The Vorel app connects as a non-superuser database role. Forgetting to set the tenant context returns zero rows: fail-closed by design.
  </Step>

  <Step title="Transaction-scoped tenant context">
    Every tenant-facing route opens a Postgres transaction and sets the current tenant context as the first statement. Exits cleanly on commit/rollback.
  </Step>

  <Step title="Operator-side admin lint">
    Operator-console reads use a separate database client that bypasses RLS by design (it's used for cross-tenant operator views like the tenants list). Every such query MUST filter by tenant explicitly. Our CI runs a static check that flags any cross-tenant query without an explicit tenant filter and blocks the PR. Legitimate cross-tenant queries (e.g. listing all tenants) need an explicit annotation explaining why.
  </Step>

  <Step title="Append-only triggers">
    Three tables (`messages`, `audit_log`, `billing_events`) reject UPDATE + DELETE via Postgres triggers. Even if RLS were bypassed, mutation isn't possible from the application's database role.
  </Step>
</Steps>

This setup was pressure-tested in a 2026-05-01 internal cross-tenant-leak walkthrough; the static lint shipped same-day to close the failure-mode class.

## Encryption

| Layer                     | Mechanism                                                                                                                                                                                                                                         |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **At rest (database)**    | Disk-level encryption (managed by our hosting provider)                                                                                                                                                                                           |
| **At rest (cache/queue)** | Disk-level encryption (managed by our hosting provider)                                                                                                                                                                                           |
| **In transit (internal)** | TLS within the private hosting network                                                                                                                                                                                                            |
| **In transit (public)**   | Edge-terminated TLS at `app.vorel.ai` (HSTS `max-age=63072000; includeSubDomains; preload`)                                                                                                                                                       |
| **CRM credentials**       | AES-256-GCM, 12-byte random IV per row, master key from a secret-store environment variable. A KMS-backed envelope-encryption path (per-row DEK + KMS-wrapped KEK) is implemented in code but dormant today; it activates when KMS is configured. |
| **Webhook outbound**      | HMAC-SHA256 signed bodies (`X-Webhook-Signature`) + TLS to your URLs                                                                                                                                                                              |
| **Tenant API keys**       | scrypt-hashed at rest (Node native, N=2^14, r=8, p=1, 64-byte derived key, per-key 16-byte salt); 58-char `vapk_live_<48 hex>` issuance shape; plaintext shown once at creation                                                                   |
| **JWTs**                  | HS256 over rotated shared secret, short TTL (5 min for tool calls, 2 min for worker calls)                                                                                                                                                        |

## Append-only audit

Three tables capture immutable audit trails:

* **`messages`**: every conversation turn, immutable once written. Underpins the conversation transcript that QA scoring + analytics rely on.
* **`audit_log`**: every operator-console read AND every mutation. Forensic trail for who-saw-what + who-did-what. Includes both operator actions (e.g. "claimed voice number for tenant X") and reads (e.g. "operator opened tenant X's CRM-credentials page").
* **`billing_events`**: every vendor cost-of-goods event (voice, telephony, and model spend per call) AND every chargeable event (per conversation, per minute). Immutable for accounting integrity.

Append-only is enforced via Postgres triggers: even superuser writes are blocked unless explicitly bypassed via a dedicated operator-side database role used for retention sweeps + the right-to-erasure path.

Recent audit action additions cover the new architectural surfaces: `pci_vault_redirect_issued` / `pci_vault_redirect_completed` / `pci_vault_redirect_failed` (PCI vault-redirect payment flows), `pci_enable_toggled` / `pci_vendor_changed` (per-tenant PCI configuration), `crm_as_sor_enforcement_disabled` (per-tenant CRM-write enforcement override), and `impersonation_started` / `impersonation_ended` (operator view-as-tenant sessions).

## Rate limiting

Vorel enforces a layered rate-limit stack to defend against floods, accidents, and runaway integrations:

| Layer                      | Limit        | Where                                             |
| -------------------------- | ------------ | ------------------------------------------------- |
| Per-IP webhook             | 500 req/min  | `/api/webhooks/*` (before signature verification) |
| Per-authenticated-user     | 200 req/min  | All authenticated dashboard surfaces              |
| Per-API-key                | 200 req/min  | Each issued `vapk_*` key has its own bucket       |
| Per-(tenant, tool)         | 50 req/min   | Each internal tool endpoint                       |
| Per-(tenant, agent-router) | 1000 req/min | The agent dispatch chokepoint                     |
| Per-tenant aggregate       | 5000 req/min | Across all authenticated surfaces                 |
| Per-customer-number (chat) | 30 req/min   | WhatsApp inbound, post-payload-parse              |

Built on a fixed-window primitive backed by our in-memory data store, with a **fail-open posture**: a transient cache outage admits all traffic rather than 429-everyone. Plan-aware limits land at the call site when our billing model launches.

## Right-to-access + right-to-erasure

Vorel ships first-class endpoints for PDPL Art. 15-17 + GDPR Art. 15-17:

* **`POST /api/tenant/export`** (operator-gated): returns a ZIP of `conversations + messages + leads + appointments + offerings + knowledge_base + audit_log` for a single tenant. Default redaction: customer email + phone redacted; opt-out via `include_full_pii=true` (audit-logged). Includes a chain-of-custody README.
* **`POST /api/tenant/forget`** (operator-gated, dry-run-by-default): runs a 7-step scrub for a single customer phone within a single tenant: tombstones conversations, redacts message content, nulls leads/appointments PII, scrubs audit-log JSONB references. Wrapped in a Postgres transaction for atomicity. Salted-hash audit-log references so the deletion event is provable without re-introducing the PII.

When a customer requests their data or asks to be forgotten, your operator routes the request through these endpoints + provides you with the resulting export ZIP or audit-log reference as proof of deletion.

## SLOs

We maintain a set of 5 service-level objectives:

| SLO                           | Target                                                                        |
| ----------------------------- | ----------------------------------------------------------------------------- |
| Tool surface success rate     | 99.9%                                                                         |
| Voice call success rate       | 99.5%                                                                         |
| Voice turn p95 latency        | \< 3s                                                                         |
| Operator console availability | 99.9%                                                                         |
| Outbound webhook delivery     | 99.0% (within 6 attempts; backoff ladder `[60s, 300s, 1800s, 7200s, 43200s]`) |

A 4-state error-budget policy (healthy / caution / concern / exhausted) drives engineering response.

## Observability + incident response

* **OpenTelemetry tracing** exported to a managed observability backend for distributed tracing + custom metrics dashboards.
* **Centralized error monitoring** for uncaught exceptions across web + workers.
* **Structured JSON logs** with W3C trace-context propagation across every cross-process boundary (web, workers, and workflow automation).
* **`app.vorel.ai/status`**: public status page with operator-curated incident banner.
* **Incident response playbook**: sev definitions, RACI matrix, 6-phase response, tenant comms templates aware of PDPL Art. 17 + GDPR Art. 33's 72-hour regulator-notification window.

## Compliance posture

| Standard                                    | Status                                                                                                                                                                                                                                                                                                                                              |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **PDPL** (UAE Personal Data Protection Law) | Designed to support PDPL obligations: PII inventory + DPA template + automated right-to-access + erasure paths are all live. Not a certification (PDPL has no certification scheme); the DPA template requires counsel review before signing                                                                                                        |
| **GDPR**                                    | Aligned with the same data-subject-rights paperwork posture. EU regions are captured as declared posture (`tenant.region='eu'`); per-region storage routing is on the roadmap, so EU-region data is hosted in our US region today and disclosed via the DPA                                                                                         |
| **SOC 2**                                   | **Not yet certified.** Foundational evidence is in place (PII inventory, data-flow diagram, incident-response playbook, control matrix, SLOs). SOC 2 **Type 1** is targeted for Q4 2026, contingent on selecting and onboarding a compliance-automation platform (founder decision pending). A Type 2 follow-on may follow for enterprise customers |
| **PCI**                                     | **SAQ-A: outsourced vault-redirect payment processing; PAN/CVC/expiry never touch Vorel infrastructure; CI lint gate refuses DTMF imports on payment paths.** See [Payments + PCI](/security/payments)                                                                                                                                              |
| **HIPAA**                                   | Not turn-key; available only via per-customer engagement (BAAs with all PHI-touching vendors). The clinic vertical pack ships with safety guards (`forbidden_phrases='diagnose'`) but a HIPAA-grade workflow requires a separate engagement                                                                                                         |
| **CRM-as-SoR**                              | Architecturally enforced: class-(c) transcript + PII tables purge on policy-locked TTL windows after CRM-write success. CI hard-fail gate refuses schema PRs that lack a class declaration. See [Data retention](/security/data-retention)                                                                                                          |

For the formal documents (DPA template, PII inventory excerpt for buyer DD, SLO commitments), see [Compliance](/security/compliance).

## Bug bounty + disclosure

We don't run a formal bug bounty today. To report a security issue: email `security@vorel.ai` with details. We'll acknowledge within 48 hours, triage within 5 business days, and credit you in the [changelog](https://vorel.ai/changelog) once fixed if you're comfortable with disclosure.

We do NOT use Vorel-collected customer data for any purpose other than running your tenant. We do NOT sell, share, or analyse customer data across tenants. We do NOT use customer conversations for training third-party AI models.

{/* verified-against: apps/web/src/lib/crm/credentials.ts AES-256-GCM 12-byte IV; KEK_ID='dev-local'; KMS path scaffolded but not wired */}

{/* verified-against: apps/web/src/lib/security/forget.ts hashCustomerIdentifier salted SHA-256 with RIGHT_TO_ERASURE_HASH_SALT */}

{/* verified-against: apps/web/src/lib/rate-limit.ts checkRateLimit fail-open posture + 7 layer call-sites (webhook, dashboard, API-key, per-tool, agent-router, tenant-total, customer-number) */}

{/* verified-against: handoff/docs/SLOs.md (5 SLOs: 99.9% tool / 99.5% voice / p95<3s voice turn / 99.9% operator console / 99.0% webhook) */}

{/* verified-against: handoff/docs/security/INCIDENT-RESPONSE.md + drills/2026-05-01-drill-2-cross-tenant-leak.md */}
