Qorven is designed to run on untrusted or semi-trusted infrastructure. The threat model assumes: the operator trusts themselves but wants to defend against accidental leaks, compromised LLM outputs, malicious users on the same install, and loss of the host machine.
Non-goals
Up front, what Qorven is not trying to protect against:- A root user on the host. If someone has root on the Qorven box, they can read memory and decrypt the vault. No amount of in-process crypto helps against root.
- A compromised LLM provider. If OpenAI returns a poisoned response, the agent loop processes it.
- Side-channel leaks via chosen prompts. A sufficiently clever user prompt can always smuggle information out.
- Cross-tenant data leaks on a multi-tenant install
- Secret exfiltration via regular HTTP requests (vault is encrypted at rest)
- Prompt-injection from web pages / emails / PDFs fed to the Qor
- Accidental destructive operations (approval flow)
- Passive network observers (TLS on by default)
- Replay attacks (time-bound auth tokens + audit trail)
The six pillars
Per-install encryption
A 256-bit key generated once at install encrypts every secret. Lose the key, lose the secrets — no backdoor.
Tenant isolation via RLS
Every Postgres table has row-level security. Queries run in a tenant-scoped transaction; even a bug in a handler can’t leak across tenants.
TLS by default
Local CA generated at install. Let’s Encrypt for public domains. BYO cert supported. No HTTP option without explicit opt-in.
Audit trail
Every mutation + tool call lands in the
audit_log table with actor, tenant, tool, args, result, timestamp.Prompt-injection defence
Tool calls parsed through a policy layer that catches known injection patterns. Destructive tools require approvals.
PII redaction
Opt-in filter that strips emails, phones, cards, SSNs from memory writes. Protects both users and the training set any Qor builds from memory.
Data flow with trust boundaries
- LLM output is untrusted. Even from OpenAI. Never directly execute LLM-generated shell commands without the approval gate.
- Scraped content is untrusted. A malicious page can contain prompt-injection payloads crafted to hijack tool calls. The policy layer catches known patterns; not all.
- Email content is untrusted. Emails can contain injection payloads aimed at an email-reading Qor. Use
destructive_tools_require_approval=truefor any email-handling Qor.
Secrets lifecycle
Generated at install
encryption_key (32 bytes from /dev/urandom) and auth_token (16 bytes). Written to /etc/qorven/config.toml with 0640 permissions owned by qorven:qorven.Provider keys / OAuth secrets arrive
User pastes into Settings → Provider Keys. Gateway immediately encrypts with AES-256-GCM keyed by
encryption_key.Stored in Postgres as bytea
provider_keys.key_enc is ciphertext. key_hash is SHA-256 for dedup without decryption. Neither is reversible without the install’s key.Decrypted in-memory just before use
Agent loop pulls the ciphertext, decrypts, calls the LLM. The plaintext never crosses a process boundary or lands on disk.
Multi-tenant boundaries
Every Postgres table is tenant-scoped viatenant_id UUID NOT NULL. At the gateway:
tenant_id != current_setting('app.current_tenant')::uuid. Bug in a handler that forgets to filter? RLS still blocks. Tenant isolation →
Destructive-action approvals
Tools can declare themselves destructive in their metadata:approval_required mode, any destructive call pauses the loop and emits an approval_required event. The operator sees a prompt in chat: “Approve executing rm -rf /tmp/staging?” Yes / No / Yes-always-for-this-command. Approvals flow →
Prompt-injection mitigations
Layered, not perfect:Input-side sanitisation
Input-side sanitisation
When the agent loop pulls in external content (scraped page, email body, PDF extract), it prefixes it with an injection-resistant wrapper:
<external_content source="..." trust_level="low">...</external_content> so the LLM sees it as data, not instructions.Pattern detection
Pattern detection
Known injection patterns (
"ignore previous instructions", "system:", "</assistant>") are flagged before the LLM call. Flagged content gets an extra wrapper warning the LLM to treat it as adversarial.Tool call validation
Tool call validation
The gateway parses every tool call emitted by the LLM against a JSONSchema. Malformed or out-of-schema calls are rejected before execution.
Destructive-call approval
Destructive-call approval
Even if injection succeeds, destructive tools require human approval unless explicitly configured otherwise. Shifts from “don’t let it happen” to “catch it at the last moment.”
SSRF guard
SSRF guard
The
web_fetch / browse / scrape tools go through an SSRF allowlist. Default-deny for private IP ranges. SSRF config →What’s audited
Every mutation with a named subject. Examples:/var/log/qorven/audit.jsonl) for redundancy. Audit UI →
Upgrade path
Security patches land as minor releases.qorven update verifies SHA-256 against the release’s sidecar before swapping binaries. Migrations run automatically on next boot. Self-update →
Where next
Threat model deep-dive
Enumerated threats, mitigations, residual risks.
Per-install encryption
Key format, algorithms, rotation.
PII redaction
Config, what patterns are caught, how to extend.
Audit trail
Schema, retention, export.