Skip to content

Settings: hide the deletion token modal without opening deletion to all users - #8319

Merged
JohnMcLear merged 5 commits into
ether:developfrom
sirsqm:issue-7996-suppress-deletion-token
Oct 15, 2026
Merged

JohnMcLear merged 5 commits into
ether:developfrom
sirsqm:issue-7996-suppress-deletion-token

Conversation

@sirsqm

@sirsqm sirsqm commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

What and why

Fixes #7996.

Until now the only way to stop the one-time "Save your pad deletion token" modal from interrupting every new creator was allowPadDeletionByAllUsers: true. That setting does exactly what it says — it also lets any editor delete the pad, even if they are not the creator — so operators had to choose between an annoying modal and a data-loss risk.

This adds suppressPadDeletionTokenModal (default false). When enabled it hides the modal without widening deletion rights:

  • the deletion token is still issued, and still deletes the pad from another device (the value stays in clientVars.padDeletionToken, so a client that wants to surface it on demand, e.g. from a settings panel, still can);
  • canDeleteWithoutToken stays false and non-creators still cannot delete.

It only stops the modal from being shown on arrival.

The setting is also exposed to the container image as SUPPRESS_PAD_DELETION_TOKEN_MODAL, and documented in settings.json.template / settings.json.docker.

Files

  • src/node/utils/Settings.ts — new setting + default
  • src/node/handler/PadMessageHandler.ts — send suppressPadDeletionTokenModal in clientVars
  • src/static/js/pad.ts — skip showDeletionTokenModalIfPresent() when suppressed
  • src/static/js/types/SocketIOMessage.ts — type the new client var
  • settings.json.template, settings.json.docker — docs + env var
  • src/tests/backend/specs/socketio.ts — regression test

Testing

New regression test in the socketio spec asserts that with the setting on the creator still receives a token, suppressPadDeletionTokenModal is true, and canDeleteWithoutToken remains false (i.e. deletion rights are unchanged). I verified the test fails without the server-side change (45 passing / 1 failing) and passes with it (46 passing):

$ pnpm --filter ep_etherpad-lite exec mocha --import=tsx --extension ts tests/backend/specs/socketio.ts
46 passing

pnpm run ts-check passes.

Note: pnpm run lint currently fails on develop for an unrelated reason — ESLint 10 no longer loads the repo's .eslintrc.cjs (flat-config migration), so eslint . cannot find a config at all. Not touched here.

…ll users

Until now the only way to stop the one-time "Save your pad deletion token"
modal from interrupting every new creator was allowPadDeletionByAllUsers,
which also lets any editor delete the pad -- a data-loss risk operators did
not want.

Add a suppressPadDeletionTokenModal setting (with a
SUPPRESS_PAD_DELETION_TOKEN_MODAL container env var) that hides the modal
while leaving deletion rights untouched: the token is still issued and
still usable to delete the pad from another device, and non-creators still
cannot delete. It only stops the modal from being shown on arrival.

The server still sends padDeletionToken in clientVars, so a client that
wants to surface it on demand (e.g. from a settings panel) can; only the
interrupting modal is suppressed.

Add a regression test asserting the token is still issued and
canDeleteWithoutToken stays false when the modal is suppressed.
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Suppress the pad deletion token modal without changing deletion rights

🐞 Bug fix ✨ Enhancement ⚙️ Configuration changes 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Add an opt-in setting to hide the creator’s deletion-token modal without allowing other editors to
 delete.
• Keep issuing the recovery token and preserve existing deletion permissions.
• Expose the setting in container configuration and test the server’s client-vars response.
Diagram

graph TD
  Config["Operator setting"] --> Server["Pad handshake"] --> Vars["Client vars"] --> Check{"Suppress modal?"}
  Check -->|Yes| Hidden["Keep token available"]
  Check -->|No| Modal["Show token modal"]
Loading
High-Level Assessment

Keep the separate presentation setting: enabling deletion for everyone would widen permissions, while withholding the token would remove recovery access from other devices. Before merging, restore the unrelated character-encoding changes throughout CHANGELOG.md; the intended change there is the new release-note entry.

