Security Model¶
Numi Chat checks account sessions and resource ownership at the web boundary, limits which tools a chat can use, and sandboxes generated Python by default. The host, database, operator configuration, and installed Python tool modules remain trusted parts of the deployment.
For public deployment steps, use Run with Docker. For data storage and external services, see Privacy and Self-Hosting.
Threat Model¶
| Boundary | What it controls |
|---|---|
| Anonymous to authenticated | Access to chats, uploads, settings, and memory |
| One account to another | Ownership checks on chat and user data |
| User to administrator | User management, instance settings, and model catalog writes |
| Model to tool | Registered and chat-active tool names, tool input checks, server-owned context |
| Generated Python to host | Code interpreter validation and its selected sandbox |
Administrator access does not make ordinary chat routes ignore ownership. Administrators manage users and configuration through separate endpoints. The operator can still access the host's database and files outside the application.
Tools that fetch remote content process untrusted input. Tool policy and validation reduce exposure; they do not make every installed tool or external response trustworthy.
Authentication: JWT with Short-Lived Tokens¶
Access tokens contain a stable user subject and a session ID. Authentication
checks both the JWT and an unexpired, unrevoked session record in the
refreshtoken table. It is not stateless JWT-only authentication.
The main implementation is in services/auth.py, services/auth_flow.py, and
services/access.py under src/numi_chat/.
Token Expiration Strategy¶
Defaults are 30 minutes for access tokens and 7 days for refresh tokens.
Refresh tokens are stored as hashes in the database. The server sets access
and refresh cookies with HttpOnly and SameSite=Lax; outside development it
also sets Secure.
Refresh Token Flow¶
- Login or registration creates a session and returns a token pair.
- Refresh validates the submitted token, revokes that token's stored handle, and issues a new pair for the same session ID.
- Logout revokes the refresh token supplied in the cookie and clears auth cookies. Access checks require a remaining valid session record.
- Password changes and administrator resets revoke the user's stored sessions. Username changes revoke prior sessions and issue a new pair for the caller.
WebSocket connections recheck session validity while connected. Revocation is therefore relevant before the access JWT's nominal expiry; it does not undo an action that has already completed.
Password Security¶
Passwords are SHA-256 pre-hashed and base64 encoded before bcrypt hashing.
The default bcrypt work factor is 13. Registration requires a username matching
[A-Za-z0-9_-]+ with 3–50 characters and a password of at least 12 characters;
the configured password policy also requires mixed case, a digit, and a
supported symbol by default.
Account lockout¶
Login throttling stores account and IP tracking keys as keyed hashes in
auththrottlebucket, rather than process-local counters. Repeated failures
trigger delay and temporary blocking; successful login clears the account's
failure state.
The controls are AUTH_MAX_FAILED_LOGIN_ATTEMPTS,
AUTH_ACCOUNT_LOCKOUT_MINUTES, AUTH_IP_LOGIN_ATTEMPTS_PER_MINUTE,
AUTH_MAX_LOGIN_DELAY_SECONDS, and AUTH_THROTTLE_RETENTION_HOURS.
User Registration Security¶
On a fresh empty database, the first account becomes administrator. Public
registration is then disabled unless an administrator enables the application
setting. This is stored in appsetting, not an environment variable.
Tool Safety Boundaries¶
Allowlist/Denylist System¶
The startup registry admits ordinary tools only when allowlisted and not
denylisted. The denylist wins. request_tool_activation is exempt from the
allowlist but can still be denylisted and is omitted from a chat's active tools
when there are no available disabled custom groups.
Custom tools also require chat activation. Requesting activation does not enable a group; the user makes that choice. Before execution, the executor checks the active names and injects server-owned user/chat identifiers over any model-supplied values with the same names.
Availability checks can be deferred by a tool; a registered tool is not proof of a healthy external service. See Tool System Design for discovery, execution, and error behavior.
Code Interpreter Sandboxing¶
The interpreter tries Monty for eligible scripts without third-party dependencies, with Deno/Pyodide through mcp-run-python as a fallback and for package-based execution. Each call receives its own execution state.
It validates code and dependency requests, provides validated files from the current chat's uploads and generated artifacts, and bounds execution, output, and saved files. File access is not limited to paths explicitly mentioned in the current prompt: validated current-chat files are provisioned for reuse.
Networking is disabled by default. Deno may use networking to load packages
before revoking that permission for user code. The optional
TOOLS_CODE_INTERPRETER_UNSANDBOXED=true path runs host Python with the
server account's permissions and removes this sandbox boundary.
The interpreter sandbox does not apply to other installed Python tools. Timeouts are not a general memory quota; use OS or container resource limits where the deployment needs them. See Tool Access for configuration.
Rate Limiting¶
SlowAPI handles HTTP rate limits with process-local memory. Login-specific
account/IP throttling uses the database. WebSocket connections and active
turns have additional application limits controlled by WEBSOCKET_*.
Separate worker processes do not share the in-memory HTTP limiter or active turn manager. A deployment with multiple workers needs coordination beyond those per-process limits.
Security Headers¶
The security middleware sets content-type, framing, referrer, permissions, and content-security policies. Production responses also include HSTS. The current content-security policy permits inline scripts and styles; it is not a guarantee that unsafe HTML can be inserted into the UI.
Exact header values are in src/numi_chat/web/middleware.py. The generated
OpenAPI schema and documentation routes are enabled only in development.
CORS Configuration¶
Set CORS_ALLOW_ORIGINS to the actual trusted browser origins. WebSockets have
an additional origin check: a supplied origin must match a configured origin
or the request's own origin; a connection without an origin requires bearer
header authentication. * is not accepted as a WebSocket trusted origin.
The app currently uses SameSite=Lax cookies and restricted CORS. Its optional
CSRF-token middleware is not enabled in web/app.py. Changing cookie or origin
policy therefore requires reviewing those controls together.
Data Isolation¶
Chat routes resolve a chat against the authenticated owner. HTTP ownership failures generally return 404. Uploads and generated files use chat UUIDs and validated paths; generated downloads are served through an authenticated route, not the public static directory.
Returned reasoning is stored alongside messages even when replay is disabled. Changing the preservation setting is not a data-deletion operation. See Preserved Reasoning and Database Schema for storage behavior.
Security Checklist¶
Before exposing an instance publicly:
- Set a strong, private
AUTH_SECRET_KEYand retain it securely. - Create the first administrator account during controlled setup.
- Use HTTPS and restrict backend access to the trusted reverse proxy.
- Set exact trusted origins and the correct
FORWARDED_ALLOW_IPSvalue. - Keep the interpreter sandbox enabled and admit only required tools.
- Restrict host access to the database, credentials, uploads, and logs.
- Keep backups and a tested restore procedure for the database and runtime files.
These are deployment controls, not a claim that all possible vulnerabilities have been excluded.