PowDB ships security fixes only for the latest minor series. Upgrade to the latest release to stay supported.
| Version | Supported |
|---|---|
| 0.29.x | ✅ |
| 0.28.x | ❌ (superseded) |
| 0.27.x | ❌ (superseded) |
| 0.26.x | ❌ (superseded) |
| 0.25.x | ❌ (superseded) |
| 0.24.x | ❌ (superseded) |
| 0.23.x | ❌ (superseded) |
| 0.22.x | ❌ (superseded) |
| 0.19.x | ❌ (superseded) |
| 0.18.x | ❌ (superseded) |
| 0.17.x | ❌ (superseded) |
| 0.16.x | ❌ (superseded) |
| 0.15.x | ❌ (superseded) |
| 0.14.x | ❌ (superseded) |
| 0.13.x | ❌ (superseded) |
| 0.12.x | ❌ (superseded) |
| 0.11.x | ❌ (superseded) |
| 0.10.x | ❌ (superseded) |
| 0.9.x | ❌ (superseded) |
| 0.8.x | ❌ (superseded) |
| 0.7.x | ❌ (superseded) |
| 0.6.x | ❌ (superseded) |
| 0.5.x | ❌ (superseded) |
| 0.4.4 – 0.4.9 | ❌ (superseded) |
| 0.4.1 – 0.4.3 | ❌ (yanked) |
| ≤ 0.4.0 | ❌ |
v0.4.1, v0.4.2, and v0.4.3 are yanked for data-loss bugs in crash recovery and were replaced by v0.4.4, which added a permanent durability regression suite. If you are on any of those three versions, or any release older than the current minor series, upgrade to the latest release. See
CHANGELOG.mdfor details.
If you discover a security vulnerability in PowDB, please report it responsibly.
Do not open a public issue. Instead, use GitHub's private vulnerability reporting:
https://github.com/ZVN-DEV/powdb/security/advisories/new
That link opens a report visible only to the maintainers. It works from any GitHub account, keeps the discussion attached to the repository, and lets us issue a security advisory and a patched release from the same place.
Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
You should receive an acknowledgment within 48 hours. We aim to provide a fix or mitigation within 7 days for critical issues.
If the advisory form is unavailable to you for any reason, open a public issue containing only the words "security report, please open a private channel" and no technical detail, and we will follow up.
PowDB is a storage engine and query executor. Security-relevant areas include:
- Wire protocol (
crates/server/) — binary framing, authentication, connection limits - Query parser (
crates/query/src/parser.rs) — input validation, nesting depth limits - Storage engine (
crates/storage/) — WAL integrity, mmap safety, file I/O bounds - Network binding — server binds to
127.0.0.1by default (not0.0.0.0)
PowDB supports native TLS for encrypted client-server connections. To enable TLS, set the following environment variables when starting the server:
POWDB_TLS_CERT— path to the PEM-encoded TLS certificatePOWDB_TLS_KEY— path to the PEM-encoded TLS private key
When both are set, the server requires TLS for all connections. When unset, the server accepts plaintext TCP connections. For production deployments, always enable TLS or use a reverse proxy / SSH tunnel. Setting POWDB_REQUIRE_TLS makes the server refuse to start if authentication is configured without TLS.
The bundled CLI supports TLS in remote mode (since v0.17.0):
--tls(orPOWDB_TLS=1) encrypts the connection, verifying the server certificate against the built-in webpki (Mozilla) root store--tls-ca <path>(orPOWDB_TLS_CA) trusts a custom root CA PEM instead, for self-signed deployments; implies--tls--tls-server-name <name>(orPOWDB_TLS_SERVER_NAME) sets the hostname the certificate is verified against, for connecting by IP to a certificate issued for a hostname; implies--tls
Without any TLS flags the CLI behaves exactly as before and connects over plaintext TCP. On releases before v0.17.0, the bundled CLI has no TLS support and cannot reach a TLS-required server; use the TS client or a TLS-terminating tunnel instead.
Run openssl version first. The recipe below is written so that LibreSSL (what stock macOS
ships as /usr/bin/openssl), OpenSSL 1.1.1 and OpenSSL 3.x all produce the same certificate,
and two details do that work:
-pkeyopt ec_param_enc:named_curveputs the P-256 object identifier in the key. Without it, LibreSSL writes the curve as explicit parameters, and the server's TLS stack (aws-lc) refuses such a key. The refusal surfaces as a key/certificate mismatch even though the pair does match, so the error text points nowhere near the cause.- The extensions come from a config file, not from
-addext. OpenSSL 1.1.1'sreq -x509applies its configuration's CA:TRUEbasicConstraintsas well as an-addextone, and the server refuses to start on a certificate carrying twobasicConstraintsextensions. - Extensions have to come from somewhere. A plain
openssl req -x509one-liner with no extension flags produces a CA:TRUE certificate with no subject alternative name. The server starts with it, and then every client handshake fails withCaUsedAsEndEntity, because a CA certificate is not a valid end-entity certificate.
cat > cert.cnf <<'EOF'
[req]
distinguished_name = dn
prompt = no
x509_extensions = ext
[dn]
CN = localhost
[ext]
subjectAltName = DNS:localhost, IP:127.0.0.1
basicConstraints = critical, CA:FALSE
keyUsage = critical, digitalSignature
extendedKeyUsage = serverAuth
EOF
openssl req -x509 -newkey ec \
-pkeyopt ec_paramgen_curve:P-256 -pkeyopt ec_param_enc:named_curve -nodes \
-keyout server.key -out server.crt -days 365 -config cert.cnf
# server: POWDB_TLS_CERT=server.crt POWDB_TLS_KEY=server.key powdb-server ...
# client: powdb-cli --remote 127.0.0.1:5433 --tls --tls-ca server.crt ...Verified end to end (server starts, CLI connects with --tls, query returns rows) against
LibreSSL 3.3.6, OpenSSL 1.1.1v, OpenSSL 3.4.1 and OpenSSL 3.6.3.
PowDB supports two authentication modes:
- Shared password — set the
POWDB_PASSWORDenvironment variable. All clients authenticate with the same shared secret. Applies only when no named users are defined. - Named users with roles (since 0.4.5) — users with
admin,readwrite, orreadonlyroles, managed viapowdb-cli useradd/passwd/userdel. Passwords are stored as argon2id hashes only (auth.jsonin the data directory,0600on Unix). WhenPOWDB_ADMIN_USERandPOWDB_ADMIN_PASSWORDare both set, the server bootstraps an initial admin on startup without the CLI. Once any user is defined, the shared password is no longer used.
In both modes:
- Rate limiting: failed authentication attempts are counted per peer address in a
fixed 60-second window. 5 failures against one username from one peer lock that
(peer, username) pair out; 50 failures across all usernames from one peer lock the
peer out entirely. The two thresholds differ on purpose: a wrong username swept across a
server must not be able to lock a real user out of their own account. A locked-out client
is refused with
too many auth failures, retry after 60s(wire error class 7,rate_limited) rather than being told whether the credentials were right. A successful authentication clears both counters for that peer. A server with no user store (open, or shared-password) never reads the username while authenticating, so it counts failures per peer alone: 5 per minute from one address, not 50. Unix-socket peers have no address and share one bucket between them. - The limiter itself is bounded. A failure bucket retains at most 64 bytes of the username the peer sent, and the table holds at most 4096 buckets. At capacity it evicts deterministically (lowest failure count first, then oldest window, then key order), so a spray of throwaway source addresses cannot clear a peer that is close to its bound. The expiry sweep runs at most once a second rather than on every handshake.
- Pre-auth payload limits: the server enforces frame size limits on unauthenticated connections to prevent resource exhaustion.
- Connection limits: the server accepts at most
POWDB_MAX_CONNECTIONSconcurrent connections (1024 by default). A peer past the ceiling is not refused: its connection is established and then waits, unserved, for a slot, and its 10-second pre-auth deadline does not start until it gets one. - Pre-auth deadline: the whole phase before a successful
CONNECT, pings included, runs under one 10-second deadline. It is not configurable on the binary. - A Unix-domain socket (
--socket/POWDB_SOCKET) is published at mode 0660 by an atomic rename from a staging name in the same directory, so the published path never names a socket at the process umask and is never briefly absent. Socket peers have no address, so they share one rate-limit bucket between them rather than being limited per address.
Note on the
readonlyrole: in releases up to and including 0.4.5, role storage is in place but read-only restrictions are not enforced at the query layer — do not rely on thereadonlyrole as a security boundary against writes on those versions. Read-only restrictions are enforced as of 0.4.6 at the server dispatch layer: write statements fromreadonlyusers are rejected withpermission denied, and unknown roles fail closed.
- Roles are coarse (
admin/readwrite/readonly). There are no per-table ACLs, row-level security, or multi-tenant isolation;readonlyenforcement is absent in ≤0.4.5 and enforced from 0.4.6 (see note above). - The query parser has a nesting depth limit. Runaway queries are bounded by
POWDB_QUERY_TIMEOUT(default 30s) and the per-query memory budget (POWDB_QUERY_MEMORY_LIMIT).