Administration

Storage

Decide where evidence files and database backups live, and how each one is protected.

Targets, roles and tiers

Storage configuration has three layers. Separating them lets you build a target once and reuse it for several purposes.

LayerWhat it isWhere you set it
Target One configured backend: an S3 bucket, an SMB share, an SFTP server, or the local disk. Holds the connection details, credentials, an optional KMS key and an optional quota. Settings → Storage → Storage Targets
Role A binding that says which target serves which purpose. There are three: Encrypted Storage, Direct Storage and Backup. Settings → Storage → Storage Roles
Tier How one particular file is protected. A case attachment lands in either the Secure tier or the Direct tier, and each tier maps to a role. Chosen per upload by the user, from the tiers you enabled

One S3 bucket can hold encrypted attachments under attachments/, large disk images under direct/ and database backups under backups/ at the same time. Three separate buckets work equally well, as do three different backends.

The two attachment tiers

A file attached to a case or an evidence item goes through one of two pipelines. The user picks by choosing where they upload.

TierEncryptionSizeUse it for
Secure
always available
AES-256-GCM under DFIRe's own key hierarchy. Each 8 MiB chunk is encrypted as it reaches the application server, so the file is never written to disk in plaintext. The encrypted chunks spool to a temporary file, then transfer to the bound target as one encrypted blob. Up to 4 GB per file Notes, screenshots, exported reports, ordinary evidence files. Anything where DFIRe should own the encryption end to end.
Direct
only when configured
None from DFIRe. At-rest protection is whatever the backend gives you: SSE-KMS, SSE-S3 or nothing on S3, and operator-managed on SMB and SFTP. No practical limit Disk images, memory dumps, large archives. Anything where streaming through DFIRe to encrypt is impractical.

Direct Storage moves the trust boundary. It is off until you bind a target to it. Turn it on only if your threat model accepts the backend as the place evidence is protected at rest. That means your own S3 bucket, an air-gapped SMB share, or an SFTP server on a trusted segment. If your chain-of-custody requirements are strict, leave the Direct Storage role unbound and send everything through the Secure tier.

The Backup role never appears in a case. It is where scheduled and on-demand database snapshots go. See Backup and recovery for that workflow. This page covers Backup only where it affects target and role configuration.

Storage targets

Targets live at Settings → Storage → Storage Targets. Choose Add target, pick the type, fill in the connection fields, then select Test connection. The test runs a real round trip and names the step that failed. Save once it passes.

Prefer S3, especially for Direct Storage. Direct uploads to S3 go straight from the browser to the bucket through presigned URLs, so they never touch the DFIRe server. That makes S3 the fastest option and the only one that scales to many concurrent multi-gigabyte uploads. SMB and SFTP stream every byte through the backend and hold a server worker for the whole transfer. Reach for them when you need an on-premise share or SSH delivery. The Secure tier always streams through the server, because that is where the encryption happens, so the difference shows up mostly on the Direct tier.

Local filesystem

The default target, created on first run. Files live on the application server's disk inside the media_data Docker volume. You cannot delete it or change its type, but you can set a quota and bind roles to it.

Local suits development, single-server deployments, and air-gapped labs where the application server has the disk budget for evidence. It cannot serve Direct Storage. Direct exists to move large files onto a separate customer-managed system, and pointing it back at the local disk would defeat that.

Local storage is only as durable as the volume. Snapshot media_data on the same schedule as the database. See Backup and recovery.

S3-compatible storage

Works with AWS S3, MinIO, Backblaze B2, Wasabi, DigitalOcean Spaces and anything else that speaks the S3 API.

FieldWhat to enter
NameFree-form label shown in the target list and the role pickers, such as "AWS Frankfurt".
Endpoint URLYour provider's S3 endpoint. Leave empty for AWS. Backblaze B2 looks like https://s3.eu-central-003.backblazeb2.com.
RegionThe AWS region, such as eu-north-1, or your provider's region label.
Bucket nameMust already exist. One bucket serves several roles safely, because each role writes under its own prefix.
IAM role or access keyTick Use IAM role when DFIRe runs on EC2, ECS or EKS with an instance profile or task role. Otherwise paste an access key ID and secret for a user scoped to this bucket.
KMS key IDOptional. Set it to use SSE-KMS with that key. Leave it blank to fall back to the bucket's default encryption. Backblaze B2 does not support AWS KMS, so leave it blank there.
Use SSL/TLSAlways on in production.
Verify SSL certificatesChecks the server certificate against the system trust store. Turn it off only for self-signed development setups.
Storage quota (GB)Optional cap DFIRe enforces before each upload. Empty means unlimited.

