feat: support X-Forwarded-Prefix and X-Ingress-Path (#7802)#7806
Conversation
Spec covers the architecture, header handling rules, components touched, backwards-compatibility story, risks, and test plan for honoring X-Forwarded-Prefix / X-Ingress-Path under trustProxy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After exploring the codebase, much of the proposed architecture is already in place (sanitizeProxyPath, padBootstrap.js basePath derivation, admin SPA rewrite). Spec now reflects the actual delta: header source expansion, /manifest.json prefix-awareness, socialMeta proxyPath honoring, and template URL touch-ups for index/timeslider/pad/export. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the bite-sized TDD task list to ship X-Forwarded-Prefix / X-Ingress-Path support: extends sanitizeProxyPath, makes /manifest.json and socialMeta prefix-aware, touches up the remaining leading-slash URLs in index/pad/timeslider/export templates, fixes a pre-existing manifest .. count bug. Drops the originally-proposed <base href> belt-and-braces after discovering it'd break the existing relative URLs in pad.html/timeslider.html and wouldn't help plugin DOM injection anyway (path-absolute URLs ignore <base>'s path component). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Proxy (#7802) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#7802) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#7802) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nifest .. count (#7802) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… fix manifest .. count (#7802) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lable (#7802) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s-Path (#7802) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Review Summary by QodoSupport X-Forwarded-Prefix and X-Ingress-Path headers for reverse proxy URL base-path prefixes
WalkthroughsDescription• Extends sanitizeProxyPath to honor X-Forwarded-Prefix and X-Ingress-Path headers (gated on settings.trustProxy === true) in addition to existing x-proxy-path • Makes /manifest.json prefix-aware: icon src and start_url now reflect the proxy path with appropriate cache headers • Makes social-meta tags (og:url, og:image) honor proxy path when falling back to from-request origin • Updates templates (index.html, pad.html, timeslider.html, export_html.html) to use proxy-path-prefixed URLs for manifest links, jslicense links, and reconnect form actions • Fixes pre-existing manifest relative-path bugs in pad.html and timeslider.html (reduces .. count by one for correctness under prefix) • Adds comprehensive test coverage for proxy-path handling across all public surfaces Diagramflowchart LR
A["Request with<br/>X-Forwarded-Prefix or<br/>X-Ingress-Path"] -->|sanitizeProxyPath| B["Sanitized proxyPath<br/>or empty string"]
B -->|threaded to| C["Templates<br/>index/pad/timeslider"]
B -->|threaded to| D["/manifest.json<br/>handler"]
B -->|threaded to| E["socialMeta<br/>og:url/og:image"]
C -->|renders| F["HTML with<br/>prefixed URLs"]
D -->|returns| G["JSON with<br/>prefixed paths"]
E -->|builds| H["Absolute URLs<br/>with prefix"]
File Changes1. src/node/utils/sanitizeProxyPath.ts
|

Summary
Etherpad already honored a custom
x-proxy-pathheader for URL base-path support. This PR adds the two standard headers that the wider ecosystem uses and tightens the remaining surfaces that still emitted root-absolute URLs:sanitizeProxyPathto honorX-Forwarded-Prefix(HAProxy / Traefik / Nginx) andX-Ingress-Path(Home Assistant supervisor ingress) in addition tox-proxy-path. Standard headers are gated onsettings.trustProxy === true;x-proxy-pathstays un-gated (operator-controlled)./manifest.jsoniconsrcandstart_urlnow reflect the proxy path.VaryandCache-Controlset when the prefix is non-empty so intermediaries don't collapse responses.socialMeta(og:url,og:image,twitter:image) honors the proxy path when falling back to from-request origin.publicURLstill wins when set.href/actioninindex.html,pad.html,timeslider.html,export_html.htmlnow prefix-aware via the existing EJS render context.../../manifest.json/../../../manifest.json— one..too many. Resolved to/manifest.jsonfrom any pad URL (works at root by luck; broken under prefix). Reduced to../manifest.json/../../manifest.jsonso it works in both cases.No
<base href>was introduced — it would break existing relative URLs in pad.html and wouldn't help plugin-injected leading-slash URLs anyway (path-absolute URLs deliberately ignore<base>'s path component).Closes #7802.
Test plan
pnpm vitest run— 702/702 passing across 44 files (extendedsanitizeProxyPath.test.tstruth table for both new header sources and the trustProxy gate).mocha tests/backend/specs— 1493 passing, 22 pending, 0 failing. Includes the newpwaManifest.ts(5 tests), extendedsocialMeta-unit.ts(4 new), and a new end-to-endurlBasePath.ts(10 tests) that asserts every public surface emits the prefix consistently forx-proxy-pathandx-ingress-path-under-trustProxy, and that the no-header case is byte-identical.Design + plan:
docs/superpowers/specs/2026-05-18-issue-7802-url-base-path-support-design.mdanddocs/superpowers/plans/2026-05-18-issue-7802-url-base-path.md(included in this branch).🤖 Generated with Claude Code