Security
This page summarizes practical security properties and controls. For the full adversary model and per-threat mitigations, see Threat Model.
Threat Model (High Level)
Primary adversaries:
- malicious peers
- network observers
- malicious or degraded infrastructure nodes
Primary goals:
- protect funds from unauthorized signing
- reduce linkability between participant activity
- preserve availability under spam/DoS pressure
Core Controls
- transaction verification before maker signing (see Maker Verification Checklist)
- PoDLE anti-abuse commitments
- Tor-based transport and hidden-service support
- rate limiting and message validation in directory/maker paths
- fidelity bond weighting as Sybil-cost mechanism
Automated Vulnerability Scanning
GitHub Actions applies complementary scanners to source, dependencies, and published images:
- CodeQL runs extended security queries against Python, JavaScript/TypeScript, and GitHub Actions workflows on pull requests, main-branch updates, and a weekly schedule.
- Dependency review blocks pull requests that introduce high or critical
vulnerabilities.
pip-auditchecks every production lock file and a fresh resolution of the package set used by unsigned development installs. - Trivy scans every container candidate before it is promoted. Pull requests cover amd64; main and release promotion cover amd64, arm64, and arm/v7.
- A daily job re-scans both the moving
mainimages and the currentlatestrelease images so newly published advisories are detected between builds.
Automated dependency update pull requests are intentionally disabled. Maintainers refresh dependencies with the repository update scripts before releases, while GitHub vulnerability alerts and scheduled audits report issues between releases.
All high and critical image findings are retained in the workflow artifacts. Findings with a published fix block image promotion; findings without a fix produce warnings for maintainer review. Scanner matches identify affected package versions, not exploitability, so maintainers must evaluate whether the affected code is reachable in JoinMarket's runtime and threat model.
Randomness and Key Material
Wallet mnemonics are encoded from explicit entropy bytes obtained through
Python's secrets API, which delegates to the operating system CSPRNG. The
command-line wallet requests 256 bits by default; jmwalletd requests 128 bits.
Both sizes are valid BIP39 entropy lengths. Entropy acquisition errors abort
wallet creation before backend initialization or wallet-file persistence, and
there is no weaker fallback.
Private wallet and TLS key files are created through owner-only temporary files
and atomically installed with mode 0600. Daemon wallet and TLS directories are
mode 0700. These permissions protect against other unprivileged local users;
they do not protect against the wallet process's own user, root, backups, swap,
or a compromised host.
PoDLE proof nonces use a domain-separated RFC 6979-style HMAC-SHA256 derivation
keyed by the UTXO private key and bound to the proof transcript. Proof generation
therefore does not depend on runtime randomness, avoiding private-key exposure
if an operating-system RNG later repeats a nonce. Maker selection, transaction
ordering, fee variation, and other adversary-relevant choices use
secrets.SystemRandom. Explicit tumbler seeds remain deterministic only for
reproducible plans.
Directory and Messaging
- use multiple directory servers where possible
- prefer direct maker/taker channels when available
- enforce per-message signatures and a strict session state machine during the CoinJoin flow (takers may switch transport mid-session)
Neutrino Notes
- neutrino is convenient, but full-node backends remain the strongest default for verification and compatibility
- run neutrino infrastructure you trust and route traffic with Tor where possible
- neutrino-api supports TLS with certificate pinning and bearer-token authentication, enabled by default; see Neutrino TLS for setup details
- the TLS certificate is self-signed and pinned on first use (TOFU model), so only the specific neutrino-api instance that generated it is trusted
Operational Advice
- treat mnemonics and wallet files as high-value secrets
- keep software updated
- test operational setup on testnet/signet/regtest before production use
Process Memory Hardening
Each long-running daemon (jmwalletd, maker, taker, directory server,
orderbook watcher) calls jmcore.process_hardening.harden_current_process
at startup. This applies two cheap, OS-level mitigations on Linux to keep
secrets (mnemonic, BIP32 extended keys, derived private keys, NaCl session
keys, signed PSBTs) from leaking on crash or live introspection:
RLIMIT_CORE = 0disables core dumps for the process, preventing tools likesystemd-coredump(8)from writing the address space to diskprctl(PR_SET_DUMPABLE, 0)blocks non-privilegedptraceand/proc/$pid/memreads from peer processes in the same user namespace
Set JOINMARKET_DISABLE_PROCESS_HARDENING=1 to opt out (only useful when
debugging with gdb or rr).
These mitigations do not protect anonymous pages that get paged out to swap or written to a hibernation image. Operators who hold non-trivial funds should also:
- enable encrypted swap (for example LUKS-backed swap with a random key on each boot, or zram-swap, so paged-out pages never persist plaintext)
- disable hibernation, or back the hibernation partition with the same
encrypted volume as
/ - avoid running daemons on systems with
kernel.yama.ptrace_scope = 0(Ubuntu's default is1, which is fine; RHEL-style systems should set this in/etc/sysctl.d/) - prefer running daemons under a dedicated unprivileged user account
and, where possible, a
systemdunit withProtectKernelTunables=,ProtectControlGroups=,PrivateTmp=, andNoNewPrivileges=
For protocol-level details, see Protocol and Privacy. For the maker-side pre-sign checklist, see Maker Verification Checklist.