Application Security
Technical overview of DFIRe's security architecture, including authentication, encryption at rest, and role-based access controls.
Security Overview
DFIRe is designed for handling sensitive forensic and incident response data. The application implements defense-in-depth with multiple security layers.
| Feature | Implementation |
|---|---|
| Authentication | Session-based with OIDC SSO support |
| File Encryption | AES-256-GCM with three-layer key hierarchy |
| Credential Storage | Fernet encryption (AES-128-CBC + HMAC-SHA256) |
| Access Control | Two-tier RBAC (global + case-scoped) |
| Session Management | Database-backed with O(1) revocation |
| CSRF Protection | Double-submit cookie pattern |
| Rate Limiting | Configurable per-endpoint throttling |
| Audit Logging | Immutable, fail-closed audit trail |
Infrastructure Security Responsibility
Important: The security features documented on this page secure the DFIRe application itself. You are solely responsible for securing your underlying infrastructure, including the database, file storage, and network access.
DFIRe is designed as a user interface layer that operates on data you own and control. When deployed for production, DFIRe does not include a database or file storage system - you must provide these yourself. This separation is intentional: database security requirements vary significantly between organizations, and DFIRe allows you to choose the solution that fits your needs - whether that's an on-premise PostgreSQL cluster, a managed Database-as-a-Service, or another configuration.
What DFIRe Secures
- Application-layer authentication and session management
- Role-based access control within the application
- Encryption of file attachments before storage
- Encryption of stored credentials (API keys, SSO secrets)
- CSRF protection and rate limiting
- Audit logging of user actions
What You Must Secure
| Component | Your Responsibility |
|---|---|
| PostgreSQL Database | Access control, authentication, encryption at rest, backups, replication, network isolation. Case data is stored in plaintext in the database for performance and full-text search capability. |
| File Storage | Access control, redundancy, backups. Files are encrypted by DFIRe before storage, but you control the storage infrastructure. |
| Network Access | Firewall rules, TLS termination, VPN/private network configuration, load balancing, DDoS protection. |
| Server Infrastructure | Operating system security, container runtime security, resource limits, monitoring, patching. |
| Backup & Recovery | Database backups, file storage backups, encryption key backups, disaster recovery procedures. |
| Compliance | Ensuring the data you store complies with applicable regulations (GDPR, HIPAA, etc.) and that your infrastructure meets compliance requirements. |
High-Security Environments
For organizations with strict security requirements:
- Air-gapped deployment: DFIRe supports offline licensing for networks without internet access
- Network isolation: Deploy DFIRe in a dedicated VLAN or private network segment
- Database encryption: Enable PostgreSQL's Transparent Data Encryption (TDE) or use encrypted storage volumes
- Access logging: Configure database audit logging in addition to DFIRe's application-level audit trail
- Zero-trust architecture: Require VPN or identity-aware proxy for all access to DFIRe
See the End User License Agreement included with DFIRe for the complete terms regarding infrastructure and data security responsibilities.
Authentication
DFIRe uses session-based authentication with support for both local credentials and federated identity via OpenID Connect (OIDC).
Session Management
Sessions are managed using Django's database-backed session framework with additional tracking for security:
- Session storage: PostgreSQL database (not cookies or file-based)
- Session tracking: UserSession model tracks active sessions per user
- Session metadata: IP address and user-agent recorded for each session
- O(1) revocation: Instant invalidation of all user sessions (e.g., on password change)
- Cookie security: HttpOnly, SameSite=Lax, Secure flags in production
Local Authentication
For users without SSO, DFIRe provides username/password authentication:
- Password policy: Minimum 12 characters with complexity requirements
- Password storage: PBKDF2-SHA256 with per-user salt (Django default)
- Login rate limiting: 5 attempts per minute per IP
- Session invalidation: All sessions revoked on password change
OIDC Single Sign-On
DFIRe supports federated authentication via OpenID Connect, allowing integration with enterprise identity providers:
| Provider | Status |
|---|---|
| Microsoft Entra ID (Azure AD) | Tested |
| Google Workspace | Tested |
| Okta | Supported |
| Auth0 | Tested |
| Any OIDC-compliant IdP | Supported |
OIDC Flow
- User selects SSO provider on login page
- Browser redirects to identity provider's authorization endpoint
- User authenticates with the identity provider
- IdP redirects back with authorization code
- DFIRe exchanges code for tokens via back-channel
- Claims extracted from ID token (email, name, phone, picture)
- User matched by (provider_id, subject) or email fallback
- Session created and user redirected to dashboard
User Provisioning
When a user first authenticates via OIDC:
- Account is automatically created from OIDC claims
- Default group assigned based on provider configuration
- Profile picture downloaded if provided by IdP
- OIDC identity (provider + subject) stored for future logins
See Single Sign-On for configuration instructions.
Encryption at Rest
DFIRe encrypts all sensitive data at rest using a three-layer key hierarchy. This design ensures that data compromise requires access to multiple keys, and deletion of any key renders associated data permanently unreadable.
Key Hierarchy
Tenant Master Key (32-byte, generated once)
└── Entity Key (per Case or Evidence Item)
└── File Key (derived per attachment)
| Layer | Storage | Purpose |
|---|---|---|
| Tenant Master Key | Database (Tenant.encryption_key) | Root key for all tenant data |
| Entity Key | Database (Case/Item.encryption_key) | Per-case/item isolation |
| File Key | Derived at runtime | Unique per attachment |
File Key Derivation
Each attachment has a unique encryption key derived using HKDF-SHA256:
1. intermediate = HKDF(salt=tenant_key, ikm=entity_key, info="dfire-entity-derivation")
2. file_key = HKDF(salt=file_salt, ikm=intermediate, info="dfire-file-encryption")
The file_salt is a random 16-byte value generated for each attachment and stored alongside the encrypted file. This ensures every file has a unique key even within the same case.
File Encryption (Attachments & Photos)
All file attachments and photos are encrypted using AES-256-GCM:
| Property | Value |
|---|---|
| Algorithm | AES-256-GCM (authenticated encryption) |
| Chunk size | 8MB plaintext per chunk |
| Nonce | 12 bytes, derived from chunk index |
| Auth tag | 16 bytes per chunk |
| Streaming | Yes - supports files of any size |
The chunked design enables:
- Streaming encryption/decryption without buffering entire files in memory
- Compatibility with S3 multipart uploads
- Resumable uploads (same key derivation produces same key)
- Parallel chunk processing
Data Deletion: Deleting a tenant, case, or evidence item permanently destroys the associated encryption key. All attachments encrypted with that key become unreadable with no recovery mechanism. This is by design for secure data disposal.
Credential Encryption
Stored credentials (API keys, webhook secrets, SSO client secrets) are encrypted using a separate key:
| Property | Value |
|---|---|
| Algorithm | Fernet (AES-128-CBC + HMAC-SHA256) |
| Key source | CREDENTIAL_ENCRYPTION_KEY environment variable |
| Field types | EncryptedCharField, EncryptedJSONField |
Encrypted database fields include:
- OIDC client secrets
- Slack bot tokens and signing secrets
- Webhook signing keys
- License server credentials
- S3/SMB storage credentials
Key Separation: The CREDENTIAL_ENCRYPTION_KEY is separate from the tenant's file encryption keys. This means database credential encryption is independent of file storage encryption.
Role-Based Access Control (RBAC)
DFIRe implements a two-tier RBAC model: global permissions control what actions a user can perform, while case-scoped access controls which cases they can access.
Permission Architecture
Can user X perform operation Y on case Z?
│
├─ Is user a superuser?
│ └─ YES → Allow (bypasses all checks)
│
├─ Does user have the atomic permission for Y?
│ └─ NO → Deny
│
├─ Does user have global override (view_all/edit_all)?
│ └─ YES → Allow
│
├─ Is user assigned to case Z?
│ ├─ Lead Investigator → Allow
│ ├─ Investigator → Allow (write operations)
│ └─ Viewer → Allow (read-only operations)
│
└─ NO → Deny (no access to this case)
User Types
| Type | Description |
|---|---|
| Superuser | Bypasses all permission checks. Has access to system-critical settings (Tenant, Storage, SSO, Collaboration). Should be limited to system administrators. |
| Regular User | Permissions determined by assigned Django permissions (typically via groups). Case access determined by assignment. |
Case Roles
| Role | Capabilities |
|---|---|
| Lead Investigator | Full control: edit case, manage team, delete case, close case |
| Investigator | Edit access: add/edit evidence, notes, attachments, timeline events |
| Viewer | Read-only access to case and all its contents |
Global Override Permissions
| Permission | Effect |
|---|---|
core.view_all_cases |
View any case regardless of assignment |
core.edit_all_cases |
Edit any case regardless of assignment |
core.view_archived_cases |
View archived cases (required in addition to other access) |
Settings Access
Access to System Settings is controlled by user type and specific permissions:
Superuser-Only Settings
- Tenant Configuration
- Storage Backend
- SSO/OIDC Configuration
- Collaboration (Slack)
- Configuration Import/Export
Delegatable Settings
Accessible to users with core.change_tenant plus the relevant atomic permission:
- Webhooks
- User Accounts
- Case Types & Evidence Types
- Workflow Steps & Incident Phases
- Compliance Timers
- Flags
Write Protection
Closed and archived cases are write-protected:
- Closed: Case is read-only, can be reopened by Lead Investigator
- Archived: Case is permanently read-only, requires
view_archived_casespermission to view
Additional Security Controls
CSRF Protection
Cross-Site Request Forgery protection uses the double-submit cookie pattern:
- CSRF token set in cookie (
csrftoken) - Token must be included in
X-CSRFTokenheader for mutations - Token validated on all POST, PUT, PATCH, DELETE requests
Rate Limiting
| Endpoint | Limit |
|---|---|
| Login | 5 requests/minute |
| API (authenticated) | 1500 requests/minute |
| Slack webhooks | 60 requests/minute |
Secure Cookies
| Flag | Value |
|---|---|
| HttpOnly | Yes (session cookie not accessible to JavaScript) |
| SameSite | Lax (prevents CSRF from external sites) |
| Secure | Yes in production (HTTPS only) |
WebSocket Authentication
Real-time WebSocket connections:
- Authenticate using the same session cookie as HTTP requests
- Re-validate permissions on each ping
- Scoped to specific resources (case, item, dashboard)
- Anonymous connections rejected
Audit Logging
All security-sensitive operations are logged to an immutable audit trail:
- Immutable: Audit records cannot be modified or deleted
- Fail-closed: Operations fail if audit logging fails
- Comprehensive: Logs user, action, object, changes, timestamp, IP
- Searchable: Full-text search across audit records
Logged operations include:
- Authentication events (login, logout, failed attempts)
- Case and evidence CRUD operations
- Team assignment changes
- Configuration changes
- Attachment uploads and downloads
Preconfigured Permission Groups
DFIRe comes with four preconfigured permission groups. These use atomic permissions (not group name checks) and can be customized as needed:
DFIRe Admin
Full administrative access for managing the entire system:
core.change_tenant- access to System Settingscore.view_all_cases,core.edit_all_cases- global case accesscore.view_archived_cases,core.view_statisticsauth.add_user,auth.change_user- user management- All case, item, attachment, note permissions
- Webhook, compliance timer, case type, and evidence type management
Team Lead
Oversight role with full case visibility but no system settings access:
core.view_all_cases,core.edit_all_cases- global case accesscore.view_archived_cases,core.view_statisticscore.manage_team- can manage case assignments- All case, item, attachment, note, timeline permissions
- Report template management
- No
change_tenant- cannot access System Settings
Standard User
Regular investigator working on assigned cases:
core.add_case,core.change_case,core.delete_casecore.manage_team- can manage team on cases they lead- All item, attachment, note, timeline permissions
- No global case access - only sees assigned cases
View Only
Read-only access for auditors or external reviewers:
core.view_case,core.view_item,core.view_attachment- View-only permissions for all case content
- Cannot create, modify, or delete anything
- Access limited to assigned cases only