Problem
app/middleware/pagination.js builds absolute URLs for the RFC 5988 Link header using req.protocol + req.get('host'):
const proto = (req.protocol || 'http');
const host = (req.get && req.get('host')) || 'localhost';
return `${proto}://${host}${basePath}?${params.toString()}`;
The Host header is client-controlled. Any deployment that doesn't already filter unknown Host values upstream — raw docker-compose up without the TLS layer, a NAT setup that accepts any Host, sidecar-style local exposure — can be fed Host: evil.com and reflect it back into the next/prev/first/last URLs. A paginating client following those URLs would then hit the attacker's hostname for the rest of its result-set walk.
Repro:
curl -H 'Host: evil.com' 'http://localhost:3000/v1/customer/bycompany/1?limit=10&offset=0' -i | grep -i ^link
# Link: <http://evil.com/v1/customer/bycompany/1?limit=10&offset=10>; rel="next", …
Proposed fix
Opt-in PUBLIC_BASE_URL env var. When set, the pagination helper uses it as the canonical base; when unset, fall back to the existing req-derived behavior so the TLS-fronted compose deployment (Caddy with TLS_DOMAIN pinned) and any other Host-filtering proxy keep working without configuration.
Strip trailing slashes so operators who include them don't end up with //path in the emitted links. Treat whitespace-only values as unset.
Document in README env table and .env.example.
Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/
Problem
app/middleware/pagination.jsbuilds absolute URLs for the RFC 5988Linkheader usingreq.protocol+req.get('host'):The
Hostheader is client-controlled. Any deployment that doesn't already filter unknownHostvalues upstream — rawdocker-compose upwithout the TLS layer, a NAT setup that accepts any Host, sidecar-style local exposure — can be fedHost: evil.comand reflect it back into the next/prev/first/last URLs. A paginating client following those URLs would then hit the attacker's hostname for the rest of its result-set walk.Repro:
Proposed fix
Opt-in
PUBLIC_BASE_URLenv var. When set, the pagination helper uses it as the canonical base; when unset, fall back to the existing req-derived behavior so the TLS-fronted compose deployment (Caddy withTLS_DOMAINpinned) and any other Host-filtering proxy keep working without configuration.Strip trailing slashes so operators who include them don't end up with
//pathin the emitted links. Treat whitespace-only values as unset.Document in README env table and
.env.example.Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/