CVE-2026-55086 is a medium-severity (CVSS 4.2) vulnerability in ep_etherpad-lite. O3 Security confirms whether CVE-2026-55086 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
ep_etherpad-lite: Import/export uses Math.random() for temp file paths; predictable paths on shared /tmp enable symlink-based file overwrite
Real-World Exposure
How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.
ep_etherpad-litenpmDescription
Description
src/node/handler/ImportHandler.ts and src/node/handler/ExportHandler.ts both compute their temporary working-file paths as:
const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
const srcFile = `${os.tmpdir()}/etherpad_export_${randNum}.html`;
const destFile = `${os.tmpdir()}/etherpad_export_${randNum}.${type}`;
Two flaws compound:
-
Math.random()is not crypto-secure. It yields at most ~32 bits of entropy and is predictable across calls within the same Node process (V8 shares PRNG state between consecutiveMath.random()invocations). An attacker on the same host who observes any earlier temp-file name from logs or other side channels can predict subsequent names. -
The paths land in
os.tmpdir(). On a typical Linux system this is/tmp— a shared world-writable directory. An unprivileged local attacker can pre-create a symbolic link at a predicted path pointing at any file the Etherpad process can write:ln -s /etc/etherpad/SESSIONKEY.txt /tmp/etherpad_export_<predicted>.htmlWhen
ExportHandlercallsfs.writeFile(srcFile, html)(orImportHandlercallsfs.rename(srcFile, destFile)/sofficewrites its converted output to the path), the open syscall follows the symlink and either reads from or overwrites the linked target. For deployments where the Etherpad process runs as a privileged user (notably some Docker base images that run as root, snap confinement edge cases, or hand-rolled systemd units), this becomes arbitrary file overwrite.
The Import path is more impactful in practice: the file content the attacker can land in the symlink target is partially attacker-controlled (the post-soffice/post-mammoth conversion output of the uploaded document).
Severity rationale
- AV:L — requires local access to the host that runs Etherpad. Multi-tenant hosts (shared dev boxes, k8s shared-node setups, single-server CI workers) are the realistic threat surface.
- AC:H — attacker needs to predict the temp filename, which requires observing prior names or shared PRNG state.
- PR:L — any unprivileged local account.
- UI:N — no user interaction.
- S:C — scope change from local user to whatever the Etherpad process can write.
- C:L / I:L / A:N — bounded by what the Etherpad process can already touch; confidentiality is via secondary read-paths (the LibreOffice conversion error log can echo bytes from the symlinked file).
Single-tenant deployments where only the Etherpad operator has shell access on the host are not exposed.
Affected versions
- All
ep_etherpad-liteversions throughv3.0.0(inclusive). TheMath.floor(Math.random() * 0xFFFFFFFF)pattern is present insrc/node/handler/ImportHandler.tsandsrc/node/handler/ExportHandler.tsfor as far back as the repository history extends — older than the 2024-03-16 snapshot at commit107598bwhere it first appears in the current file paths, and predating the v1.x era.
Patched versions
ep_etherpad-lite >= 3.1.0— the fix is ondevelopHEAD as commit8c6104c. Update this field with the actual tagged release version when it ships.
Proof of concept (sketch)
# Pre-condition: attacker has shell on the same host as Etherpad,
# has read access to /tmp, and Etherpad's process can write to the
# chosen target (e.g. runs as root inside a Docker container).
# 1. Observe a temp filename from logs (or guess via prior exports).
TARGET=/etc/etherpad/SESSIONKEY.txt
GUESS=/tmp/etherpad_export_$(./predict-next-random)$EXT # implementation-specific
# 2. Place the symlink before Etherpad creates the file.
ln -s "$TARGET" "$GUESS"
# 3. Trigger an export from the Etherpad UI (or via the API).
# Etherpad calls fs.writeFile(srcFile, ...) which open()s the symlink
# target with O_WRONLY|O_TRUNC and clobbers $TARGET with HTML content.
In practice the exact reproduction depends on the host's PRNG state predictability and the deployment's process-owner privileges. A reliable exploit chain needs setpriv-style host access or a sibling tenant.
Workarounds
- Run Etherpad in a container with a private
/tmp(Docker:--tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m, systemd:PrivateTmp=true). - Ensure the Etherpad process does NOT run as root and has write access only to its own data directory.
- Set
TMPDIRto an Etherpad-private directory in the environment.
Fix
Patched in 8c6104c (PR #7784):
-const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
+const randNum = crypto.randomBytes(16).toString('hex');
128 bits of CSPRNG entropy. Predicting the next filename is no longer feasible.
A bigger follow-up — per-request mkdtemp subdirectories with O_EXCL/O_NOFOLLOW semantics — is deferred to a later release; the immediate window (predictable 32-bit collisions across processes) is what the patch closes.
Resources
- Patched in: https://github.com/ether/etherpad/pull/7784 (squash commit
8c6104c). Math.random()is not appropriate for security-sensitive contexts. See the MDN guidance forMath.random()and the Node.js recommendation to usecrypto.randomBytesfor unpredictable values.
Credits
Reported during an internal security audit by Claude (via @JohnMcLear).
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | ep_etherpad-lite | all versions | 3.1.0 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for ep_etherpad-lite. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update ep_etherpad-lite to 3.1.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-55086 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether CVE-2026-55086 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to CVE-2026-55086. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-55086 in your dependencies?
O3 detects CVE-2026-55086 across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.