1. Overview
We treat security as a product requirement, not a compliance afterthought. Zybo AI hosts conversations between your team, your AI agents, and your end users — three audiences whose data must never bleed into each other. The architecture below is what makes that guarantee enforceable rather than aspirational.
The short version — Every database row that holds chat or knowledge-base data carries a tenant identifier. Every query filters on it. A vector search for AI agent A cannot return AI agent B's documents because the filter sits in the SQL WHERE clause before the similarity scoring runs.
This page summarizes the controls in production today. Items still on the roadmap are clearly marked; everything else is in the backend you're already using.
2. Tenant isolation
Multi-tenancy is the most consequential design decision in a SaaS that processes customer knowledge bases. We separate two distinct concepts:
- Organization. The billing and team unit. Owns plan, subscription, users, and AI agents.
- Tenant. The data-isolation unit. Each AI agent has its own
tenant_id; all of that agent's documents, document chunks, conversations, messages, and prompt templates carry the sametenant_id.
Why per-AI-agent tenants and not per-organization? Because an organization may run two AI agents with totally different audiences (e.g. a customer-support agent and an internal HR agent) — and an HR document accidentally retrieved by the support agent's RAG pipeline would be a serious data leak. Tying the tenant boundary to the AI agent makes that mathematically impossible.
This separation is enforced at the application layer:
- Database schema. Every table that holds RAG / chat data —
documents,document_chunks,conversations,messages,prompt_templates— carries a non-nulltenant_idwith a foreign-key constraint to the tenants table. - Query layer. Every query that reads from those tables is required to include a
WHERE tenant_id = $1clause. Thetenant_idis derived from the authenticated request context (JWT or API key) — never from a client-supplied value. - Vector search. Similarity queries pre-filter by
tenant_idin the SQLWHEREclause before the ORDER BY ...<=>scoring runs. Keyword search uses the same pre-filter pattern. There is no code path by which a query for one tenant can score against another tenant's chunks.
3. Authentication
Authentication uses short-lived access tokens plus rotating refresh tokens, with multiple supported entry points:
- Email + password (bcrypt with cost factor 12).
- Magic-link sign-in (15-minute single-use tokens; SHA-256 hashed at rest).
- OAuth (Google — Microsoft is implemented but not enabled by default; contact support if you need it).
- API keys for server-to-server widget and API access, scoped per AI agent.
Refresh tokens are stored as one-way SHA-256 hashes and rotated on every use — using an old refresh token revokes the new one and forces a fresh sign-in, which is how we detect replay attacks. Access tokens are JWTs with a 1-day lifetime; refresh tokens have a 7-day lifetime by default.
AI-agent API keys are SHA-256 hashed in the database (for lookup) and additionally encrypted with AES-256-GCM (so the dashboard can reveal the raw value to an owner / admin on demand). Revoked keys are periodically purged by the lifecycle worker.
Two-factor authentication (TOTP) for dashboard sign-in is on the roadmap.
4. Encryption
Data is encrypted in transit and at rest:
- In transit. TLS 1.2+ for all customer traffic, terminated at the Cloudflare edge with HSTS. Internal service-to-service traffic runs over the private container network.
- At rest — application-layer secrets. AI-agent API keys (which we must be able to re-display to an owner) and OAuth refresh tokens for customer-connected identity providers are encrypted with AES-256-GCM. Encryption keys are derived from a separate
API_KEY_ENCRYPTION_KEYenvironment secret, isolated from the application's JWT signing key. - At rest — one-way hashes. Passwords (bcrypt cost 12), refresh tokens, magic-link tokens, password-reset tokens, email-verification tokens, OTP codes — all stored as one-way SHA-256 hashes (bcrypt for passwords). The raw values cannot be recovered from the database.
- At rest — attachments & documents. Files uploaded to AI agents and chat conversations are stored in Cloudflare R2, which encrypts every object at rest with AES-256 by default. Files are served via short-lived signed URLs.
5. Audit log
An append-only audit log captures every authentication event and every state-changing API call. Each entry records the actor (user or API key), the action, the affected resource, the source IP, the user agent, and a timestamp. Owners and admins can review the log from the dashboard. The current action vocabulary includes (non-exhaustive):
- Auth: sign-up, sign-in (success/failure), sign-out, password change, email change, OAuth identity linked, magic-link consumed.
- AI agents: created, operation-mode updated, API key created / updated / revoked / rotated.
- Conversations: assigned, auto-assigned, reassigned (admin or peer), taken over, resolved, released, reassigned to AI, escalated, priority updated.
- Knowledge base: document uploaded, text added, website crawl started / cancelled, re-ingested.
- Workspace: created, deletion scheduled / cancelled, ownership transferred.
- Subscription: currency-change scheduled / cancelled (plan upgrades and downgrades are audited via Razorpay webhook events).
- Team: invites accepted, human-agent profile created, agent status updated.
A separate security_events table records platform-level events: prompt-injection detections, rate-limit violations, budget exceedances, provider failovers, and unauthorized-access attempts. These are indexed by tenant and severity for fast triage.
Audit logs and security events are retained for the lifetime of your account. They are not currently shipped to a third-party SIEM.
6. Hardening
The chat pipeline includes several inline defenses, applied in order:
- Prompt-injection detection. Every user query is scanned for known injection patterns (role override, persona injection, system-prompt extraction, encoding tricks) before it enters the RAG pipeline. Strictness is configurable (low / medium / high — default medium); high-confidence attacks are blocked and the rest are logged to
security_events. - Output filtering. Model responses are sanitized before delivery — script tags and event handlers are stripped, obvious PII patterns (SSN, credit-card numbers) are redacted, and apparent system-prompt leakage is filtered.
- Rate limiting. Redis-backed sliding-window counters enforce per-tier, per-AI-agent limits scoped by minute:
| Plan | Chat requests / min | Ingestion / min |
|---|---|---|
| Free | 20 | 10 |
| Starter | 60 | 30 |
| Pro | 120 | 60 |
| Scale | 240 | 120 |
- A generic API limit of 120 requests/min applies to other endpoints. Rate-limited requests return
429with aRetry-Afterheader. The limiter fails open if Redis is unreachable so a Redis outage doesn't cascade into a chat outage. - Per-organization quota & overage. Each plan has a monthly included-query quota and a hard cap; usage beyond the included quota is billed at the per-query overage rate (Free blocks at the cap rather than incurring overage). Quota state is exposed on the dashboard in real time.
- Provider failover. Wrapper classes for LLM and embedding providers exist so a primary outage can transparently fail over to a secondary. This is opt-in per environment via platform configuration — the default deployment runs a single primary.
7. Reporting vulnerabilities
If you believe you have found a security vulnerability in Zybo AI, please report it to security@zybo.ai. Encrypt sensitive details with our PGP key (available on request) if you prefer.
We commit to:
- Acknowledging receipt within two business days.
- Providing an initial assessment within five business days.
- Keeping you updated on remediation progress.
- Not pursuing legal action against researchers who follow this responsible-disclosure policy in good faith.
Please do not exploit a vulnerability beyond what is necessary to demonstrate it, and do not access data that is not your own.
Questions about this document? Email support@zybo.ai or visit our contact page.