pebble.
Pebble / Docs / Internals

Security

Pebble is security-sensitive infrastructure software. This document states the threat model, trust boundaries and the protections that exist — and is explicit about which have been tested and which rest on platform assumptions.

Trust boundaries and attacker-controlled input#

Input Trust Handling
Internet traffic hostile PF default deny on ingress; no service listens on the WAN except WireGuard UDP; anti-spoofing; tagged port forwards
LAN clients partly trusted may use DNS, DHCP, ping and (if allowed) SSH on the router; everything else to the router is dropped; cannot reach other sites without rules
WireGuard peers partly trusted traffic accepted only from the peer's allowed IPs (WireGuard) and only where rules permit (PF); peer networks are anti-spoofed on other interfaces
DHCP client data (host names, client IDs) hostile dnsmasq stores it; pebble dhcp leases strips control characters; client host names are never published in DNS
Firewall IP list feeds hostile HTTPS and size limits as for blocklists; IPv4 entries only, nothing broader than /8, entries overlapping the router's own networks dropped; checked by pfctl -n before the live table is swapped; previous list kept on failure; pass rules from lists warned about
Blocklist feeds hostile HTTPS only, size/time/redirect limits, decompression limit, strict name syntax, never executed or interpreted, validated before reload
OPNsense exports hostile bounded XML parser, DTDs and entities refused, depth/element/size limits, no network access, secrets never copied into ordinary output
pebble.yaml administrator strict schema; every value validated and restricted to characters that are safe in the target syntax
Generated files, manifests root-owned manifests re-validated before use: only Pebble-owned paths and well-formed store names are accepted
Backups to restore semi-trusted only known regular-file members accepted; no links, directories or traversal
Web interface requests hostile until signed in Host allowlist, argon2id sign-in with throttling, CSRF tokens and origin checks, strict CSP, body limits; roles enforced again by the agent (web.md)
Agent socket clients local, root or pebble only peer credentials checked at accept; structured JSON with unknown fields refused; inputs validated; diagnostics rate-limited
System log lines hostile (any local process can log) parsed with anchored patterns into bounded events; never executed or interpreted; control characters stripped

Privileges#

Engineering controls#

Control Implementation Tested
No shell interpolation commands run with exec and argument vectors; rc.d scripts read generated files with read, never eval unit test: non-allowlisted executable refused
Fixed executable paths allowlist of absolute paths in internal/platform/exec.go; fixed PATH and LC_ALL=C yes
Timeouts and bounded output every command has a timeout; captured output is capped yes
Atomic writes temp file in the same directory, fsync, rename, directory fsync yes
Symlink safety Pebble never follows or replaces a symlink at a path it writes or reads; key files must be regular files yes
Path traversal owned-path allowlist; store names fNNN; key file names restricted; backup members allowlisted yes
Protected keys private/preshared keys only in /usr/local/etc/pebble/keys (0700), files 0600 root; loaded with wg set private-key <file>; never in YAML, generated files, generations, logs or diagnostics yes (generation and import outputs checked for key material)
Escaping names limited to [a-z0-9_-.]; TXT and descriptions exclude quotes/backslashes/control characters; validated before rendering yes (fuzzed)
No WAN exposure by accident DNS never offered to WAN-only networks; SSH from WAN requires allow_wan; port forwards produce "dangerous" warnings in imports yes
No default-pass window block all is the first rule; pfctl -f swaps rulesets atomically; forwarding is enabled after PF at boot and only if PF is enabled policy unit-tested; boot ordering not yet tested on FreeBSD
No open resolver Unbound binds to loopback/LAN/tunnel only; access-control refuse by default; PF allows DNS only from internal networks config unit-tested; integration test written
Rollback independent of the process cron watchdog + daemon(8) timer + boot recovery simulated crash tests; not yet on FreeBSD
Web interface sessions, CSRF, CSP, Host validation, throttling, role checks in two processes yes (internal/web, internal/agent tests)
Bounded monitoring journal size and retention limits; no packet capture; no DNS query logging yes
No telemetry Pebble makes no network connections except feed downloads you configure and pkg when you run pebble update check by inspection
No insecure updates Pebble never installs updates; pkg verifies repository signatures by design
Privacy no query logging by default; quiet-dhcp; diagnostics are aggregate by inspection

Fuzz targets: configuration parsing, network/port parsing, DNS name normalisation, feed parsing and OPNsense import (make fuzz). Fuzzing has already found and fixed one crash (a nil dereference on exports without an <OPNsense> section).

Assumptions about the platform#

These protections depend on FreeBSD behaving as documented; they were checked against FreeBSD 15.0 sources but not yet on a running system:

Known limitations#

Reporting vulnerabilities#

Please report security issues privately to the maintainer ([email protected]) rather than in a public issue.