IAM permissions

Whether you use an access key or an instance role, the principal needs these actions on the bucket and its objects. Replace YOUR-BUCKET with your own:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DFIReBucket",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetBucketCors",
        "s3:ListBucketMultipartUploads"
      ],
      "Resource": "arn:aws:s3:::YOUR-BUCKET"
    },
    {
      "Sid": "DFIReObjects",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:AbortMultipartUpload",
        "s3:ListMultipartUploadParts"
      ],
      "Resource": "arn:aws:s3:::YOUR-BUCKET/*"
    }
  ]
}

ListBucket and ListBucketMultipartUploads support Direct Storage Sync and the daily cleanup of abandoned uploads. GetBucketCors lets the test report a missing CORS rule instead of a bare 403. The object actions cover the multipart upload lifecycle, presigned downloads and deletion.

With SSE-KMS, also grant kms:Encrypt, kms:Decrypt, kms:GenerateDataKey and kms:DescribeKey on the key ARN.

Bucket CORS, for Direct Storage only

Direct uploads have the browser send parts to the bucket, so the bucket must allow your DFIRe origin. The Secure tier needs nothing here, because it goes through the application server. Apply this from the bucket's Permissions tab:

[
  {
    "AllowedOrigins": ["https://dfire.example.com"],
    "AllowedMethods": ["PUT", "GET", "HEAD", "POST", "DELETE"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3600
  }
]
  • AllowedOrigins is where the browser loads DFIRe from. List each origin you use. A wildcard works but lets any site drive uploads at your bucket.
  • AllowedMethods covers PUT for parts, POST to complete a multipart upload, GET and HEAD for downloads and preflights, and DELETE for cleanup.
  • ExposeHeaders must include ETag. Without it the browser cannot read a part's ETag, and DFIRe cannot complete the upload.

Recommended bucket settings

  • Block Public Access on, all four settings. DFIRe never needs public reads, because downloads use short-lived presigned URLs.
  • Versioning on. It protects evidence against an accidental delete or an overwrite.
  • Default encryption set to SSE-S3 at minimum, or SSE-KMS for customer-managed keys.

Backblaze B2 needs the CLI for CORS, not the web console. Every web preset produces download-only rules, including the one that offers to share the whole bucket with a single origin. Part uploads then fail their PUT preflight and the browser reports only Failed to fetch. The rule has to name the S3 pseudo-operations and expose etag:

b2 bucket update --cors-rules '[{
  "corsRuleName": "dfireDirectUploads",
  "allowedOrigins": ["https://dfire.example.com"],
  "allowedOperations": ["s3_put", "s3_get", "s3_head", "s3_post", "s3_delete",
    "b2_upload_file", "b2_upload_part",
    "b2_download_file_by_name", "b2_download_file_by_id"],
  "allowedHeaders": ["*"],
  "exposeHeaders": ["etag"],
  "maxAgeSeconds": 3600
}]' your-bucket-name

Substitute your origin and bucket, then upload a small file from a case to check. A wrong rule fails at the first preflight, before any large transfer starts.

SMB and CIFS shares

Works with Windows file servers, Samba and enterprise NAS devices. DFIRe requires SMB3 with end-to-end encryption and refuses a server that cannot negotiate it, even when the share would accept SMB1 or unencrypted SMB2.

FieldWhat to enter
Hostname or IPThe server, reachable from the DFIRe backend container.
Share nameThe network share, such as evidence_files. One share serves several roles, each in its own subdirectory.
Username and passwordA service account with read, write, create and delete on the share root.
DomainThe AD domain, or WORKGROUP for standalone servers and most NAS devices. DFIRe rewrites a domain user as DOMAIN\user when it authenticates.
Storage quota (GB)Optional cap. Empty means unlimited.

Check the container can reach TCP/445. On Docker's default bridge network, make sure SMB traffic routes to the share, or move the container to host networking.

SFTP over SSH

