Skip to content

Security

Bartosz Kupidura edited this page Jul 29, 2026 · 2 revisions

Run DMH and Vault on different servers / locations

Deploy the DMH component and vault component on separate machines - ideally in different datacenters or use separate service providers (ex. self-host for dmh and AWS for vault). Never enable both components in a single instance for production.

WHY: Decryption requires both: DMH holds the encrypted action, vault holds the encryption key. This split is the core of the design - a single compromised system reveals nothing usable. If both live on the same host, that split collapses and anyone with access can decrypt everything before you are dead.

Use TLS for every connection (strongly recommended)

Terminate HTTPS/TLS in front of both DMH and vault. Make sure remote_vault.url uses https://. Never run production traffic over plain http://.

WHY: When DMH stores an action it sends the age private key (encryption key) to vault, and when it decrypts an action it fetches that key back. Over HTTP these keys are visible in plain text in the network.

Enable authentication

Authentication is enabled by default and required for both DMH and Vault HTTP endpoints - a request needs either a bearer token or a valid signed URL, unless its path is explicitly listed in auth.anonymous_scope. Generate a token with dmh-cli crypt generate-bearer and give its scope only the paths that token actually needs (see Configuration page for the auth: block and how scopes work). Don't set auth.enabled: false in production.

WHY: Without this, anyone who can reach DMH/Vault over the network can list, create, test or delete actions.

Pin the signed URL secret

Set auth.signed_url.secret explicitly (generate one with dmh-cli crypt generate-signed-secret) instead of leaving it empty in production.

WHY: If auth.signed_url.secret is empty, DMH generates a new random secret in memory on every start - it is never written to disk. That secret signs every {sig_auth:<page>} link (see Action/Alive-probe pages). If the process restarts before a recipient clicks a link that was already sent - every link signed under the old secret stops validating.

Restrict network access to DMH, in addition to authentication

Authentication alone is not a reason to expose DMH to the internet unnecessarily. Put it behind a reverse proxy, VPN, mTLS, or allowlist, and expose it only to yourself - in addition to enabling authentication.

WHY: Second, independent barrier still helps if a token ever leaks or a scope is misconfigured.

Understand recurring actions (min_interval > 0) keep keys alive

Actions with min_interval > 0 run repeatedly and their key is never deleted from vault. Use min_interval: 0 unless you specifically need job to be executed forever.

WHY: With min_interval: 0, DMH deletes the key from the Vault right after the action runs. With min_interval > 0 the action runs forever and its key must stay in the Vault indefinitely.

Keep process_unit aligned between DMH and vault

Use the same process_unit on DMH and vault.

WHY: Release timing is computed independently on each side from configured process_unit. DMH decides when to attempt decryption, vault decides when to release the key. If the two deployments disagree on whether process_after counts hours vs. minutes the switch can fire too early or too late.

Restrict the exec plugin to specific paths

Configure execute.plugin.exec.allowed_paths and/or denied_paths before using the exec plugin - see Execute-plugins page for syntax. denied_paths always wins if a path matches both lists. An empty allowed_paths with a non-empty denied_paths allows everything except what's denied; a non-empty allowed_paths restricts to just that list (minus anything also denied). Leaving both lists empty allows every path - DMH logs a warning each time an exec action runs unrestricted like this.

WHY: exec runs arbitrary local code with the privileges of the dmh user - the plugin ships with no built-in "dangerous binaries" list, so an unconfigured or overly broad allowlist is a direct path to arbitrary code execution for anyone who can create an action (api:action:store scope). Also be aware that an allowlisted binary can still shell out on its own (ex. find -exec - see GTFOBins for more) - the allow/deny lists restrict which binary DMH itself starts, not what that binary does afterwards. If that matters for your deployment, enforce it at the OS/container level (seccomp, a restricted user, ...).

Clone this wiki locally