Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 3.3.7

### Notable fixes

- **Admin — the plugin catalog no longer offers deprecated or known-broken plugins (#8246).** The "Available plugins" list was built straight from the plugin feed, so any package the feed knew about could be installed from the admin UI — including packages npm marks deprecated, packages the plugin registry itself could not get working against the current release, and `ep_adminpads2`, which is archived upstream and takes over `/admin/pads` with a template whose scripts core no longer ships, hanging the admin page on "Loading…". Those are now filtered out of the catalog, the admin UI refuses to install one if a stale page asks for it anyway (`pnpm run plugins i ep_<name>` on the server still overrides), and an already-installed plugin in that state is flagged as deprecated in the *Installed plugins* list. The npm deprecation lookup is cached for 12 hours and fails open: if the registry cannot be reached the full catalog is still listed. Reported by @JohnMcLear.

# 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.
Expand Down
18 changes: 17 additions & 1 deletion PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ scope — audit any plugin you install.

Etherpad ships with two outbound calls to `etherpad.org`. Both are
documented below. Both can be disabled with a single config value each.
No analytics, no usage pings, no third-party SDKs at runtime.
It also queries the public npm registry, but only while an admin is using
the plugin manager — never on a plain pad server. No analytics, no usage
pings, no third-party SDKs at runtime.

## Outbound calls

Expand All @@ -36,6 +38,20 @@ No analytics, no usage pings, no third-party SDKs at runtime.
| Disable | set `privacy.pluginCatalog: false` in `settings.json` (manual install via CLI still works) |
| Source | `src/static/js/pluginfw/installer.ts` |

### 3. Plugin deprecation and engine check

| | |
|---|---|
| URL | `https://registry.npmjs.org/<plugin>/<version>` |
| Frequency | once per listed plugin when an admin opens the plugin manager (cached 12 h), and once per plugin install |
| Payload | GET only; same `User-Agent`; only `ep_*` package names are sent |
| Purpose | hide plugins npm marks deprecated from the catalog, and refuse to install one that is deprecated or whose `engines.node` excludes the running Node |
| Disable | set `privacy.pluginCatalog: false` in `settings.json` — the catalog, and with it the deprecation sweep, is then not used at all. The install-time check only runs when you install a plugin from the admin UI; `pnpm run plugins i` does not make this call |
| Source | `src/static/js/pluginfw/installer.ts` |

Failures here are non-fatal by design: if npm is unreachable the catalog is
still listed in full and installs still proceed.

## What we removed

`swagger-ui-express` was dropped because the upstream npm package
Expand Down
6 changes: 6 additions & 0 deletions admin/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,12 @@ a.pm-btn-primary:link, a.pm-btn-primary:visited { color: #fff; }
color: var(--ep-accent-d);
border: 1px solid rgba(20,148,116,.25);
}
.pm-tag--deprecated {
background: rgba(196,92,24,.12);
color: #a24a12;
border: 1px solid rgba(196,92,24,.35);
cursor: help;
}

/* Toolbar (search + sort) */
.pm-toolbar {
Expand Down
15 changes: 12 additions & 3 deletions admin/src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ export const HomePage = () => {
}
const onFinishedInstall = (data: {plugin: string; code?: string | null; error?: string | null}) => {
if (data?.error) {
const key = data.code === 'PLUGIN_REQUIRES_NEWER_ETHERPAD'
? 'admin_plugins.install_error_requires_newer_etherpad'
: 'admin_plugins.install_error'
const errorKeys: Record<string, string> = {
PLUGIN_REQUIRES_NEWER_ETHERPAD: 'admin_plugins.install_error_requires_newer_etherpad',
PLUGIN_DEPRECATED: 'admin_plugins.install_error_deprecated',
}
const key = (data.code && errorKeys[data.code]) || 'admin_plugins.install_error'
useStore.getState().setToastState({
open: true,
title: t(key, {plugin: data.plugin, error: data.error}),
Expand Down Expand Up @@ -247,6 +249,13 @@ export const HomePage = () => {
<span className="pm-tag pm-tag--core"><Trans i18nKey="admin_plugins.tag_core"/></span>
)}
<span className="pm-tag pm-tag--ver">v{plugin.version}</span>
{plugin.deprecated && (
<span
className="pm-tag pm-tag--deprecated"
role="alert"
title={`${t('admin_plugins.deprecated_title')} ${plugin.deprecated}`}
><Trans i18nKey="admin_plugins.tag_deprecated"/></span>
)}
</div>
{plugin.description && (
<div className="pm-installed-desc">{plugin.description}</div>
Expand Down
8 changes: 7 additions & 1 deletion admin/src/pages/Plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ export type InstalledPlugin = {
realPath: string,
version: string,
description?: string,
updatable?: boolean
updatable?: boolean,
/**
* Reason this installed plugin should no longer be used (npm deprecation
* message, or core's reason for treating it as superseded). Set by the
* server; absent for healthy plugins.
*/
deprecated?: string
}


Expand Down
12 changes: 12 additions & 0 deletions doc/plugins.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ You can also browse to `http://yourEtherpadInstan.ce/admin/plugins`, which will
list all installed plugins and those available on npm. It even provides
functionality to search through all available plugins.

The catalog does not offer every package it knows about. A plugin is left out
when npm marks the published version as deprecated, when the plugin registry
could not get it working against the current Etherpad release, or when it is
on Etherpad's short list of superseded packages that break a working install
(`src/static/js/pluginfw/pluginCatalogFilter.ts`). An already-installed plugin
in that state is flagged as deprecated in the _Installed plugins_ list rather
than hidden. The admin UI also refuses to install such a plugin, so a stale
page cannot get one in through the back door. Nothing is hidden or refused on
a failed lookup — if the npm registry cannot be reached the full catalog is
listed and installs proceed — and `pnpm run plugins i ep_<name>` on the server
installs it anyway, for an operator who knows what they are doing.

=== Folder structure

Ideally a plugin has the following folder structure:
Expand Down
12 changes: 12 additions & 0 deletions doc/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ You can also browse to `http://yourEtherpadInstan.ce/admin/plugins`, which will
list all installed plugins and those available on npm. It even provides
functionality to search through all available plugins.

The catalog does not offer every package it knows about. A plugin is left out
when npm marks the published version as deprecated, when the plugin registry
could not get it working against the current Etherpad release, or when it is
on Etherpad's short list of superseded packages that break a working install
(`src/static/js/pluginfw/pluginCatalogFilter.ts`). An already-installed plugin
in that state is flagged as deprecated in the *Installed plugins* list rather
than hidden. The admin UI also refuses to install such a plugin, so a stale
page cannot get one in through the back door. Nothing is hidden or refused on
a failed lookup — if the npm registry cannot be reached the full catalog is
listed and installs proceed — and `pnpm run plugins i ep_<name>` on the server
installs it anyway, for an operator who knows what they are doing.

## Folder structure

Ideally a plugin has the following folder structure:
Expand Down
3 changes: 3 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"admin_plugins.disables.warning_title": "This plugin intentionally removes the listed Etherpad features.",
"admin_plugins.error_retrieving": "Error retrieving plugins",
"admin_plugins.install_error": "Failed to install {{plugin}}: {{error}}",
"admin_plugins.install_error_deprecated": "Cannot install {{plugin}}: it is no longer maintained and is not offered by the plugin catalog. Install it from the command line if you are sure you need it.",
"admin_plugins.install_error_requires_newer_etherpad": "Cannot install {{plugin}}: it requires a newer version of Etherpad. Please upgrade Etherpad and try again.",
"admin_plugins.installed": "Installed plugins",
"admin_plugins.installed_fetching": "Fetching installed plugins…",
Expand All @@ -90,7 +91,9 @@
"admin_plugins.sort.version": "Version",
"admin_plugins.source": "Plugin source",
"admin_plugins.subtitle": "Install, update, and remove Etherpad plugins. Changes require a server restart.",
"admin_plugins.deprecated_title": "This plugin is no longer maintained and may break Etherpad. Consider uninstalling it.",
"admin_plugins.tag_core": "Core",
"admin_plugins.tag_deprecated": "Deprecated",
"admin_plugins.update_tooltip": "Update",
"admin_plugins.updates_available": "Updates available",
"admin_plugins.update_now": "Update",
Expand Down
1 change: 1 addition & 0 deletions src/locales/qqq.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"admin_plugins.installed_uninstall.value": "{{Identical|Uninstall}}",
"admin_plugins.last-update": "{{Identical|Last update}}",
"admin_plugins.name": "{{Identical|Name}}",
"admin_plugins.tag_deprecated": "{{Identical|Deprecated}}",
"admin_plugins.version": "{{Identical|Version}}",
"admin_settings": "{{identical|Settings}}",
"index.newPad": "Used as button text.\nA pad, in the context of Etherpad, is a notepad, something to write on.",
Expand Down
29 changes: 25 additions & 4 deletions src/node/hooks/express/adminplugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import {ArgsExpressType} from "../../types/ArgsExpressType";
import {ErrorCaused} from "../../types/ErrorCaused";
import {QueryType} from "../../types/QueryType";

import {getAvailablePlugins, install, search, uninstall} from "../../../static/js/pluginfw/installer";
import {
getAvailablePlugins,
getInstalledPluginWarnings,
install,
search,
uninstall,
} from "../../../static/js/pluginfw/installer";
import {PackageData, PackageInfo} from "../../types/PackageInfo";
import semver from 'semver';
import log4js from 'log4js';
Expand Down Expand Up @@ -46,14 +52,29 @@ exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => {
})

socket.on('getInstalled', async (query: string) => {
// send currently installed plugins
const installed =
Object.keys(pluginDefs.plugins).map((plugin) => pluginDefs.plugins[plugin].package);
// Send currently installed plugins. Shallow copies, not the live
// package objects from the plugin registry: `updatable` and
// `deprecated` are per-response status, and writing them onto the
// registry left a stale badge showing after the condition had passed
// (e.g. npm un-deprecates, a lookup fails, or the catalog is turned
// off).
const installed: PackageInfo[] = Object.keys(pluginDefs.plugins)
.map((plugin) => ({...pluginDefs.plugins[plugin].package}));

if (settings.privacy.pluginCatalog) {
const updatable = await checkPluginForUpdates();
// An installed plugin that has since been deprecated or superseded is
// flagged in the UI: the catalog filter only stops new installs, it
// cannot help an admin who installed the plugin before (#8246).
let deprecated = new Map<string, string>();
try {
deprecated = await getInstalledPluginWarnings(installed);
} catch (err) {
logger.warn(`Could not check installed plugins for deprecation: ${err}`);
}
installed.forEach((plugin) => {
plugin.updatable = updatable.includes(plugin.name);
plugin.deprecated = deprecated.get(plugin.name);
})
}
// When the catalog is disabled, `updatable` simply stays unset on
Expand Down
15 changes: 14 additions & 1 deletion src/node/types/PackageInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,20 @@ export type PackageInfo = {
* plugin-registry build pipeline; absent for plugins that don't
* declare a disables list.
*/
disables?: string[]
disables?: string[],
/**
* Plugin-registry verdict for the current Etherpad release: `compatible`,
* `warning` or `failed`. Feed-provided; see pluginCatalogFilter.ts.
*/
compatibility?: string,
/** Set on *installed* plugins when a newer version is available. */
updatable?: boolean,
/**
* Set on *installed* plugins that npm marks deprecated or that core knows
* to be superseded. Carries the human-readable reason. Never set on
* catalog entries — those are filtered out instead (#8246).
*/
deprecated?: string
}


Expand Down
Loading
Loading