github ether/etherpad v3.3.6

3 hours ago

3.3.6

3.3.6 is a security and bug-fix release. It closes an OIDC login bypass for accounts configured without a password (GHSA-62cj-9j72-mfrh), and fixes a batch of reported defects across the installer, the admin settings editor, session transfer, the welcome screen, accessibility and plugin configuration.

Security

  • OIDC — refuse interactive logins for accounts without a password (GHSA-62cj-9j72-mfrh, #8247). The embedded OpenID Connect provider compared the submitted password against String(user.password), so a settings.users account with no password property compared against the literal string "undefined", and one with "password": null against "null". Submitting that literal logged the account in and issued a token carrying its admin claim; with the default authenticationMethod: "sso" the HTTP API accepts that token. Accounts without a usable password occur in practice — the container image leaves password null when ADMIN_PASSWORD is unset, and an ep_hash_auth entry replaces password with hash. The sibling HTTP Basic path in webaccess.ts already failed closed here; the credential check now lives in verifyInteractiveLogin() and refuses any account without a real string password rather than coercing a missing secret to a literal. Hash-only entries are refused on this path too, since they authenticate through the authenticate hook, which it does not consult. Reported by Wenhao Wu (Southeast University).

Notable fixes

  • PDF export honours font-family without LibreOffice (#8245, #8249). The built-in PDF path used only pdfkit's Helvetica and Courier and ignored font-family entirely, so any font applied by a plugin such as ep_font_family was lost — while HTML, ODT and DOCX all carried it. Font families are now mapped onto the PDF standard fonts by category (sans-serif to Helvetica, serif to Times, monospace to Courier), including the bold and italic variants, honouring declaration order and !important. Exact non-standard faces can be supplied by an operator through the new exportPdfFonts setting, which points a family at TTF/OTF files; no fonts are bundled. Every failure path degrades with a warning rather than failing the export, and pads with no font styling export exactly as before. Family names arrive from pad content, so they are normalised and matched against an allow-list and are never used as a file path.
  • Installer — the Node version check no longer fails under Windows PowerShell 5.1 (#8214, #8235). bin/installer.ps1 read the major version with node -p 'process.versions.node.split(".")[0]'. Windows PowerShell 5.1 — the default shell on Windows 10 and 11, and a version the script declares support for — strips the double quotes when passing arguments to a program, so Node received split(.)[0] and threw a SyntaxError. The empty result became 0, and the installer rejected every Node version as too old. The version now comes from node --version parsed in PowerShell, and an unparsable result reports that rather than claiming the version is too old. The Windows CI job now runs under both PowerShell 7 and Windows PowerShell 5.1.
  • Admin — settings form fields honour escape sequences (#8211, #8239). In the settings form view (raw mode was unaffected), string settings are edited in single-line inputs. Plain strings such as defaultPadText were rendered with literal newlines, which the browser silently strips from a single-line input, and whatever the user typed was escaped a second time on save, so \n was written as \\n. Environment-variable defaults such as ${DEFAULT_PAD_TEXT:...} were shown escaped but escaped again on save. Both widgets now display values in escaped form and decode them before saving, so typing Welcome\n\ntest\n writes the same bytes as editing settings.json by hand. A half-typed escape is not saved: the field is marked invalid and reverts to the last saved value on blur.
  • Session transfer — preferences survive the transfer, and the cookie is no longer double-encoded (#8171, #8238). The transfer only handled the prefsHttp cookie, but over HTTPS the pad stores its preferences in prefs, so nothing was sent and the receiving side wrote a cookie the destination never reads. The client also sent the cookie still percent-encoded and res.cookie() encoded it again, leaving a value the destination pad could not parse, so it silently fell back to defaults. The server now reads the preferences from the request's own cookies (accepting either name, with or without the cookie prefix), accepts only a JSON object, and writes prefs or prefsHttp according to req.secure, encoded once. When there is nothing to transfer no cookie is written, so an existing destination preference set is no longer wiped. Author-token handling is unchanged.
  • Session transfer — the dialog describes what actually happens (#8173, #8236). The home-page dialog offered to copy a "link" that would move your "session". It copies a one-time code, valid once and for five minutes, that is pasted into the Receive session tab, and what moves is the author identity and preferences, not a sign-in session. The English wording of the existing strings now says so.
  • Welcome screen — deleted pads leave the recent list (#8201, #8237). The Recent pads list is stored in the browser and nothing ever removed an entry, so a deleted pad stayed listed and opening it silently created a new, empty pad under the same name. Clients now drop the pad from the list when the server announces the deletion, which covers the creator's own Delete pad action, a deletion performed with the recovery token from another device, and any other tab open on the pad. Names stored URL-encoded by older versions are matched too. Pads deleted through the HTTP API or the admin interface still linger in browsers that had no tab open on them, since the list is per-browser.
  • Accessibility — screen readers can move through a pad line by line (#7778, #8240). Every pad line is rendered as a plain <div> with no role, which browsers expose as an anonymous generic node, so assistive technology saw one flattened run of text with no line boundaries and no way to step between lines or reach the links on a line. Plain lines now carry role="paragraph"; lines that already contain a semantic block element, such as list items and headings from ep_headings2, keep their native semantics. The element itself is unchanged, so plugin selectors that target div.ace-line are unaffected.
  • Plugins — settings.ep_<plugin> config blocks are reachable again from require() (#8109, #8110). Plugins read their own configuration out of a top-level ep_* block in settings.json via require('ep_etherpad-lite/node/utils/Settings'). The CJS-compatibility shim in Settings.ts installed accessor properties on module.exports for the keys present on the settings object while that module was still evaluating — but ep_* blocks are only merged in later, by the reloadSettings() call at the bottom of the same module. Every plugin config block was therefore invisible to the require() path (the value was reachable only under .default), so plugins silently fell back to their built-in defaults. For ep_hash_auth that meant hash_dir reverted to /var/etherpad/users, every hash lookup failed, and admin login returned 401 with no usable diagnostic — the symptom that surfaced this. The shim is now re-run after each settings load. Reported by @mathewcsims and @tris-ots; an equivalent fix was also proposed by @AkprasadoP in #8113.

Don't miss a new etherpad release

NewReleases is sending notifications on new releases.