mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 01:54:10 +00:00
Say what the default TRUSTED_PROXIES trusts, and where to report a hole
#34 points out that the default trusts every private range, so a machine on the LAN that reaches plMail directly can forge X-Forwarded-For and step around the per-address limits on the login form and the booking page. The default stays. The proxy's own address cannot be known here, and a proxy missing from the list breaks OAuth sign-in, shares one login allowance between everybody and strips `secure` off cookies, with nothing saying why — a worse failure, on every proxied install, than the one being closed. What was missing is the sentence: the comment in .env and the reverse-proxy page now say that the default is wide, what that costs, what it does not (the per-account login limit ignores the address and still holds), and how to narrow it once the proxy works. SECURITY.md is new. The report was filed publicly because there was nowhere private to send it. Refs #34 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
219fc9e283
commit
3e0b4456cf
5 files changed
+117
-1
No files matched your search
@@ -175,6 +175,19 @@ MICROSOFT_OAUTH_TENANT=common
|
||||
APP_DB_LOG_LEVEL=warning
|
||||
APP_CONTAINER_NAME=web
|
||||
|
||||
# WIDE ON PURPOSE, AND WORTH NARROWING. Loopback plus every private range,
|
||||
# because a reverse proxy on the same Docker network or LAN has an address in
|
||||
# one of them that cannot be known here — and a proxy that is NOT in this list
|
||||
# breaks sign-in with OAuth, shares one login allowance between everybody and
|
||||
# strips `secure` off cookies, none of which says why.
|
||||
#
|
||||
# The cost of the width: anything else with a private address that can reach
|
||||
# plMail directly — another machine on the LAN, another container — is believed
|
||||
# when it sends X-Forwarded-For, and can pose as any address it likes. That
|
||||
# gets it past the PER-ADDRESS limits on the login form and the public booking
|
||||
# page. It does not get it past the per-account login limit, which ignores the
|
||||
# address. Once your proxy works, replace this with the proxy's own address or
|
||||
# subnet; docs/install/reverse-proxy.md says how to find it.
|
||||
TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
|
||||
###> lexik/jwt-authentication-bundle ###
|
||||
|
||||
@@ -27,6 +27,18 @@ ran code, both of which let an outsider put misleading entries on a calendar unn
|
||||
server's verdict is believed when its name is on the same domain as the IMAP host.
|
||||
- **Mail in Spam or the bin is no longer read for events.** A message moved back out is read then.
|
||||
|
||||
**A mail's styles can no longer draw over plMail when you reply to it or print it.** Inline styles
|
||||
are kept because in mail they are the design, and in the reading pane that is harmless: the message
|
||||
is in a sandboxed frame. The quote in the reply composer and the print page are not framed, and a
|
||||
mail using `position: fixed` could cover the composer with content of its own. Such declarations are
|
||||
now removed, and both places clip what is inside them. Also from #34.
|
||||
|
||||
**The default `TRUSTED_PROXIES` is documented for what it is.** It trusts every private address so a
|
||||
reverse proxy works unconfigured, which also lets another machine on the LAN forge its address and
|
||||
step around the per-address limits on login and booking. The per-account login limit is unaffected.
|
||||
The default is unchanged — narrowing it blindly would break sign-in behind a proxy — and
|
||||
[Reverse proxy](docs/install/reverse-proxy.md) now says how and when to narrow it. Also from #34.
|
||||
|
||||
Nothing already on a calendar is removed or changed by upgrading. One new nullable column,
|
||||
`event_source_link.hold_reason`. See
|
||||
[Invitations and events from mail](docs/features/calendar-invitations.md).
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# Security
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
Please report it privately first: on GitHub, open the repository's **Security** tab and choose
|
||||
**Report a vulnerability**. That opens a private advisory only the maintainer can read, and it is
|
||||
where the fix and the disclosure are coordinated.
|
||||
|
||||
If you would rather not use GitHub for it, open an ordinary issue that says only that you have a
|
||||
security report and how to reach you — no details — and you will be contacted.
|
||||
|
||||
What helps most:
|
||||
|
||||
- which version or commit you looked at, and whether you ran it or read the code;
|
||||
- what an attacker needs (an account, a place on the network, only the ability to send mail);
|
||||
- what they get, in a sentence;
|
||||
- the files and lines involved, if you know them.
|
||||
|
||||
You will get an answer within a few days. plMail is maintained by one person in spare time, so a
|
||||
fix can take longer than that; you will be told what the plan is either way. Reporters are credited
|
||||
in the changelog unless they ask not to be.
|
||||
|
||||
## What counts
|
||||
|
||||
plMail is self-hosted, so the things worth reporting are the ones that cross a boundary an
|
||||
installation relies on:
|
||||
|
||||
- one user reading or changing another user's mail, calendar or settings;
|
||||
- anything a **sender of mail** can make happen on the reader's side beyond showing them a
|
||||
message — running script in the app, changing the calendar, reaching the reader's session;
|
||||
- anything an unauthenticated visitor can do beyond the pages meant for them (login, shared
|
||||
calendar links, booking pages, the demo door);
|
||||
- secrets or tokens leaving the install.
|
||||
|
||||
How the app is meant to hold those lines is written down in
|
||||
[docs/internals/security-model.md](docs/internals/security-model.md). A report that shows one of
|
||||
its claims to be false is exactly what this file is for.
|
||||
|
||||
## Supported versions
|
||||
|
||||
Fixes go into `main` and the next release. There are no maintained older branches: the answer to a
|
||||
fixed vulnerability is upgrading.
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- translated-from: install/reverse-proxy.md sha1:72aecc70f1035f186e2322b3152b8dd4e0ad00f4 -->
|
||||
<!-- translated-from: install/reverse-proxy.md sha1:781b856b21629be768f778e6c04d76360defb8ec -->
|
||||
# Hinter einem Reverse-Proxy
|
||||
|
||||
Alles, was von außerhalb des eigenen Netzes erreichbar ist, will ein echtes Zertifikat davor, und
|
||||
@@ -103,6 +103,26 @@ Setzt du ihn zu weit — alles wird als vertrauenswürdig behandelt —, dann ka
|
||||
`X-Forwarded-For` fälschen und sich als beliebige Adresse ausgeben, was die Ratenbegrenzung pro
|
||||
Adresse aushebelt und einen frei wählbaren Wert in deine Logs schreibt.
|
||||
|
||||
**Die mitgelieferte Vorgabe liegt mit Absicht auf der weiten Seite.** Sie vertraut jeder privaten
|
||||
Adresse, weil sich die Adresse des Proxys nicht im Voraus wissen lässt und ihr Fehlen die vier Dinge
|
||||
oben kaputt macht. Der Preis: Jede andere Maschine in deinem LAN und jeder andere Container im
|
||||
Docker-Netz, die plMail *am Proxy vorbei* erreichen, wird geglaubt, wenn sie `X-Forwarded-For`
|
||||
schickt. Sie kann sich dann bei jeder Anfrage als neue Adresse ausgeben und die Grenzen umgehen, die
|
||||
pro Adresse zählen: die 25 Anmeldeversuche pro 15 Minuten von einer Adresse und die Grenze der
|
||||
öffentlichen Buchungsseite. Die Grenze von 20 Versuchen pro **Konto** schaut nicht auf die Adresse
|
||||
und gilt weiter, ein einzelnes Konto lässt sich so also nicht schneller durchprobieren.
|
||||
|
||||
Sobald der Proxy funktioniert, eng es auf den Proxy allein ein:
|
||||
|
||||
```dotenv
|
||||
# die Adresse des Proxys oder das Subnetz des Docker-Netzes, das er mit plMail teilt
|
||||
TRUSTED_PROXIES=127.0.0.1,172.20.0.0/16
|
||||
```
|
||||
|
||||
`docker network inspect <netz> --format '{{(index .IPAM.Config 0).Subnet}}'` gibt das Subnetz eines
|
||||
Docker-Netzes aus. Ist der Port von plMail nur für den Proxy und nicht ins LAN veröffentlicht,
|
||||
kostet die weite Vorgabe nichts — es gibt sonst niemanden, dem geglaubt würde.
|
||||
|
||||
**Der typische Fehlerfall ist, das als OAuth-Problem zu diagnostizieren.** Eine
|
||||
Redirect-URI-Abweichung nennt die URI, und das `http://` darin ist der ganze Hinweis.
|
||||
|
||||
@@ -256,6 +276,11 @@ eine Installation, die ihre eigene Konfiguration verwaltet, ist das das gewünsc
|
||||
bedeutet aber auch: Änderst du die Adresse in der Compose-Datei und erwartest, dass der Wert aus dem
|
||||
Einrichtungsbildschirm zählt, bearbeitest du die falsche Stelle, und umgekehrt.
|
||||
|
||||
**Die Vorgabe für `TRUSTED_PROXIES` glaubt deinem ganzen privaten Netz.** Deshalb funktioniert ein
|
||||
Proxy ohne weiteres Zutun, und es heißt, dass eine Maschine im LAN, die plMail direkt erreicht, ihre
|
||||
Adresse fälschen und die Ratenbegrenzung pro Adresse umgehen kann. Eng es ein, sobald der Proxy
|
||||
läuft — siehe oben.
|
||||
|
||||
**Ein falsches `TRUSTED_PROXIES` sieht aus wie vier voneinander unabhängige Fehler.**
|
||||
OAuth-Abweichungen, ein von allen geteiltes Kontingent an Anmeldeversuchen, Cookies ohne `secure`
|
||||
und Freigabelinks mit falschem Schema sind ein und dieselbe Einstellung.
|
||||
|
||||
@@ -94,6 +94,26 @@ Get it wrong in the wide direction — trusting everything — and a client can
|
||||
and present itself as any address it likes, which defeats the per-address rate limits and puts a
|
||||
chosen value into your logs.
|
||||
|
||||
**The committed default is on the wide side of that, deliberately.** It trusts every private
|
||||
address, because the proxy's own address cannot be known in advance and leaving it out breaks the
|
||||
four things above. The price is that any other machine on your LAN, or any other container on the
|
||||
Docker network, that can reach plMail *without* going through the proxy is believed when it sends
|
||||
`X-Forwarded-For`. It can then pose as a new address for every request and step around the limits
|
||||
that count per address: the 25-per-15-minutes cap on login attempts from one address, and the cap
|
||||
on the public booking page. The limit of 20 attempts per **account** does not look at the address
|
||||
and still holds, so one account cannot be guessed at faster this way.
|
||||
|
||||
Once the proxy works, narrow it to the proxy alone:
|
||||
|
||||
```dotenv
|
||||
# the proxy's address, or the subnet of the Docker network it shares with plMail
|
||||
TRUSTED_PROXIES=127.0.0.1,172.20.0.0/16
|
||||
```
|
||||
|
||||
`docker network inspect <network> --format '{{(index .IPAM.Config 0).Subnet}}'` prints the subnet
|
||||
of a Docker network. If plMail's port is published only to the proxy and not to the LAN, the wide
|
||||
default costs nothing — there is nobody else to believe.
|
||||
|
||||
**The failure mode is diagnosing this as an OAuth problem.** A redirect-URI mismatch names the URI,
|
||||
and the `http://` in it is the entire clue.
|
||||
|
||||
@@ -235,6 +255,10 @@ intended behaviour for a deployment that manages its own configuration — but i
|
||||
operator who changes the address in the Compose file and expects the setup screen's value to matter
|
||||
is editing the wrong one, and vice versa.
|
||||
|
||||
**The default `TRUSTED_PROXIES` believes your whole private network.** That is what makes a proxy
|
||||
work out of the box, and it means a machine on the LAN that reaches plMail directly can forge its
|
||||
address and dodge the per-address rate limits. Narrow it once the proxy is working — see above.
|
||||
|
||||
**A wrong `TRUSTED_PROXIES` looks like four unrelated bugs.** OAuth mismatches, everyone sharing a
|
||||
login-attempt allowance, cookies without `secure`, and share links with the wrong scheme are all one
|
||||
setting.
|
||||
|
||||
Reference in new issue
Block a user