Files changed (8) +176 / -116

Enhancement (1) +1 / -0
SocketIOMessage.tsType the new client-vars flag +1/-0

Type the new client-vars flag

• Adds an optional boolean suppressPadDeletionTokenModal field to ClientVarPayload.

src/static/js/types/SocketIOMessage.ts

Bug fix (2) +11 / -0
PadMessageHandler.tsSend the suppression flag in pad client vars +6/-0

Send the suppression flag in pad client vars

• Includes the setting in the pad handshake without changing deletion-token issuance or permission calculations.

src/node/handler/PadMessageHandler.ts

pad.tsSkip the token modal when suppression is enabled +5/-0

Skip the token modal when suppression is enabled

• Returns before opening the creator’s modal when the flag is set, leaving the issued token in client vars for other UI to use.

src/static/js/pad.ts

Tests (1) +22 / -0
socketio.tsCover token and permission behavior with suppression enabled +22/-0

Cover token and permission behavior with suppression enabled

• Resets the setting between tests and asserts that an enabled suppression flag reaches the creator while the token is still issued and canDeleteWithoutToken remains false. The test does not directly exercise browser modal rendering.

src/tests/backend/specs/socketio.ts

Documentation (1) +118 / -116
CHANGELOG.mdDocument optional deletion-token modal suppression +118/-116

Document optional deletion-token modal suppression

• Adds a 3.3.7 note explaining the setting and unchanged deletion rights. The diff also replaces characters with mojibake across many unrelated historical entries; those changes should be reverted.

CHANGELOG.md

Other (3) +24 / -0
settings.json.dockerExpose the modal setting through a container environment variable +10/-0

Expose the modal setting through a container environment variable

• Documents the behavior and maps SUPPRESS_PAD_DELETION_TOKEN_MODAL to the new setting, defaulting to false.

settings.json.docker

settings.json.templateDocument the default modal setting +9/-0

Document the default modal setting

• Adds suppressPadDeletionTokenModal with a false default and explains that it does not grant other users deletion rights.

settings.json.template

Settings.tsDefine the opt-in modal-suppression setting +5/-0

Define the opt-in modal-suppression setting

• Adds the boolean setting to SettingsType and initializes it to false.

src/node/utils/Settings.ts

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Creators cannot retrieve hidden tokens ✓ Resolved
Description
showDeletionTokenModalIfPresent() returns before displaying the issued token, and the pad settings
panel has no control to reveal or copy it. When suppressPadDeletionTokenModal is enabled, the
creator receives the plaintext token only on first arrival; later visits receive null, leaving a
creator who did not capture it unable to use token-based deletion from another device.
Code

src/static/js/pad.ts[269]

+  if ((window as any).clientVars?.suppressPadDeletionTokenModal) return;
Evidence
Rule 1 requires a retained deletion token to be accessible through settings. The new early return
hides the only token display; pad settings provides a field for entering a previously saved token,
not a way to retrieve one. The token manager returns plaintext only on creation and stores its hash,
so revisiting the pad does not restore access.

Suppress the default pad deletion token UI without enabling deletion by all users
src/static/js/pad.ts[262-296]
src/templates/pad.html[397-440]
src/node/db/PadDeletionManager.ts[32-41]
src/node/handler/PadMessageHandler.ts[1329-1336]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Suppressing the modal leaves creators without a settings-based way to obtain the deletion token. The server issues its plaintext value only once and stores only a hash.
## Fix Focus Areas
- src/static/js/pad.ts[262-296]
- src/templates/pad.html[397-440]
- src/node/db/PadDeletionManager.ts[32-41]
## Recommended Fix
Add an on-demand reveal or copy action in pad settings, with a secure creator-authorized way to obtain a usable token after the initial arrival. Verify that the suppressed modal stays hidden and the settings action works on a later visit.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Readers see corrupted release notes ✓ Resolved
Description
The CHANGELOG.md edit replaces existing punctuation and symbols with mojibake such as ŌĆö and
ŌĆ”. The corruption extends across historical release entries, including security notes, making
those records harder to read and search.
Code

