Skip to content
3 min read · 652 words

Security Model

Current posture in one sentence: the platform trusts its network completely — there is no authentication, no authorization, and no rate limiting — which is acceptable for local development and nothing else.

Anyone who can reach port 8001 can create agents, read every conversation, upload documents, trigger paid OpenAI calls, and delete data. Deployment guidance in Deployment assumes a private network until the hardening items below land.

What exists today

ControlStatusWhere
Secret managementOPENAI_API_KEY read from .env (gitignored), never logged, never returned by any endpointservices/*.py
Server-side LLM calls only✅ The browser never holds the OpenAI key; all calls originate in the backendwhole design
Tool authorization per agentenabled_tools allow-list checked before any tool runs (403 otherwise)agent_has_tool() in both routers
Tenant isolation for vectors✅ Every Chroma query filters where={"agent_id": ...}document_service.search_agent_documents
Input validation✅ Pydantic schemas on every body; upload restricted to UTF-8 .txtschemas.py, documents.py
SQL injection resistance✅ All queries go through the SQLAlchemy ORM (parameterized)models.py usage
Destructive-action guard⚠️ Partial — task status changes validated against an allow-list; the (buggy) delete path requires ?force=true when data existsrouters/agents.py
User authentication❌ None — no users, sessions, tokens, or API keys
Authorization / roles❌ None — every caller can do everything
Rate limiting / quotas❌ None — unbounded paid OpenAI spend per caller
CORS policy❌ Not configured (no CORSMiddleware) — browsers cannot call the API cross-origin, which the frontend sidesteps via server-side fetching
HTTPS / TLS❌ Local HTTP only
Audit trail⚠️ Operational logging exists (ToolCall/runs) but records no actor identity

Trust boundaries

Rendering diagram…

The only real boundary today is outbound: the OpenAI key stays server-side, and prompts sent to OpenAI are subject to OpenAI's data-usage policies — treat uploaded documents and emails accordingly.

Prompt-injection surface

LLM inputs are user-controlled (email_text, chat messages, uploaded documents). Mitigations currently in place are structural rather than filtering:

  • The email analyzer, evaluator, and reviewer demand strict JSON output and the backend validates it with json.loads, raising on garbage — a malicious email can skew content but cannot change shape.
  • The RAG answer prompt instructs the model to use only the provided context and to refuse otherwise; the corrective evaluator independently gates generation (Corrective RAG).
  • Task creation from action items is bounded: only title, priority, deadline fields flow into Task rows, and normalize_due_date sanitizes dates.

There is no content filtering or output moderation; a hostile email can still produce a hostile suggested reply. The Reviewer Agent (approved, issues) is the current line of defense — treat its verdict as advisory, not enforcement.

Hardening checklist for production

Priority-ordered; tracked on the Roadmap:

  • Authentication — API keys or OAuth2/JWT via FastAPI dependencies; scope every /agents/{id}/* route to the caller's tenant.
  • Authorization — ownership checks (which user owns which agent) on top of the existing tool gating.
  • Rate limiting & spend caps — per-caller request limits and a daily OpenAI budget circuit breaker (the estimated_cost data already exists to build on).
  • CORS middleware — explicit allowed origins if the browser ever calls the API directly.
  • TLS termination — reverse proxy (Caddy/Nginx) or platform TLS.
  • Secrets manager — move .env to platform secret storage in deployment.
  • Upload limits — max file size on /documents/upload (currently unbounded).
  • Structured audit logging — attach actor identity to ToolCall/AgentRun rows.
  • Configuration — secret handling in development
  • Error Handling — what leaks in error messages (exception strings are returned in detail)
  • Roadmap — when hardening is planned