Email: kacper.wlodarczyk@vstorm.co (or open a private security advisory on the repo). Please include:
- Affected version / commit
- Steps to reproduce
- Impact assessment (data exposure / privilege escalation / DoS / …)
We aim to acknowledge within 48h and ship a fix within 7 days for high-severity issues.
- JWT (
HS256) signed withSECRET_KEY. Access token TTL =ACCESS_TOKEN_EXPIRE_MINUTES(default 30 min). Refresh token TTL =REFRESH_TOKEN_EXPIRE_MINUTES(default 7 days). - Password hashing: bcrypt via
passlib. Plain passwords never persisted. - OAuth 2.0 (Google) — auth-code flow. Token validated server-side, internal user record looked up/created by email.
- Session management — DB-backed sessions with revocation. Each refresh-token issuance creates a session row;
/sessionsendpoint lets users see + revoke devices. - Admin API key — static
settings.API_KEYmatched viaX-API-Keyheader for service-to-service calls. Constant-time compared withsecrets.compare_digest().
- Permission-based — authority inside an organization is a membership row plus the permission catalog (
app/core/permissions.py). There is no role column on the user and no role-based route dependency. - Org roles — a role is a name on the membership (
owner/admin/builder/operator/member/viewer) that maps to a set of permissions. Collection routes gate on a permission; per-resource access resolves the role together with explicit grants, and a grant widens what a role allows — it never narrows it. See Permissions. - Workspace scope — every authenticated request resolves an
ActiveOrg(default = personal org). Resources scoped byorganization_idforeign key. - Deployment administration — the
is_app_adminflag on a user, checked by its own dependency; not a role.
- CORS — origin list from
settings.CORS_ORIGINS. Restrict to your domains in production. - HTTPS — enforce via reverse proxy (Nginx / Traefik / ALB). Strict-Transport-Security header set in middleware when
ENVIRONMENT=production. - CSP — frontend sets
frame-ancestors 'none'by default to prevent click-jacking. Seefrontend/next.config.tsheaders block.
- Secrets — read from environment via
pydantic-settings. Never committed. Seebackend/.env.exampleand Configuration. - Audit log — app-admin actions (user updates, deletes, impersonations) recorded in the
app_admin_audit_logstable with actor + IP + payload snapshot. Organization-level actions that change access or spend money carry their own trail, gated byaudit:read— see Governance. - RAG documents — file uploads scoped per-org. No public read endpoint; all retrieval happens server-side during chat.
- Rotate
SECRET_KEYandAPI_KEYfrom generated defaults. - Set
DEBUG=falseandENVIRONMENT=production. - Restrict
CORS_ORIGINSto your domain(s). - Tune
RATE_LIMIT_RUN_PER_MINUTE/RATE_LIMIT_EMBED_PER_MINUTEin.env. - Review the rate limits on every public surface — the embed widget's
messages-per-visitor limit and each channel bot's per-sender
rate_limit_rpm. The console's own routes are not metered. - Behind a proxy or CDN, set
RATE_LIMIT_TRUST_FORWARDED_FOR=trueand make sure the API is not also reachable directly — otherwise every visitor shares one bucket, or the header can be forged. The limiter reads the rightmostX-Forwarded-Forhop (the one your proxy appended), so put exactly one trusted proxy in front; with two, collapse the header to a single hop at your edge. - Enforce HTTPS at the proxy layer.
- Run
pip-audit/bun auditin CI for dependency vulnerabilities. - Configure database backups + restore test schedule.
- No 2FA / MFA out of the box.
- No SAML / OIDC beyond Google OAuth. Enterprise SSO needs custom IdP integration.
- No automatic PII redaction in logs — be careful what you log.