CHANGELOG.md[7]

+- **Renderer ŌĆö attribute-pool values can no longer forge class tokens (GHSA-4mx2-rqx5-2pp6, #8302).** `linestylefilter` appended the `start` and `list` line-attribute values verbatim to the space-delimited class string that `domline` parses back, and `domline` emits any `tag:` token in that string as a raw element. A `start` value containing a space, which can be planted through a crafted changeset or `.etherpad` import, therefore smuggled in a token such as `tag:img/src=x/onerror=...` and rendered a live element with a live event handler for every viewer of the pad and its timeslider. This is the same function as GHSA-f7h5-v9hm-548j but a different token. `start` is now emitted only when it is an integer and `list` only when it is a plain list type, and `domline` accepts only a bare element name (letters, digits and hyphens) for `tag:` tokens, which also covers class strings contributed by plugins. Reported by @arpitjain099.
Evidence
The changed historical entries display ŌĆö in place of dashes near the top of the file, and a
previously existing celebratory emoji is also corrupted later in the changelog.

CHANGELOG.md[7-12]
CHANGELOG.md[493-496]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR corrupts existing changelog characters across historical release notes, replacing punctuation and symbols with mojibake.
## Fix Focus Areas
- CHANGELOG.md[7-12]
- CHANGELOG.md[493-496]
## Recommended Fix
Restore the pre-existing changelog text with its original UTF-8 characters, retaining only the new pad-deletion entry.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can hide the parts of a finding you never read, like the evidence or the agent prompt

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/static/js/pad.ts Outdated
Comment thread CHANGELOG.md Outdated
Addresses review feedback on ether#8319.

1. Creators could not retrieve the token. The modal is the only place the
   plaintext token is ever shown, and the server stores only a hash, so a
   creator who did not save it on arrival could never delete the pad from
   another device. The suppressed branch now hands the token to a new
   disclosure in the pad settings panel (#delete-pad-token-reveal), which
   shows the value with a copy button. It is unhidden only for the session
   that actually received a token under suppression, so nothing is revealed
   to later visits (which get null anyway) or to non-creators. The modal
   itself stays hidden.

2. The CHANGELOG edit had rewritten existing UTF-8 punctuation to mojibake
   across historical entries. Restored the file from develop and re-added
   only the new ether#7996 bullet, this time keeping the original bytes intact.

Add a source-lint regression test covering the settings control and the
client wiring, plus locale strings for the new labels.
@sirsqm

sirsqm commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Addressed both review findings in 3a8a97a.

  1. Creators can now retrieve the token when the modal is suppressed. The modal was indeed the only place the plaintext token was ever surfaced, and the server stores only a hash, so a creator who missed it on arrival had no way back. The suppressed branch now hands the token to a new disclosure in the pad settings panel (#delete-pad-token-reveal), which shows the value with a copy button. It is unhidden only for the session that actually received a token under suppression — later visits get null from the server anyway, and non-creators never get the token — so nothing is revealed beyond that session. The interrupting modal stays hidden, which is the point of the setting.

    I looked at adding a creator-authorized server re-issue instead, but the token manager is deliberately write-once and stores only a hash, and any "re-issue" endpoint would hand the token to whoever holds the creator cookie — a real privilege-escalation surface on a shared device. Reusing the value the server already sent this session avoids widening anything.

  2. CHANGELOG encoding restored. My edit had rewritten pre-existing UTF-8 punctuation to mojibake across historical entries. I restored the file from develop and re-added only the new Disable pad deletion token UI WITHOUT allowPadDeletionByAllUsers #7996 bullet, keeping the original bytes intact.

Also added a source-lint regression test (pad-deletion-token-reveal.test.ts) covering the settings control and the client wiring, plus the two new locale strings in en.json. tsc --noEmit, the locale/template-key suites, and the socketio suite (46 passing) are green.

This comment was posted by an AI agent (OpenHands) on behalf of the contributor.

@sirsqm

sirsqm commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Status: ready for review on the current head (3a8a97a).

  • Both review items are addressed — the hidden-token retrieval path (token is now revealed in the pad settings panel for the session that received it) and the CHANGELOG encoding.
  • Re-verified locally: tsc --noEmit is clean, and the new pad-deletion-token-reveal spec passes (2/2) alongside the socket.io spec additions.
  • Mergeable, no conflicts with develop.

@JohnMcLear whenever you have a moment — this one plus #8314 and #8320 are all ready for a look. Happy to rebase or adjust anything.

@JohnMcLear

Copy link
Copy Markdown
Member

Yea I like this, reviewing now

@JohnMcLear

Copy link
Copy Markdown
Member

Few bits I don't like, UX is awful, UI is lazy. If you are going to get an AI Agent to work on the code, please click through/test it first!

Follow-up review changes for ether#7996:

- Replace the "Show the pad deletion token" disclosure with a button
  that shows the token in a read-only input, selects it and copies it
  to the clipboard. Move it above the delete controls instead of under
  them.
- Replace the source-text vitest with Playwright tests that drive the
  real flow: no modal, one click shows and copies the token, and a
  second browser deletes the pad with it. Also check that the button
  is not offered when the modal is not suppressed.
- Back up suppressPadDeletionTokenModal in the socketio spec so the
  new test doesn't leak it into later specs.
- Drop the CHANGELOG entry: on current develop it landed in the
  already released 3.3.7 section. Release notes are written at
  release time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VigRrw8y5o5FaKH2JSzoAv
@JohnMcLear

Copy link
Copy Markdown
Member

Thanks @sqmyou, this is a good fix. I pushed a few review changes on top (083a6a3) and merged develop in:

  • The suppressed token is now offered as one button above the delete controls. Clicking it shows the token in a read-only input, selects it, and copies it to the clipboard.
  • The vitest that grepped pad.html / pad.ts is replaced by Playwright tests in pad_deletion_token.spec.ts. They check that no modal appears, that one click shows and copies the token, and that a second browser can delete the pad with that token. They also check that the button isn't offered when the modal isn't suppressed.
  • suppressPadDeletionTokenModal is added to the settings backed up in socketio.ts, so the new test doesn't leak it into later specs.
  • The CHANGELOG entry is dropped. On current develop it landed in the already-released 3.3.7 section, and release notes get written at release time.

Tests: the deletion-token Playwright specs pass in Chromium and Firefox (12/12), backend 1843 passing, vitest 840 passing, ts-check clean.

🤖 Generated with Claude Code

The show-and-copy button sat on its own between the settings and the
delete buttons, and disappeared after a reload because the server only
sends the token once.

It is now a "Show and copy deletion token" toggle at the end of the
User Settings column, like the other toggles. Turning it on shows the
token, selects it and copies it to the clipboard. Under
suppressPadDeletionTokenModal the browser keeps the token in
localStorage per pad, so the toggle is still there after a reload.
Anything that can read that storage could already delete the pad as
the creator. It is only offered where this browser can delete the
pad, and the saved token is cleared when the pad is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VigRrw8y5o5FaKH2JSzoAv
@JohnMcLear

Copy link
Copy Markdown
Member

One more change after a click-through (1bf72db): the suppressed token is now a "Show and copy deletion token" toggle at the end of the User Settings column, like the other toggles, instead of a button near the delete controls.

It also survives a reload now. The server only sends the token once, so under suppressPadDeletionTokenModal the browser keeps it in localStorage per pad. It's only offered where this browser can delete the pad, and it's cleared when the pad is deleted. The Playwright test covers the reload and the clean-up.

🤖 Generated with Claude Code

@JohnMcLear
JohnMcLear merged commit a66a51f into ether:develop Oct 15, 2026
26 of 27 checks passed
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.

Disable pad deletion token UI WITHOUT allowPadDeletionByAllUsers

2 participants