Works with any SSH server that has the SFTP subsystem enabled. Useful when network policy forbids SMB, or when the storage host is reachable only over SSH.

FieldWhat to enter
Host and portThe server and its port. The default is 22.
UsernameAn SSH user with read, write, create and delete on the working directory.
Authentication methodPassword or SSH private key. Pick one. DFIRe does not fall back between methods, because silent fallback makes an authentication problem harder to debug.
SSH private keyKey authentication only. Paste a complete OpenSSH-format key, Ed25519, ECDSA or RSA, and add the passphrase if the key has one. Modern ssh-keygen defaults are accepted, including the AEAD ciphers [email protected] and [email protected].
Active directoryThe working directory on the server. Each role gets a subdirectory of it.
Host key fingerprintCaptured the first time you run Test connection, on trust-on-first-use. Confirm it out of band against the server's real host key before you save. If the host key later changes, delete the target and create it again to trust the new one.
Storage quota (GB)Optional cap. Empty means unlimited.

Storage roles

A role binds a target to a purpose. The bindings live at Settings → Storage → Storage Roles, one row per role with a target picker.

RoleAcceptsWhat it holds
Encrypted StorageLocal, S3, SMB, SFTPThe Secure tier. Every attachment uploaded through the standard Upload button, encrypted before it leaves the application server.
Direct StorageS3, SMB, SFTPThe Direct tier. Appears in a case as its own section once bound. DFIRe adds no encryption, and it rejects a Local target by design.
BackupLocal, S3, SMB, SFTPDatabase snapshots and on-demand exports. See Backup and recovery.

Three things follow from this model.

  • One target can serve every role. A single S3 bucket can be all three at once, because each role writes under its own prefix.
  • Each binding is independent. Repointing Encrypted Storage leaves Direct Storage and Backup alone.
  • Files stay where they were written. Changing a role's target affects new uploads only. DFIRe keeps reading older files from the target they went to. There is no built-in migration. To move historical data, copy it across out of band and use Sync to register it under the new target.

Checking Direct Storage works

Run Test on the target to confirm credentials and reachability, then upload a small file from a case. Direct uploads to S3 run browser to bucket, so a browser-side problem such as a missing CORS rule never shows up in the backend test. It appears at the start of that first upload, which is exactly when you want to find it.

What people see in a case

The Files tab shows up to three upload sections, depending on what you bound.

  • Encrypted Storage is always there and takes any file up to 4 GB. The progress bar counts encrypted chunks arriving, then switches to syncing while the encrypted blob moves from the server's spool to the target.
  • Images uses the same Secure tier, shown separately because images get a thumbnail and a grid instead of a file row.
  • Direct Storage appears only when the role is bound, with no size cap. Selecting several files queues them one at a time per browser tab.

Every evidence item has its own Files tab with the same sections. The only difference is which record the file belongs to.

A Direct Storage row moves through four visible states. It sits queued behind another file in the same tab, then goes uploading. Finalizing means the server is closing the multipart upload or the chunk stream. Then ready, and downloads work from that moment.

Leaving the case cancels an upload in progress. Browser back, opening another case and refreshing all stop it. Moving between tabs inside the same case does not. Each row has a Cancel button, which is the clean way to stop one. If someone closes the tab instead, the daily sweep removes the partial file from the backend within a day or two.

Thumbnails outlive their target. DFIRe renders a thumbnail for each uploaded image and stores it encrypted in its own database, separate from the file on the storage backend. Delete the bound Encrypted Storage target and the thumbnails keep rendering in the case while the full images fail to load.

The hash buttons on a Direct Storage row

  • Calculate SHA-256 shows on a row with no hash, which usually means a file registered by Sync. It queues a task that streams the file through the hasher. Above 256 MiB it asks for confirmation first, because the read costs time and, on cloud backends, egress.
  • Re-verify shows on any row that already has a hash. It streams the file back through the server-side hasher and records whether the stored hash still matches.
  • Recompute shows on a row whose hash went stale because Sync noticed the file's size or modification time changed.

Path layout

Each role writes under a predictable prefix, so you can find a file from the storage console or by browsing the share.

# Encrypted Storage
attachments/{tenant_uuid}/{case_id}/{file_uuid}.bin

