Security Features¶
Depictio implements comprehensive security measures to protect your data and ensure safe operation in production environments.
Authentication¶
JWT-Based Authentication¶
Depictio uses JSON Web Tokens (JWT) for secure authentication:
| Feature | Description |
|---|---|
| Token Security | Public/private key encryption (RS256) |
| Algorithm Pinning | Algorithm pinned server-side (RS256/RS512); alg=none and algorithm-confusion attacks are rejected before any MongoDB access |
| Claim Validation | Signature and exp claim are both verified on every request; expired tokens are rejected immediately |
| Session Management | Configurable token lifetime and refresh |
| Secure Storage | Tokens stored in HTTP-only cookies |
| Token Refresh | Automatic refresh before expiration |
Authentication Flow¶
┌─────────┐ ┌─────────────┐ ┌─────────────┐
│ User │────▶│ Login API │────▶│ JWT Token │
│ │ │ │ │ (signed) │
└─────────┘ └─────────────┘ └──────┬──────┘
│
┌─────────────────────▼──────┐
│ Subsequent API Requests │
│ (Token in Authorization) │
└────────────────────────────┘
Registration Hardening¶
The /register endpoint strips the is_admin field from any incoming payload, so self-promotion to admin at registration is not possible. Admin privileges are granted exclusively via the /auth/turn_sysadmin endpoint, which enforces two guards: at least one admin must remain in the system at all times (last-admin guard), and a user cannot demote themselves (no-self-demote guard).
Session Configuration¶
| Variable | Default | Description |
|---|---|---|
JWT_ACCESS_TOKEN_EXPIRE_MINUTES |
30 | Access token lifetime |
JWT_REFRESH_TOKEN_EXPIRE_DAYS |
7 | Refresh token lifetime |
JWT_ALGORITHM |
RS256 | JWT signing algorithm |
Role-Based Access Control (RBAC)¶
Depictio implements a hierarchical permission system for fine-grained access control.
User Roles¶
| Role | Description | Typical Use |
|---|---|---|
| Admin | Full system access | System administrators |
| User | Standard access | Project members |
| Viewer | Read-only access | Collaborators, stakeholders |
Permission Levels¶
Permissions are applied at multiple levels:
┌─────────────────────────────────────────┐
│ System Level │
│ (Admin: user management, settings) │
├─────────────────────────────────────────┤
│ Project Level │
│ (Owner, Editor, Viewer per project) │
├─────────────────────────────────────────┤
│ Dashboard Level │
│ (Inherited from project permissions) │
└─────────────────────────────────────────┘
Project Permissions¶
| Permission | Admin | Editor | Viewer |
|---|---|---|---|
| View dashboards | Yes | Yes | Yes |
| Create dashboards | Yes | Yes | No |
| Edit dashboards | Yes | Yes | No |
| Delete dashboards | Yes | No | No |
| Manage project settings | Yes | No | No |
| Manage project members | Yes | No | No |
Group-Based Access¶
Users can be organized into groups for easier permission management:
- Groups inherit project permissions
- Individual users can have additional permissions
- Permission inheritance follows the most permissive grant
Code Execution Security (Code Mode)¶
When using Code Mode for custom visualizations, Depictio implements strict security measures.
RestrictedPython Security¶
Depictio uses RestrictedPython (Zope Foundation) for secure code execution:
| Feature | Description |
|---|---|
| Battle-Tested | Used in production by Zope/Plone for 20+ years |
| Compile-Time Restrictions | Unsafe operations blocked during compilation |
| Safe Execution Environment | Pre-approved globals and built-ins only |
| No System Access | File system, network, and OS operations blocked |
Security Architecture¶
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ User Code │────▶│ compile_ │────▶│ Restricted │
│ (Python) │ │ restricted() │ │ Bytecode │
└─────────────────┘ └─────────────────┘ └────────┬────────┘
│
┌────────────────────────────────▼────────┐
│ Safe Execution Context │
│ • Approved libraries (plotly, pandas) │
│ • Custom guards (_getitem_, _getattr_) │
│ • Read-only DataFrame copy │
└─────────────────────────────────────────┘
Allowed Operations¶
# Available libraries in Code Mode
import plotly.express as px # Visualization library
import plotly.graph_objects as go # Advanced plotting
import pandas as pd # Data manipulation
df # Your dataset (read-only copy)
# Safe built-in functions
len(), range(), str(), int(), float(), sum(), min(), max()
list(), dict(), tuple(), set(), sorted(), reversed()
Blocked Operations¶
RestrictedPython prevents these operations at compile-time:
| Category | Blocked Operations |
|---|---|
| File Operations | open(), file I/O, filesystem access |
| Network Access | requests, urllib, socket operations |
| System Calls | os.*, sys.*, subprocess, shell commands |
| Dangerous Built-ins | exec(), eval(), __import__(), compile() |
| Attribute Access | Private attributes (underscore methods) on unsafe objects |
Past versions in a render (v1.15.0+)¶
A render request can draw a component as a past dashboard version
defined it. The request names that version with definition_version, and the server
reads the component from its own record of the version. A request cannot send a
component definition, so the code of a code-mode figure always comes from a saved
dashboard.
- A request that carries
component_overridesis refused with 400. - A version that no longer exists, or belongs to another dashboard, is refused with 400.
- A component that read another data collection in that version is refused with 409.
Data Protection¶
Encryption¶
| Data State | Protection |
|---|---|
| In Transit | TLS 1.2+ for all HTTP communication |
| At Rest | MongoDB encryption, MinIO server-side encryption |
| Tokens | RS256 asymmetric encryption |
Data Isolation¶
- Project boundaries: Data strictly isolated between projects
- User isolation: Users can only access authorized projects
- Delta Lake: Versioned data with access controls
- File-delete IDOR fix: File-deletion permission is checked against
current_user.is_admin(the requesting user's flag), not the file-owner's flag. This closes an IDOR where a non-admin could delete files owned by an admin account.
Input Validation¶
All user input is validated:
- Pydantic models for API request validation
- Sanitization of user-provided text
- Type checking at API boundaries
- Size limits on uploads and queries
Remote Data Sources (v1.16.0+)¶
The url, s3_prefix and manifest scan modes, and creating a project from
a run folder, make the server read locations a user supplies, which is a
textbook server-side request forgery surface. Every https:// fetch, whether
creating a data collection from a URL, ingesting a manifest, or a refresh task
on the Celery worker, goes through one gateway module; every s3:// read is
decided by one access module; a folder on the server's disk is
read only on a depictio local server, or by a
Run ingestion inside the folders an administrator allowed. Nothing in
server context reads a user-supplied location directly. See
Remote data and manifests for the feature
itself.
What the gateway enforces¶
| Check | Behaviour |
|---|---|
| Scheme allowlist | https:// and s3://. Plain http:// only with DEPICTIO_REMOTE_ALLOW_HTTP=true |
| Address check | The host is resolved and private, loopback, link-local, reserved, multicast and unspecified addresses are rejected, including the cloud metadata endpoint |
| Redirects | Every Location is re-validated against the same policy before it is followed; hops are capped by DEPICTIO_REMOTE_MAX_REDIRECTS |
| Bounded download | Streamed, with a size cap (DEPICTIO_REMOTE_MAX_DOWNLOAD_BYTES) and a per-operation timeout (DEPICTIO_REMOTE_TIMEOUT_S); a download that exceeds the cap is aborted and the partial file removed |
| Host lists | Exact host names, compared without case. DEPICTIO_REMOTE_URL_DENYLIST always rejects. DEPICTIO_REMOTE_URL_ALLOWLIST is exclusive while set, and a listed host bypasses the private-range check. An s3:// location skips both: the S3 rules apply instead |
| Sanitised errors | Transport and HTTP errors are logged with their cause and surfaced to the client without internal details |
The policy is read from the environment on every call, so the API and the
worker apply the same one as long as both are given the same
DEPICTIO_REMOTE_* settings. The CLI, which fetches the user's own loopback and
intranet hosts, reads directly but keeps the redirect and size caps.
Residual risk: DNS rebinding
The address is checked when the host is resolved, and a hostile resolver
could answer differently between that check and the connection. The
gateway does not pin the resolved address. Hardened deployments should run
allowlist-only: set DEPICTIO_REMOTE_URL_ALLOWLIST to the hosts you
trust, which turns the address check into a host check.
S3 locations¶
Which bucket the server reads, and with which credentials, is decided from the configuration before any request goes out, so a bucket name typed by a user never becomes an existence or region oracle.
- The bucket that holds the instance's own data is refused as a source: it holds every project's data.
- The instance's own S3 keys are never used for a user-supplied location.
- A bucket is read without credentials when listed in
DEPICTIO_REMOTE_PUBLIC_S3_BUCKETS, or with project storage settings that hold no key, and with the server's ambient credentials only when listed inDEPICTIO_REMOTE_CREDENTIALED_S3_BUCKETS. Anyone on the instance can read what the second list names. Both lists are empty by default. - Any other bucket needs the project's storage settings, and is read with them alone. Settings typed in while creating a project from a run folder are the only credentials that request uses.
- Errors name the bucket and the prefix, never an endpoint of the instance, a key or a request id.
The full order is under Reading S3 buckets. The CLI reads from the user's own machine and skips the instance-bucket refusal and the credentialed list.
Folders on the server's disk¶
A server reads a local path stored on a project in two cases only:
Run ingestion (v1.15.0+),
which reads inside DEPICTIO_INGESTION_ALLOWED_DATA_ROOTS alone, see
Starting an ingestion from the browser,
and a depictio local server, which is the user's own computer. Otherwise,
data ingested from a local folder is refreshed by the CLI that ingested it. On
a depictio local server:
- Local folders are off unless
DEPICTIO_LOCAL_DATA_ROOTSis set, and only in single-user mode (DEPICTIO_AUTH_SINGLE_USER_MODE).depictio local upsets both, with the home folder and the folders given with--data-root-allow. A server with roots but not in single-user mode logs a warning and keeps them off. - Every local read (listing, inspecting, creating, refreshing) needs a request
addressed to a loopback host (
localhostor a loopback address), which also defends against DNS rebinding, from an administrator. - Paths are resolved to their real path and confined to the roots. Hidden
folders and the folders Depictio keeps for itself (
~/.depictio, the local home, the keys and CLI configuration folders, the backups) are refused even below a root. - A run folder over 100,000 files is refused. For a project made from a run folder, every template variable given as a path and every location a data collection scans must be inside it; one pointing elsewhere is refused.
Per-project storage credentials¶
A project owner or an administrator can attach S3-compatible credentials so remote and manifest collections can read a private bucket.
- Credentials live in their own collection, never on the project document, so they cannot leak through project responses or exports. Template bundles never include them. An instance backup does, with the secret still encrypted: keep backup files as safe as the keys volume.
- The secret is encrypted at rest with a symmetric key stored next to the
JWT key pair, in
DEPICTIO_AUTH_KEYS_DIR. The API encrypts on write and the Celery worker decrypts inside refresh tasks, so backend and worker must mount the same keys volume; a worker with a keys directory of its own would mint a second key and find every stored secret unreadable. - The secret is write-only in the API: responses only carry
has_secret. An update that omits the secret keeps the stored one only while the access key, the endpoint and the bucket are unchanged, so a stored secret is never sent to another endpoint. - Only project owners and administrators read or change the settings.
- The endpoint URL passes the same host gating as remote data URLs. The
instance's own object storage is always allowed. Any other endpoint on a
private network needs to be allowlisted, while an allowlist is set every
endpoint must be on it, and
http://needsDEPICTIO_REMOTE_ALLOW_HTTP. An empty endpoint, possible through the API only, means Amazon S3 and is not gated. - The stored endpoint is checked again at every read, so tightening the host lists also stops settings saved before: their reads answer 409 until an owner updates them. A secret the instance cannot decrypt answers 500.
- These are read credentials only. Delta tables are still written with the instance's own storage configuration, and refresh workers re-read the credentials from the database rather than receiving them through the task broker.
Content Security Policy¶
Deployed instances send a Content-Security-Policy header. The development server sends none, so a policy violation only ever shows up on a deployment.
Most of the policy is 'self'. Three allowances are not obvious, and are the ones to carry
across if you replace the policy at a reverse proxy or ingress:
| Directive | Allowance | Needed for |
|---|---|---|
script-src |
'unsafe-eval' 'wasm-unsafe-eval' |
Plotly's WebGL renderer and the in-browser Python runtime |
connect-src |
ws: wss: |
The real-time events WebSocket |
connect-src |
the basemap origins below | Map tiles |
Basemap origins¶
Maps load their style, glyphs, sprite and vector tiles over fetch, so connect-src
governs them; allowing images is not enough. A map missing these renders blank.
The apex domain is listed separately because a *. wildcard does not match it.
The policy lives in two files
The API middleware (depictio/api/main.py) and the viewer's nginx template
(docker-images/nginx.conf.template) each send their own copy, and the two must agree.
Audit Logging¶
Depictio logs security-relevant events for compliance and troubleshooting.
Logged Events¶
| Event Type | Details Logged |
|---|---|
| Authentication | Login attempts, token refresh, logout |
| Authorization | Permission checks, access denials |
| Data Access | Dashboard views, data queries |
| Modifications | Dashboard edits, project changes |
| Admin Actions | User management, system configuration |
Log Configuration¶
Configure logging via environment variables:
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
INFO |
Logging verbosity |
LOG_FORMAT |
json |
Log format (json/text) |
AUDIT_LOG_ENABLED |
true |
Enable audit logging |
Security Best Practices¶
JBrowse Iframe Sandbox¶
The JBrowse iframe uses a restricted sandbox attribute that omits allow-same-origin. Previously, combining allow-same-origin with allow-scripts would have allowed iframe content to access the parent page's localStorage and read authentication tokens; removing allow-same-origin closes this vector.
OAuth Callback Security¶
The OAuth callback validates that the redirect URL is same-origin before calling window.location.assign. This closes an open-redirect vulnerability where a crafted authorization response could send the user (and any attached tokens) to an attacker-controlled domain.
Since v1.5.2 the CSRF state is stored in MongoDB (oauth_states), used once and expired by a TTL index. It was previously held in per-process memory, so sign-in only worked when the callback happened to reach the worker that issued the state.
DEPICTIO_DEV_MODE no longer skips state validation
The flag used to bypass this check entirely, leaving the callback without CSRF protection on any deployment that set it. It now only affects hot-reload and verbose logging.
Deployment Recommendations¶
- Use HTTPS: Always deploy behind TLS termination
- Secure MongoDB: Enable authentication, use TLS
- Secure MinIO: Enable access keys, use TLS
- Network Isolation: Use private networks for internal services
- Regular Updates: Keep dependencies updated
Secret Management¶
| Secret | Recommendation |
|---|---|
| JWT Keys | Generate unique keys per environment |
| Database Credentials | Use strong passwords, rotate regularly |
| API Keys | Use environment variables, not config files |
| MinIO Credentials | Separate credentials per environment; root password is a SecretStr with a ≥16-character validator and no default — the server refuses to start if the value is absent or matches a known-default string |
| Project storage secrets | Encrypted at rest with secrets_key.bin in DEPICTIO_AUTH_KEYS_DIR; back the keys volume up with the JWT key pair and mount it on the worker too, see Per-project storage credentials |
Environment Configuration¶
# Example secure configuration
JWT_PRIVATE_KEY_PATH=/secrets/jwt_private.pem
JWT_PUBLIC_KEY_PATH=/secrets/jwt_public.pem
MONGODB_URI=mongodb://user:pass@mongo:27017/depictio?authSource=admin
MINIO_ACCESS_KEY=<generated-access-key>
MINIO_SECRET_KEY=<generated-secret-key>
# CORS — list allowed origins explicitly (comma-separated); wildcard with credentials is rejected
DEPICTIO_FASTAPI_CORS_ALLOWED_ORIGINS=https://your-domain.example.com
# Bootstrap admin (replaces initial_users.yaml)
DEPICTIO_BOOTSTRAP_ADMIN_PASSWORD=<strong-password>
CORS: allow_origins=["*"] has been removed. Origins are configured via DEPICTIO_FASTAPI_CORS_ALLOWED_ORIGINS (comma-separated list). A wildcard combined with allow_credentials=True is explicitly rejected by the framework and will raise a startup error.
Admin bootstrap: initial_users.yaml is replaced by env-driven bootstrap via DEPICTIO_BOOTSTRAP_ADMIN_PASSWORD. This avoids shipping a default credentials file and ensures the admin password is injected as a secret at deploy time.
Kubernetes Security¶
When deploying on Kubernetes:
- Use Secrets for sensitive configuration
- Enable Network Policies to restrict pod communication
- Use ServiceAccounts with minimal permissions
- Enable Pod Security Standards
Security Reporting¶
If you discover a security vulnerability, please report it responsibly:
- Email: Contact the maintainers privately
- GitHub: Use GitHub Security Advisories (private disclosure)
- Details: Include steps to reproduce and potential impact
Do not disclose vulnerabilities publicly until a fix is available.
What to Report¶
- Code execution restriction bypasses
- Authentication/authorization bypasses
- Data access vulnerabilities
- Injection vulnerabilities (SQL, NoSQL, command)
- Cross-site scripting (XSS) issues
Security is a shared responsibility. While Depictio provides robust security features, proper configuration and operational practices are essential for maintaining a secure environment.