Panella docs · Security

Security Policy

Reporting a vulnerability

If you believe you have found a security vulnerability in Panella, please report it privately to [email protected]. Do not open a public issue for a suspected vulnerability.

Please include enough detail to reproduce the issue (affected version, configuration, and steps). We follow responsible disclosure: we ask that you give us a reasonable window to investigate and ship a fix before any public disclosure, and we will keep you informed of progress.

Trust model

Panella is self-hosted and owner-controlled. There is no Panella-operated service in the trust path — the box, its store, its tokens, and its governance config all live in the operator's own environment. The security posture below describes what that box enforces and what the operator is responsible for.

Two separate secrets, two separate channels

Panella distinguishes two credentials that must never be commingled:

These are different secrets with different jobs on different channels. A bearer token can move data through the box; it cannot approve a durable write. An approval token proves approval identity; it is not a routing credential. Keep them stored and rotated separately. Handing an agent the approval token, or wiring the approval token in as a bearer, collapses the approval boundary — do not do it.

MCP network surface: opt-in, owner-only, DNS-rebind protected

The network MCP surface (/mcp) is off by default in the library — it mounts only when PANELLA_MCP_ENABLED is set. The shipped Docker image, however, enables it (PANELLA_MCP_ENABLED=1, see the Dockerfile / docs/SELF_HOST.md), so a compose/image deployment has /mcp live (read-only mcp-read) on the facade port out of the box, while a from-source library run does not until you set it. Wherever it is mounted it is defense-in-depth gated (panella/http/app.py):

Serving self-check: refuse rather than serve blind

At startup the box runs a coherence self-check against its own store (panella/store_probe.py). If the box's governance identity does not match its corpus — the overlay forgotten, or a generic config pointed at an owned store — every read would die in a tenant-isolation error and the box would silently serve nothing.

Instead, the self-check turns that silent dark-out into a loud refusal: the memory routes return 503 (and the break-glass mint is refused) while /v1/health stays reachable so a monitor sees a live-but-refusing process. A box that cannot prove it is serving its own corpus coherently refuses to serve at all. A genuinely fresh box acknowledges emptiness explicitly (PANELLA_FRESH_BOX=1).

Fail-closed defaults everywhere

The governance model is default-deny throughout (see docs/GOVERNANCE.md): a set-but-missing overlay pointer crashes rather than silently serving generic (an unset pointer runs the shipped generic base by design — a fresh box, not a wrong-deploy), an unknown approval transport is a load-time error, an empty approver set approves nothing, and a provenance mismatch is refused. Tenant isolation is enforced at the client boundary and fails closed on any ambiguous attribution.

Scope and honest limits

Panella runs as a single-process service. Within that process, code holding the store credentials is inside the trust boundary — Panella does not claim in-process code isolation or cryptographic attestation between components. Its security boundary is the authenticated approval plus the provenance gate, not process-level sandboxing. Process isolation and attestation are tracked as future work, and the finalizer module documents this assumption directly.

Canonical source: SECURITY.md on GitHub — the repository is the single source of truth; this page is rendered from it.