# Direct Storage, case attachment
direct/{tenant_uuid}/{case_number}/{filename}

# Direct Storage, evidence-item attachment
direct/{tenant_uuid}/{case_number}/{item_short_id}/{filename}

# Backup
backups/dfire_backup_{YYYYMMDD-HH-MM-SS}-{version}-{mode}-{shortid}.enc

Encrypted Storage uses opaque UUIDs, because the content is encrypted and a readable name would buy nothing. Direct Storage keeps the original filename so files stay recognisable in the storage console.

The tenant UUID separates deployments that share a storage location. The case number, such as CASE-2026-003, groups files by case and never changes once assigned. The item short ID is the first eight hex characters of the item UUID and appears only on item-level attachments.

Registering files uploaded out of band

Some files are too large to push through a browser at all, such as a multi-terabyte image coming off an acquisition rig. Put those into the storage location yourself, then register them with Sync on the Direct Storage section.

  1. Copy the file to the right path

    Use whatever tool suits the backend. A case attachment goes to direct/{tenant_uuid}/{case_number}/, and an evidence-item attachment one level deeper under {item_short_id}/.

  2. Open the case or the evidence item and choose Sync

    If the directory does not exist yet, the first Sync creates it so you have somewhere to drop files next time.

  3. Check what it registered

    DFIRe reconciles its records against the storage location at that exact scope.

Sync treats the storage location as the truth for that one path. A file present there but unknown to DFIRe becomes an attachment. An attachment whose backing file has been removed is cleared. An upload still finishing is skipped, so a Sync running at the same time cannot reap a partial file. The path match is strict: a case Sync never descends into item subdirectories, and an item Sync stays inside its own folder.

A registered file arrives with no hash. DFIRe will not download a multi-terabyte file just to hash it, so the row shows Calculate SHA-256 for whenever you want one. This matters for chain of custody: the audit trail for such a file starts when Sync registered it, not when the file was created. If the chain has to be unbroken, upload through DFIRe and do not hand out credentials to the storage location.

Hashes and what they prove

How a Direct Storage file gets its SHA-256 depends on how it arrived.

  • Uploaded through the browser. The browser hashes the file in the same pass that slices it for upload, and sends the hash with the completion. DFIRe stores it and records that the browser declared it.
  • Registered by Sync. No hash until someone chooses Calculate SHA-256. The task streams the file from the backend and records the result.
  • Re-verified. Re-verify streams the file back through DFIRe's own hasher and compares. A mismatch marks the row and preserves both hashes in the audit log. DFIRe does not delete the file, because the disagreement is itself the finding.
  • Gone stale. Each Sync compares the backend's reported size and modification time against the snapshot taken when the hash was computed. A difference flags the hash as stale and keeps the old value as a record of the earlier bytes.

DFIRe does not re-hash on the server by default. At forensic-image scale, reading a file back purely to confirm a hash the browser already computed takes minutes and, on cloud storage, costs egress. The browser hash comes free with the upload pass. Re-verify exists for when independent confirmation is worth the cost.

Downloads

  • Secure tier. The file streams from the target through the application server, which decrypts it on the way to the browser. The browser never receives ciphertext. Chrome and Edge stream the file to disk and count the bytes as they arrive. A download that ends short of the announced size, or runs past it, fails instead of saving a partial file. Firefox and Safari hand the download to their own download manager, so this check does not apply there.
  • Direct on S3. DFIRe issues a presigned GET URL valid for five minutes and the browser fetches from S3. The signed URL stays out of browser history.
  • Direct on SMB or SFTP. The file streams through the backend to the browser.

Every download writes an audit row recording how it was served and the hash held at that moment.

A Direct Storage file is downloadable as soon as its upload finalizes, carrying the hash the browser computed. Use Re-verify when you need confirmation that the bytes on the backend still match. A file registered by Sync has no hash until someone calculates one.

Lifecycle

Deleting one attachment removes both the record and the file on the target. Deleting a case removes every attachment and every backing file, on both tiers, however each one got there.

Changing a role's target

You can repoint a role whenever you like. DFIRe stamps each attachment with its target when it writes the file, so existing attachments keep reading from where they were written.

Deleting a target

Attachments on a deleted target stay in the database and become orphaned. Their rows remain, and a Direct Storage row shows an orphaned badge. Downloads return HTTP 410 with an explanation until you relink them or delete the row.

