Skip to content

fix(api): keep reverse-proxy path prefix in paging next url#996

Open
TowyTowy wants to merge 1 commit into
gotify:masterfrom
TowyTowy:fix/664-proxy-path-prefix
Open

fix(api): keep reverse-proxy path prefix in paging next url#996
TowyTowy wants to merge 1 commit into
gotify:masterfrom
TowyTowy:fix/664-proxy-path-prefix

Conversation

@TowyTowy

@TowyTowy TowyTowy commented Jul 9, 2026

Copy link
Copy Markdown

Fixes #664

When gotify runs behind a reverse proxy that strips a subpath before forwarding (e.g. Caddy uri strip_prefix /gotify), the incoming request path no longer contains the prefix, so the next paging URL emitted by GetMessages dropped it — returning https://example.com/message?... instead of https://example.com/gotify/message?....

As you noted in the issue, this case was missed in #127 (which fixed subpath handling for the WebUI/images by moving to relative URLs, but left the API's absolute next URL untouched).

This honors the standard X-Forwarded-Prefix header — which such proxies set to announce the stripped prefix — when building the next URL, consistent with how gotify already trusts X-Forwarded-Proto/X-Forwarded-Host for scheme and host. Behavior is unchanged when the header is absent, so it is fully backward compatible.

Proxy config example (Caddy): add header_up X-Forwarded-Prefix /gotify alongside uri strip_prefix /gotify.

Added Test_GetMessages_WithLimit_BehindProxyPrefix_ReturnsNextWithPrefix; go test ./api/ passes.

On direction: you mentioned possibly deprecating next in favour of clients building URLs manually. This is meant as the minimal, backward-compatible fix that makes next correct today for existing clients, and it does not preclude that deprecation later — but if you would rather go straight to deprecating it, happy to close this. Your call.

When gotify runs behind a reverse proxy that strips a subpath before
forwarding (e.g. Caddy `uri strip_prefix /gotify`), the incoming request
path no longer contains the prefix, so the `next` paging url emitted by
GetMessages dropped it (e.g. `/message` instead of `/gotify/message`).

Honor the X-Forwarded-Prefix header, which such proxies can set to
announce the stripped prefix, when constructing the `next` url so it
keeps pointing at the externally reachable path. Behavior is unchanged
when the header is absent.

Fixes gotify#664

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@TowyTowy TowyTowy requested a review from a team as a code owner July 9, 2026 10:44
@eternal-flame-AD

Copy link
Copy Markdown
Member

Hi, thanks for the contribution.

I think in general XF headers should be a gated opt-in feature due to security and misconfiguration risk. Maybe this time there isn't an immediate vulnerability, in the future we may want to introduce rate limiting and such and I hope there is a master control for all/each of these magic headers.

Also, given that XFP is so obscure it isn't even on MDN, maybe an alternative solution is just add a config option to override the public URL of the instance?

@TowyTowy

Copy link
Copy Markdown
Author

Thanks for taking a look, and fair point on the security framing.

For context on where things stand today: gotify already trusts X-Forwarded-For, but gated — router.go sets RemoteIPHeaders = ["X-Forwarded-For"] + SetTrustedProxies(conf.Server.TrustedProxies), so gin only honors it when the request comes from a configured trusted proxy. The gap you're pointing at is real: this PR reads X-Forwarded-Prefix unconditionally, so unlike XFF it isn't covered by that GOTIFY_SERVER_TRUSTEDPROXIES master control.

Two ways I'm happy to take it, whichever you prefer:

  1. Gate the X-Forwarded-Prefix read behind a trusted-proxy check that reuses the existing TrustedProxies config, so it's only honored from a trusted proxy and inherits the same master control — no new config, consistent with how XFF is already handled.
  2. Your suggestion — add a config option for the instance's public base URL — and build the paging next URL from that instead of any forwarded header.

(1) keeps things working with zero extra config behind a correctly-configured proxy; (2) is more explicit and sidesteps the magic headers entirely. I lean slightly toward (1) since it reuses infrastructure you already have and needs no user action, but the design direction is your call — tell me which you'd rather and I'll rework it accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

option to include back path prefix

2 participants