-
-
Notifications
You must be signed in to change notification settings - Fork 0
Security
Maestro's security model spans authentication, secret handling, agent-loop safety, input hardening, and data-deletion guarantees. This page collects the decisions; the mechanisms are cross-referenced to their pages.
-
Passwords — hashed with Argon2 (
PasswordHasher). -
JWT — HS256, three token types:
access,refresh,mfa(create_token/decode_tokenincore/security.py). Access token TTL 30 min, refresh 7 days (configurable). -
Refresh-token rotation + reuse-detection —
services/auth_service.py. Every refresh issues a new token in the samefamily_id(session lineage). If a already-rotated token is presented again (reuse), the entire family is revoked — a stolen refresh token can't outlive one use.list_active_sessions,revoke_family,revoke_other_families,logout. -
2FA (TOTP) —
services/two_factor_service.py:begin_setup(QR SVG),enable(returns recovery codes),verify_login. Login returns anmfachallenge; the client completes viaPOST /auth/login/totp. Recovery codes are Argon2-hashed and single-use.
- API keys encrypted with AES-256-GCM; the master key (
API_KEY_MASTER_KEY) is a 32-byte key held only in the environment. The TOTP secret uses the same key. See LLM-Providers-and-BYOK. - Keys are never logged, stored in plaintext, or returned to the frontend — only
provider,label,key_hint. - If a task starts without the required key, it stops and warns the user.
- Email verification / reset tokens store only a SHA-256 hash; the raw token lives only in the email link.
utils/rate_limiter.py — sliding-window limiter with a Redis Lua backend (atomic ZSET log) and an in-memory fallback (MemoryBackend, self-draining). The dispatcher tries Redis, and on RedisError/OSError/timeout logs a warning, falls back to memory, and opens a 10-second circuit breaker (RATE_LIMIT_REDIS_COOLDOWN_SECONDS). A Redis outage is not an API outage.
-
Identity — a valid access token keys by
user:{jwt_sub}, otherwiseip:{addr}. Behind a proxy (TRUST_PROXY_HEADERS=true), the IP is read from the rightmostX-Forwarded-Forentry (the hop the proxy adds — the one a client can't forge). -
Tiers (
constants.py, all 60s windows): PUBLIC 30, AUTH 20, READ 60, WRITE 20, PAYMENT 10, EXPENSIVE 30, UPLOAD 10, WEBSOCKET 30. Bucket keyrl:{tier}:{scope}:{identity}. - Every HTTP route declares an explicit
dependencies=[rate_limit(...)];tests/test_rate_limiter.py::test_every_http_route_declares_a_rate_limitfails the build if one is missing. WebSocket routes callcheck_websocketbeforeaccept()and close with1013on excess.
Deployment note:
TRUST_PROXY_HEADERSmust betrueonly behind a proxy that setsX-Forwarded-For(e.g. Caddy). Directly internet-facing with it on, a client forges the header and opens a new bucket per request; behind a proxy with it off, everyone shares the proxy's single bucket.
-
SSRF guard —
utils/url_guard.pycheck_public_urlblocks private/loopback/link-local targets. Applied to custom LLM endpoints and thedata_fetchtool (LLM_SSRF_GUARD_ENABLED). -
Prompt-injection scan —
utils/prompt_guard.pyscan_promptruns heuristics on system prompts. Mandatory on marketplace publish and on custom-agent writes; malicious patterns are rejected. -
Code-execution sandbox —
code_execution_service.pyruns Python in a throwaway Docker container with CPU/memory/time limits. Off by default in production (CODE_EXECUTION_ENABLED=false) because enabling it requires mounting the Docker socket = host takeover risk.
Infinite-loop and runaway-cost protection (see Agent-Orchestration):
-
MAX_ITERATIONS = 10per subagent. -
MAX_REVIEW_ITERATIONS = 3for the reviewer↔subagent loop. -
TASK_TIMEOUT_SECONDS = 1800total. - Hierarchical token budget (
TASK_TOKEN_BUDGET_DEFAULT = 200,000) with a per-callBudgetGuard; step-boundary quota re-checks. - Exceeding any limit stops the task, informs the user, and logs. Because the engine is durable, a killed task still records the tokens it spent.
All RAG queries filter by user_id; one user's memory can never surface for another. Custom agents run inside a sandboxed <agent_persona> block. Marketplace agents cannot read the installing user's keys directly — all provider calls go through the sandboxed service layer.
users.deletion_requested_at is the single source of truth.
-
Request (
DELETE /users/me) — account is immediately locked:ActiveUserreturns 403 on all product endpoints, but login/refresh still work so the user can self-serve undo. -
Grace period — 30 days (
ACCOUNT_DELETION_GRACE_DAYS).POST /users/me/deletion/cancelrestores it. Only an active subscription is cancelled on request. -
Purge — the cron job
python -m app.scripts.purge_deleted_accountsrunsuser_service.purge_user_dataafter the grace period.
Purge contract: delete order is Mongo → Qdrant → PostgreSQL last (the PG row carries the flag that lets the sweep re-find the account). purge_user_data raises rather than swallowing errors, so a failed run leaves the flag in place and the next sweep retries. All operations are idempotent. marketplace_items are not deleted — the author is unset and anonymized. GET /users/me/export provides the Art. 20 data export.
Errors flow to Sentry (env-gated, PII scrubbed, sendDefaultPii: false). Structured JSON logs (LOG_FORMAT=json) carry request_id / task_id / user_id. Agent fallbacks log at WARNING (below Sentry's ERROR event threshold) so one task raises at most one Sentry event. See Configuration and Deployment.
Maestro — source repository · Sustainable Use License v1.0 · This wiki documents the current code; where it differs from README.md, the wiki is authoritative.
Overview
Backend
- Backend-Reference
- API-Reference
- Database-Schema
- LLM-Providers-and-BYOK
- Security
- Billing-and-Quota
- RAG-and-Memory
- Realtime-and-WebSockets
Frontend
Operations
Project