Skip to content

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_overrides is 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 in DEPICTIO_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_ROOTS is set, and only in single-user mode (DEPICTIO_AUTH_SINGLE_USER_MODE). depictio local up sets 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 (localhost or 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:// needs DEPICTIO_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.

https://basemaps.cartocdn.com
https://*.basemaps.cartocdn.com
https://tile.openstreetmap.org

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

  1. Use HTTPS: Always deploy behind TLS termination
  2. Secure MongoDB: Enable authentication, use TLS
  3. Secure MinIO: Enable access keys, use TLS
  4. Network Isolation: Use private networks for internal services
  5. 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:

  1. Email: Contact the maintainers privately
  2. GitHub: Use GitHub Security Advisories (private disclosure)
  3. 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.