v0.6.2
Plugin platform follow-ups (sandbox hardening, install-from-URL + catalog), a mark-chat-unread
endpoint, and a batch of correctness/housekeeping fixes.
Added
- Install plugins from a URL / catalog.
POST /plugins/install-urldownloads a plugin.zipfrom an HTTP(S) URL through the SSRF guard (host validated, connection pinned, redirects refused, size-capped) and runs the exact same validate-write-load pipeline as an uploaded package.GET /plugins/catalogfetches a configured remote catalog (PLUGIN_CATALOG_URL, default the OpenWA-pluginsplugins.json) and annotates each entry withinstalled/installedVersion/updateAvailable. The dashboard install modal gains a Catalog tab to browse and one-click install. Add a non-public catalog/release host toSSRF_ALLOWED_HOSTS. (#433) - Update a plugin in place.
POST /plugins/:id/updatedownloads the new package (same SSRF-guarded path) and swaps it in while preserving operator config and the enabled state — it unloads the running plugin (keeping its registry entry, so config survives), writes the new files, reloads, and re-enables if it was enabled. The package id must match; the old version is backed up and restored if the update fails. The dashboard Catalog tab shows an Update button when a newer version is available. (#433) - Mark a chat as unread:
POST /sessions/:id/chats/unread(andsessionApi.markChatUnreadon the dashboard client), the inverse of mark-as-read, supported on both the whatsapp-web.js and Baileys engines. (#432)
Security
- Untrusted (uploaded) plugins now run with a minimal, allowlisted worker environment instead of inheriting the host process environment, so a plugin can no longer read host secrets (database/Redis credentials, the API master key and pepper,
DOCKER_HOST) out ofprocess.env. (#431)
Fixed
- Webhook delivery no longer POSTs an empty (
undefined) body when awebhook:beforeplugin hook returns a result without apayloadkey — it now falls back to the original payload. (#434) - The
session.qrWebSocket event is now actually emitted from the QR callback, so the dashboard can render the QR live instead of only pollingGET /qr. (#434) - Storage usage now reports real S3 object sizes instead of a 100KB-per-file estimate, and local file writes no longer block the event loop during an import. (#434)
- A sandboxed plugin whose
load/onEnable/onDisablehangs no longer blocks the enable/disable request (and the request behind it) indefinitely — plugin lifecycle calls are now time-bounded, and a disable always tears the worker down even ifonDisablefails, so a misbehaving plugin can't leak its worker thread. (#431) - Sandboxed plugins now receive
onConfigChange(config updates reach the worker instead of being silently ignored until disable + re-enable) and have their realhealthCheckrun —GET /plugins/:id/healthpreviously always returned the default "healthy" for sandboxed plugins. (#430) - Plugin
onDisablenow runs on graceful shutdown (OnModuleDestroy), so stateful plugins can flush buffers / close connections / persist state instead of losing in-flight work on every restart or deploy. (#430) - A concurrent enable of the same plugin no longer double-runs
onEnableor double-registers its hooks (a synchronous in-progress lock rejects the racing call). (#430) - Plugin storage writes —
ctx.storage.set()and the plugin registry — are now atomic (write to a temp file then rename), so a crash mid-write can't leave a truncated file that silently degrades to lost state. (#430)
Changed
- The plugin-management UI strings (install/uninstall, the status rail, and the install modal) are now translated into every locale instead of falling back to English. (#429)