3.3.2
3.3.2 is a bug-fix and dependency-hardening follow-up to 3.3.1. It rounds out the pad-deletion UX rework (suppressing the recovery token for durable identities, keeping the token-less Delete button reachable, and closing a read-only deletion hole), restores the saved-revision markers that went missing from in-pad history mode in 3.3.x, and adds env-var overrides so air-gapped installs can switch off Etherpad's outbound calls without editing the image. It also fixes the migrateDB / importSqlFile / migrateDirtyDBtoRealDB CLI scripts against the promise-based ueberdb2 API, rejects unreachable ./.. pad ids, and clears a batch of dependency security advisories (including CVE-2026-54285). On the CI side it unblocks the installer smoke test (which had been hanging the full 6-hour job ceiling since 3.2.0) and pins ueberdb2 past a startup-exit regression in the packaged boot.
Security
- Force
@opentelemetry/core≥ 2.8.0 (GHSA-8988-4f7v-96qf / CVE-2026-54285, #7975). The transitive dep (pulled in via@elastic/elasticsearch→@elastic/transport) had aW3CBaggagePropagator.extract()that did not enforce W3C size limits on inbound baggage headers, allowing unbounded memory allocation. Pinned via apnpm-workspace.yamloverride; satisfies the existing2.xrange with no parent bump. - Resolve open Dependabot security alerts (#7967). Refreshes stale override floors and adds new ones via
pnpm-workspaceoverrides:form-data≥ 4.0.6,ws≥ 8.21.0,esbuild≥ 0.28.1,basic-ftp≥ 5.3.1 (capped<6.0.0to avoid a surprise major on the plugin-install path),tar≥ 7.5.16,js-yaml≥ 4.2.0,qs≥ 6.15.2,ip-address≥ 10.1.1, and@babel/core≥ 7.29.6. - Reject read-only deletion via token-less paths (part of #7959 / #7960). Under
allowPadDeletionByAllUsersa read-only viewer was grantedcanDeletePad=true, and the server'sflagOk/creatorOkbranches never checkedsession.readonly— so a read-only link holder could delete a pad without a token. Read-only sessions are now excluded from both the client var and the server's token-less authorization paths; a valid recovery token stays sufficient regardless of session mode.
Notable enhancements
- Pad deletion — suppress the recovery token for durable identities and relabel the action (#7926 / #7930). Building on the
allowPadDeletionByAllUserssuppression, a creator's deletion token is now also withheld when they have a durable identity — authenticated (req.session.userwith a username) and the deployment pins that identity to a stableauthorIDvia agetAuthorIdhook — since only then does the creator survive a cookie clear or a different device, making the token redundant. This tightens the previous "require authentication ⇒ always suppress" rule: withoutgetAuthorIdthe authorID still comes from the per-browser cookie, so an authenticated user on a second device is not the creator and keeps getting a token. A newcanDeleteWithoutTokenclient var hides the whole recovery-token disclosure (label, field, submit) when no token is needed, and the recovery form now renders for all sessions (hidden by default) so an authenticated creator without a durable mapping still has UI to enter their token.API.createPadreturns anulldeletionTokenunderallowPadDeletionByAllUsers, matching the socket/UI path. - Offline/air-gapped installs — env-var overrides for the update check, plugin catalog, and updater (#7917, addresses #7911). Firewalled deployments could not disable Etherpad's outbound calls without editing
settings.jsoninside the image. The relevant keys are now wired through the${ENV:default}substitution insettings.json.dockerandsettings.json.template:PRIVACY_UPDATE_CHECK,PRIVACY_PLUGIN_CATALOG,UPDATES_TIER(off= no calls),UPDATE_SERVER, plus the docker-onlyUPDATES_SOURCE/UPDATES_CHANNEL/UPDATES_CHECK_INTERVAL_HOURS/UPDATES_GITHUB_REPO/UPDATES_REQUIRE_ADMIN_FOR_STATUS. A new "Updates & privacy" section indoc/docker.mddocuments the set; backend tests parse the shipped configs and fail if the${ENV}placeholders are dropped. Config, docs, and tests only — no runtime code change.
Notable fixes
- Pad — keep the token-less Delete button reachable without pad-wide settings (#7959 / #7960). The token-less
#delete-padbutton was nested inside theenablePadWideSettings-gated section, so disabling pad-wide settings removed the only no-token deletion path — and combined with #7926 hiding the token disclosure when no token is needed, a user allowed to delete could be left with no deletion UI at all. The button is now always rendered (hidden by default) and driven by acanDeletePadclient var (creator orallowPadDeletionByAllUsers, excluding read-only sessions), so the plain button and the recovery-token disclosure are mutually coherent and neither depends on pad-wide settings. - History mode — restore the saved-revision markers (#7946 / #7948). When #7659 moved the timeslider into the pad as an embedded iframe, the user-facing control became the outer
#history-slider-input, but the saved-revision stars were still drawn into the now-hidden iframe#ui-slider-bar, so "Save Revision" appeared to do nothing in in-pad history mode (a 3.3.x regression).pad_mode.tsnow bridges the embedded slider's saved revisions onto the outer slider as percentage-positioned, aria-hidden star markers (with click-to-seek for mouse users), and the server'sSAVE_REVISIONhandler broadcastsNEW_SAVEDREVto the pad room so a revision saved by a collaborator appears live on an already-open history slider. A single revision saved at rev 0 now renders too. Adds Playwright coverage for both the single-client and two-client live paths. - Import dialog — correct the outdated "no converter" help message (#7988 / #7989). The notice claimed only plain text and HTML could be imported and linked to the legacy AbiWord wiki, prompting LibreOffice installs for formats that already work natively. Etherpad imports
.txt,.html,.docx(via mammoth) and.etherpadwithout LibreOffice; only.pdf/.odt/.doc/.rtfstill need it. The message now says so and points at the documentation site. - PadManager — reject unreachable
.and..pad ids (#7962).isValidPadIdaccepted ids consisting only of URL dot-segments, but per the WHATWG URL standard a browser normalises/p/.to/p/and/p/..to/, so such a pad could be created in the database yet never opened or exported. These ids are now rejected, and the admindeletePadhandler falls back to a raw key purge whengetPad()throws so any legacy./..pad can still be removed.
Internal / contributor-facing
- CLI — fix the database migration/import scripts against the ueberdb2 promise API (#7982 / #7983).
migrateDB.tsopened source and target databases, copied all keys, then resolved without closing either — so under ueberdb2 6.1.x the keep-alive timer kept the process hanging after "Done syncing dbs", and buffered target writes were only guaranteed flushed onclose(). It now closes both databases (flushing writes, clearing the timer) on success and error paths and exits with an explicit status.importSqlFile.tsandmigrateDirtyDBtoRealDB.tswere ported off the pre-v6 callback API toawait db.init()/db.set(k, v)/db.close(), removing two@ts-ignores that hid broken calls and fixing an undefinedlengthin a progress log;tsc --noEmiton the bin package is now clean. - CI — stop the installer smoke test hanging the 6-hour job ceiling (#7981). The "Installer test" had hung on every ubuntu/macOS run since 3.2.0:
pnpm run prodis a nested launcher, sokill "$PID"; wait "$PID"only signalled the outer pnpm and blocked forever if the node server didn't exit on SIGTERM. Teardown now runs the launcher in its own process group, kills the whole group (SIGTERM then SIGKILL), drops the blockingwait, and adds an 8-minutetimeout-minutesbackstop to both smoke steps. - CI — run the Debian-package smoke test on PRs (#7969). The packaged-boot smoke test previously ran only on push to
develop— i.e. after merge — which is why the ueberdb2 startup-exit regression turneddevelopred instead of being blocked at PR time. Apull_requesttrigger (scoped to production-footprint paths) now runs the build+smoke job on PRs; the release/apt-publish jobs stay tag-guarded. - Release — park the non-functional
ep_etherpadnpm publish (#7922). ThereleaseEtherpadworkflow republished./srcasep_etherpad, a package with zero dependents that nothing in the repo or any deployment path consumes, and it had been failing with E404 (no OIDC trusted publisher configured). The job is now gated behind an explicitconfirm: truedispatch input so a stray run fails fast with a clear message, with the status documented in the workflow header andAGENTS.MD. - Tests — port the orphaned legacy timeslider specs to Playwright (#7949). The
src/tests/frontend/specs/mocha suite is run by no CI workflow, so its timeslider coverage was dead — which is how the #7946 history-mode regression reached a release. The still-meaningful cases (revision labels, export links, deep-link entry) were ported tofrontend-newPlaywright specs re-targeted at the real in-pad UI, and the three now-ported legacy specs were deleted.
Dependencies
ueberdb2pinned to6.1.13. 6.1.10 rewrote the cache/buffer layer to lazily arm an.unref()'d flush timer only when there are dirty keys, so on a fresh empty dirty DB nothing anchored Node's event loop and the packaged (.deb/systemd) boot could exit cleanly (code 0) beforeserver.listen()bound the port — failing the Debian-package health check. The dep was pinned back to the last green release (6.1.9, #7969) and then rolled forward to the now-fixed6.1.13(#7979), pinned exactly rather than with a caret.nodemailer8.x → 9.0.1 (#7965 / #7950 / #7976),mongodb7.1.1 → 7.3.0 (#7941),pg8.21.0 → 8.22.0 (#7985),undici→ 8.5.0 (#7980 etc.),oidc-provider9.8.4 → 9.8.5 (#7973),pdfkit0.19.0 → 0.19.1 (#7945),semver7.8.3 → 7.8.4 (#7943), and@radix-ui/react-switch1.3.0 → 1.3.1 (#7974).- Dev/build dependency group updates (#7964, #7970, #7978, #7987, #7944, #7951, #7952, and others), including
@types/node25 → 26,esbuild0.28.0 → 0.28.1,eslint10.4.1 → 10.5.0,@playwright/test1.60 → 1.61,vitest4.1.8 → 4.1.9, andactions/checkout6 → 7 (#7977).