Deleting a target never touches the objects in the bucket, share or host. Only DFIRe's link to them goes. To recover, create a target of the same type pointing at the same storage, then choose Relink files on its row.

Relinking checks before it writes. DFIRe compares each orphaned record's stored path against the target and relinks only the records whose object is really there. It then reports how many it relinked and how many it could not find. It moves and modifies no file data, changes nothing if it cannot inspect the target, and records each run in the audit log. Backup records relink the same way.

A configuration import keeps an existing target that exactly matches the imported settings, rather than recreating it. Attachments therefore keep their links across an import.

Testing a target

Test connection on the target form runs a full round trip against the backend. It writes a small object, reads it back, hash-compares it, and deletes it. That confirms the credentials, the network path and, for Secure-tier targets, that the encryption layer round-trips. On SFTP it also captures the host key fingerprint the first time.

The target list carries a badge showing the most recent result and when it ran. Green means the last test passed within 24 hours. Amber means it passed longer ago than that, or returned a warning. Red means it failed, and the badge carries the reason. A target nobody has tested yet reads as untested. Only Test connection writes the badge. When a target turns amber, test it again.

Test before you bind a target to a role in production. The test uses the same code path as a real upload, so a problem you catch here is one your users do not meet.

Quotas and limits

Each target takes an optional quota in GB, which DFIRe enforces before an upload. Reaching it rejects new uploads to every role bound to that target and leaves existing files readable. Deleting files frees the space. Current usage sits beside each target in the list. The Direct Storage section in a case shows usage and remaining space, so people can judge capacity before starting a large upload.

Two caps protect the application server from simultaneous Direct Storage uploads. One user may run 10 at once, and the whole installation may run 20. Hitting either returns a 429 with a Retry-After header, and the browser retries on its own while the row sits in the queued state.

The defaults suit a small forensics team. Both read an environment variable, MAX_CONCURRENT_DIRECT_UPLOADS for the per-user cap and MAX_CONCURRENT_DIRECT_UPLOADS_GLOBAL for the installation. The release bundle's Compose file passes a fixed set of variables to the backend and does not include these two. Raising a cap therefore takes a customized Compose file that forwards them.

Audit trail

Every storage operation reaches the audit log. Filter on these actions to reconstruct what happened to a file.

ActionWhen it fires
CREATE / DELETEA Secure-tier attachment was created or removed.
DOWNLOADAny attachment was downloaded, annotated with the delivery method and tier.
DIRECT_UPLOAD_INITA Direct Storage upload started. Records the filename, size, target and uploader.
DIRECT_UPLOAD_COMPLETEEvery part arrived and DFIRe created the attachment row.
DIRECT_UPLOAD_ABORTThe user cancelled, the sweep cleaned up, or the backend failed during init.
DIRECT_HASH_BROWSER_DECLAREDThe uploader's browser supplied a SHA-256 with the completion. This starts the trust chain for a browser-attested hash.
DIRECT_HASH_DISK_MISMATCHRe-verify found the file on the backend disagrees with the stored hash. DFIRe keeps the file and marks the row.
DIRECT_SYNCA reconciliation ran on a case or item. Lists what it added, removed and left alone, plus any upload it skipped.
SHA256_COMPUTEDA Calculate or Re-verify finished. Records the hash, the byte count, and the size and modification time at that moment.
SHA256_FAILEDA Calculate or Re-verify failed, with the reason.
SHA256_INVALIDATEDSync found the file's size or modification time had changed, so the hash went stale. The old hash is kept.
SHA256_CANCELLEDSomeone cancelled a running hash, with the bytes read so far.
ATTACHMENT_BACKEND_CLEANUP_FAILEDAn attachment row went but its backing file could not be removed. Holds the target and path so an operator or the sweep can finish the job.

Encryption keys

The Secure tier uses AES-256-GCM under a three-layer key hierarchy: a tenant key, an entity key, then a key per file. Files are encrypted before they leave the application server, so whoever holds the storage backend cannot read them. See Application security for the architecture.

Back up CREDENTIAL_ENCRYPTION_KEY as soon as you deploy. Lose it and encrypted files cannot be recovered, whichever storage backend holds them.

← Single sign-on Backup and recovery →