Skip to content

feat: support X-Forwarded-Prefix and X-Ingress-Path (#7802)#7806

Merged
JohnMcLear merged 12 commits into
developfrom
feat/url-base-path-7802
May 18, 2026
Merged

feat: support X-Forwarded-Prefix and X-Ingress-Path (#7802)#7806
JohnMcLear merged 12 commits into
developfrom
feat/url-base-path-7802

Conversation

@JohnMcLear
Copy link
Copy Markdown
Member

Summary

Etherpad already honored a custom x-proxy-path header 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:

  • Extends sanitizeProxyPath to honor X-Forwarded-Prefix (HAProxy / Traefik / Nginx) and X-Ingress-Path (Home Assistant supervisor ingress) in addition to x-proxy-path. Standard headers are gated on settings.trustProxy === true; x-proxy-path stays un-gated (operator-controlled).
  • /manifest.json icon src and start_url now reflect the proxy path. Vary and Cache-Control set 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. publicURL still wins when set.
  • Plain template touch-ups: leading-slash href / action in index.html, pad.html, timeslider.html, export_html.html now prefix-aware via the existing EJS render context.
  • Drive-by fix: pad.html and timeslider.html had ../../manifest.json / ../../../manifest.json — one .. too many. Resolved to /manifest.json from any pad URL (works at root by luck; broken under prefix). Reduced to ../manifest.json / ../../manifest.json so 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 (extended sanitizeProxyPath.test.ts truth table for both new header sources and the trustProxy gate).
  • mocha tests/backend/specs — 1493 passing, 22 pending, 0 failing. Includes the new pwaManifest.ts (5 tests), extended socialMeta-unit.ts (4 new), and a new end-to-end urlBasePath.ts (10 tests) that asserts every public surface emits the prefix consistently for x-proxy-path and x-ingress-path-under-trustProxy, and that the no-header case is byte-identical.
  • Manual smoke through the Home Assistant ingress addon (deferred to the next addon release candidate).
  • Manual smoke through an nginx subpath proxy.

Design + plan: docs/superpowers/specs/2026-05-18-issue-7802-url-base-path-support-design.md and docs/superpowers/plans/2026-05-18-issue-7802-url-base-path.md (included in this branch).

🤖 Generated with Claude Code

JohnMcLear and others added 12 commits May 18, 2026 12:10
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>
)

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-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Support X-Forwarded-Prefix and X-Ingress-Path headers for reverse proxy URL base-path prefixes

✨ Enhancement

Grey Divider

Walkthroughs

Description
• 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
Diagram
flowchart 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"]
Loading

Grey Divider

File Changes

1. src/node/utils/sanitizeProxyPath.ts ✨ Enhancement +49/-32

Extend header sources and add trustProxy gating

src/node/utils/sanitizeProxyPath.ts


2. src/tests/backend-new/specs/sanitizeProxyPath.test.ts 🧪 Tests +84/-0

Add truth table tests for new headers and trustProxy

src/tests/backend-new/specs/sanitizeProxyPath.test.ts


3. src/node/hooks/express/pwa.ts ✨ Enhancement +17/-11

Make /manifest.json prefix-aware with cache headers

src/node/hooks/express/pwa.ts


View more (14)
4. src/tests/backend/specs/pwaManifest.ts 🧪 Tests +97/-0

New test coverage for /manifest.json prefix handling

src/tests/backend/specs/pwaManifest.ts


5. src/node/utils/socialMeta.ts ✨ Enhancement +16/-4

Thread proxyPath through buildAbsoluteUrl and resolveImageUrl

src/node/utils/socialMeta.ts


6. src/tests/backend/specs/socialMeta-unit.ts 🧪 Tests +77/-0

Add proxyPath fallback tests for og:url and og:image

src/tests/backend/specs/socialMeta-unit.ts


7. src/node/hooks/express/specialpages.ts ✨ Enhancement +15/-2

Pass proxyPath to all template renders and socialMeta calls

src/node/hooks/express/specialpages.ts


8. src/templates/index.html ✨ Enhancement +2/-2

Make manifest and jslicense links proxyPath-aware

src/templates/index.html


9. src/templates/pad.html 🐞 Bug fix +3/-3

Fix manifest .. count; make reconnect/jslicense proxyPath-aware

src/templates/pad.html


10. src/templates/timeslider.html 🐞 Bug fix +3/-3

Fix manifest .. count; make reconnect/jslicense proxyPath-aware

src/templates/timeslider.html


11. src/templates/export_html.html ✨ Enhancement +1/-1

Make manifest link proxyPath-aware

src/templates/export_html.html


12. src/tests/backend/specs/urlBasePath.ts 🧪 Tests +129/-0

New end-to-end integration test for proxy-path consistency

src/tests/backend/specs/urlBasePath.ts


13. src/node/utils/Settings.ts 📝 Documentation +13/-3

Document trustProxy gating of standard headers

src/node/utils/Settings.ts


14. settings.json.template 📝 Documentation +9/-0

Document X-Forwarded-Prefix and X-Ingress-Path support

settings.json.template


15. docs/superpowers/specs/2026-05-18-issue-7802-url-base-path-support-design.md 📝 Documentation +275/-0

Design spec for URL base-path support architecture

docs/superpowers/specs/2026-05-18-issue-7802-url-base-path-support-design.md


16. docs/superpowers/plans/2026-05-18-issue-7802-url-base-path.md 📝 Documentation +1264/-0

Detailed implementation plan with task breakdown

docs/superpowers/plans/2026-05-18-issue-7802-url-base-path.md


17. src/node/utils/ExportHtml.ts Additional files +1/-0

...

src/node/utils/ExportHtml.ts


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

Looking for bugs?

Check back in a few minutes. An AI review agent is analyzing this pull request.

Grey Divider

Qodo Logo

@JohnMcLear JohnMcLear merged commit 86edd67 into develop May 18, 2026
36 of 37 checks passed
@JohnMcLear JohnMcLear deleted the feat/url-base-path-7802 branch May 18, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plugin assets + admin UI break when Etherpad runs under a URL base path (HA Ingress, sub-path reverse proxy)

1 participant