Please do not report security issues in a public issue.
Pullbackup holds SSH private keys and has filesystem write access to your backup destinations, so a vulnerability here is more consequential than in a typical web app. Report privately so a fix can ship before details are public.
Use GitHub's private vulnerability reporting (Security → Report a vulnerability), which opens a private security advisory visible only to the maintainers.
Please include:
- what you can do with the issue, not just what looks wrong
- the version or commit you tested
- steps to reproduce, ideally against a throwaway instance
- whether it requires an authenticated session
You will get an acknowledgement within a few days. This is a small self-hosted project maintained in spare time, so please allow reasonable time for a fix before disclosing publicly.
The latest release on the default branch is supported. There are no long-term-support branches.
Understanding what Pullbackup does and does not defend against will tell you whether something is a bug or expected behaviour.
In scope
- Bypassing authentication on any endpoint, whether via HTTP Basic or the login session cookie. This includes forging, tampering with, or replaying a session cookie, and using a cookie after sign-out, after it expires, or after the credential it was derived from changed.
- The login endpoint accepting a credential the middleware would reject, or answering anything other than 503 on an instance with no valid credential configured.
- Escaping the configured destination allowlist (
PULLBACKUP_DEST_ROOTS,PULLBACKUP_ZFS_DEST_ROOTS) to read or write outside it — including via symlinks, races, or path traversal. - Reading or exfiltrating the generated SSH private key through the application.
- Injecting arguments into the underlying
rsync,ssh, orsyncoidinvocations from user-supplied task fields. - Storing credentials in run logs, the API, or the UI.
Out of scope
- The instance being deployed without authentication configured. The app refuses to start without HTTP Basic credentials; removing that check yourself is your decision.
- Anything reachable only by an already-authenticated operator. An operator
can legitimately configure tasks that run
rsyncagainst paths inside the allowlist — that is the product, not a vulnerability. - Exposing the UI directly to the internet without a reverse proxy and TLS.
- Compromise of a source host you have configured, or of the machine running Pullbackup itself.
- The
aux_args/syncoid_extra_argsfields, which deliberately accept raw arguments. They are an authenticated-operator escape hatch by design.
-
Give the SSH key the narrowest access that works on each source host — read-only, and restricted to the paths you actually pull.
-
Keep the data volume (
/data) off shared storage: it holds the database and the private key. -
Put the UI behind a reverse proxy that terminates TLS. The session cookie is only marked
Securewhen the request arrives over https, and HTTP Basic sends the credential on every single request. -
If you do put it behind a proxy, set
PULLBACKUP_TRUSTED_PROXIES. The failed-login throttle keys on the address the app sees, which behind a proxy is the proxy's address for every caller. Left unset, all clients therefore share one failure bucket: five wrong guesses by anybody locks the login form for everybody for 60 seconds, renewable indefinitely — a self-inflicted denial of service. HTTP Basic is unaffected, so a scripted operator always retains a way in, which is what keeps this an availability problem rather than an access one.List the proxy addresses or CIDR ranges you control (
PULLBACKUP_TRUSTED_PROXIES=172.18.0.0/16) and each real client gets its own bucket.X-Forwarded-Foris honoured only on requests arriving from a listed address, and even then it is walked right-to-left past listed hops, so a client cannot pick its own identity by prepending entries. From an unlisted address the header is ignored outright — it can never be used to evade the throttle. The trade-off is real, so list only proxies you control: any host in that list can claim to be forwarding for any client. A malformed value is refused at startup rather than silently disabling the setting. -
Sessions are single-user and held in process memory: there is no revocation list that survives a restart, and a restart signs everyone out. Sign-out revokes the session server-side, so a copied cookie stops working too.
-
Set
PULLBACKUP_SESSION_SECRETif you need to change the password without invalidating live sessions. Leaving it unset is the safer default: rotating the credential then also rotates every session.