Security model¶
Loopback by default, no TLS, a password that flips the default bind, and an unauthenticated replication stream. Read this before the server sees a network.
Security model, in one paragraph. Pion listens on 127.0.0.1 by default and there is no TLS. Setting a password (
--requirepass-file,PION_REQUIREPASS, or--requirepass) switches the default to all interfaces, following Redis's protected-mode convention — a password is taken as the signal that you intend to serve remotely. Override either way with--bind <addr>; an address that does not parse is a startup failure, never a silent fall back to0.0.0.0. The bind address applies to every listener: the RESP port, the binary lane onport+1, the WAL replication stream onport+10000, and gossip/Raft. Note that replication and gossip are unauthenticated — anyone who can reachport+10000can stream the WAL — so put those behind a private network, and terminate TLS at a proxy if you need encryption in transit. Running with--bind 0.0.0.0and no password prints a warning at startup and means exactly what it says. SeeSECURITY.md. No telemetry: Pion never phones home — no update check, no analytics — and connects out only to what you configure or enable; the full list is inSECURITY.md.
Reporting a vulnerability¶
Reporting a vulnerability¶
Do not open a public issue for a security problem.
Report privately via GitHub's Security Advisories,
or by email to pion@pavelhorak.com with [pion-security] in the subject.
Please include:
- affected version (
./pion-server --version) and platform - a minimal reproduction — a RESP command sequence is ideal
- what an attacker gains (crash, data disclosure, data loss, bypass)
Pion is maintained by one person. Expect an acknowledgement within a few days, not within hours. If you need a faster response for a live incident, say so in the subject line.
Scope¶
Pion speaks an unauthenticated wire protocol by default, exactly as Redis does. The following are expected behaviour, not vulnerabilities:
- Any client that can reach the port can read and write all data. Bind to
localhost or a private network, and use
--requirepassif you need authentication. KEYSandSCANwalk the whole keyspace and are O(N). That is the documented Redis semantics.--tenantprovides namespace isolation, not resource isolation. A tenant can exhaust memory, CPU or WAL for every other tenant on the same process. For hard isolation, run one server per tenant. This is stated indoc/multi_tenant.mdand is a design boundary, not a bug.EVALruns Lua in-process. Scripts are sandboxed against the filesystem and network, but a script can still block the event loop.
The following are in scope and worth reporting:
- memory-safety faults reachable from the wire (crash, out-of-bounds read or write, use-after-free) — including via malformed RESP frames
- any path where one connection can read or destroy another connection's or another tenant's data
- a reply that does not correspond to the command that produced it (protocol desync), since it can cause a client to attribute one key's value to another
- silent data loss: a command that reports success without durably applying, or that destroys data it was not asked to touch
- authentication or allowlist bypass under
--requirepass/--tenant
Outbound connections — no telemetry¶
Pion never contacts us. There is no update check, no usage analytics, and no
crash reporting: crash breadcrumbs (pion-<port>.crash.log, .status) are
written to local files and go nowhere. PION.STATS is computed and served
locally.
The server opens an outbound connection only in these cases, each of which you enable or configure:
| When | Where it connects | Turn it off |
|---|---|---|
| Startup, by default | 127.0.0.1:11434 — a local Ollama probe, loopback only |
--no-auto-detect |
| An embedding, LLM or MAX endpoint is enabled | the host you configured (defaults to 127.0.0.1) |
leave it disabled |
FT.HYBRID … RERANK <host> <port> … |
the host named in that command | do not send it |
MIGRATE |
the target named in that command | do not send it |
| Replication, cluster, Raft/gossip | the peers you configured | single node (default) |
| The Python inference sidecar is enabled | a local Unix socket; the Hugging Face library inside it downloads the model on first use | HF_HUB_OFFLINE=1, or --no-auto-embed |
--nle-embed (macOS) |
nothing from Pion — Apple's on-device NaturalLanguage framework; macOS manages its own assets | leave it off |
This list was taken from the source, not from memory: every outbound
connection in the server goes through pion_connect_tcp,
pion_connect_unix or pion_tcp_connect (src/ffi/). A new call site
belongs in this table in the same change.
Supported versions¶
Pre-1.0, only the latest release receives fixes. There are no backports.
Tenants and isolation¶
Pion is shared-nothing per worker and cheap to run (--profile kv ≈ 50 MB/worker). The supported way to isolate tenants is to give each tenant its own process:
pion-server -p 1974 --requirepass "$TENANT_A_PW" --profile kv -w 4 --independent-workers # tenant A
pion-server -p 1975 --requirepass "$TENANT_B_PW" --profile kv -w 4 --independent-workers # tenant B
Each process has a private keyspace, WAL, HNSW graph, and (with --requirepass) its own authentication boundary. Route each tenant's clients to their own port. This is the model pion-lmcache/MULTITENANT.md describes.
The per-connection tenant binding (--tenant NAME=PASSWORD) is documented in
Multi-tenant; the flags themselves are in the
CLI reference.