Skip to content

v0.8.14

Choose a tag to compare

@rmyndharis rmyndharis released this 10 Jul 05:43

Added

  • Plugin search providers (host→plugin search RPC). A sandboxed plugin can now register as a SearchProvider by calling ctx.registerSearchProvider(handler) (e.g. ctx.registerSearchProvider(async (query) => …)) from its worker. The host routes GET /api/search queries to the plugin over a new correlated search / search-result wire protocol (mirroring the existing hook/webhook/health-check bridges), so a plugin owns all of its vendor-specific query logic (Meilisearch, Elasticsearch, Typesense, …) while the core stays backend-agnostic — adding a new backend is a plugin, with no core changes. When SEARCH_PROVIDER=auto (the default), an enabled plugin provider supersedes the built-in database full-text provider; SEARCH_PROVIDER=builtin-fts keeps the built-in active; SEARCH_PROVIDER=none disables search. Searches are bounded by a 10s timeout and fail fast rather than hanging, and a provider is dropped from the registry when its plugin is disabled. This is Part 1 of the plugin-search work (the query RPC + selection policy); the indexing side uses the existing message:persisted hook.

  • Search queries are bounded host-side and plugin results are re-scoped. GET /api/search now clamps limit (to SEARCH_LIMIT_MAX) and offset (to SEARCH_OFFSET_MAX) before dispatching to any provider, so a plugin backend can no longer be asked for an unbounded page; a plugin provider's returned hits are re-filtered host-side against the caller's session scope, mirroring the SQL-enforced built-in provider. The provider's total is preserved when no out-of-scope hit was stripped, so pagination is unaffected. [#680]

  • Ingress events and delivery failures are now retained. ingress_events and integration_delivery_failures are pruned past INGRESS_RETENTION_DAYS (default 90; <=0 disables), matching the existing webhook/audit retention so the tables no longer grow without bound. [#680]

  • MCP auth failures are audited and Bull Board login is throttled. MCP authentication failures are now written to the audit log (parity with the REST ApiKeyGuard), and the Bull Board login endpoint is pre-auth IP-throttled (sharing MCP's MCP_IP_RATE_LIMIT_* settings) to bound credential-guessing floods. [#680]

  • Ingress guardrails. A startup warning now names any unauthenticated (scheme:'none') ingress route a plugin declares; the documented {id} HMAC contentTemplate placeholder is now implemented (previously it silently 401'd deliveries that used it). [#680]

  • CI now type-checks spec files, and the release gate matches CI. A tsc --noEmit -p tsconfig.json step full-program type-checks test files (previously invisible to CI, since tsconfig.build.json excludes specs); the release workflow's test gate now runs the full CI suite (lint + typecheck + jest + e2e + postgres-migration + dashboard) gating both the image and the GitHub Release. [#680]

  • Korean (한국어) dashboard locale. The dashboard language picker now includes Korean alongside the existing locales. Thanks @moduvoice. [#679]

Fixed

  • Sending media to a channel (@newsletter) on the whatsapp-web.js engine now fails fast with 501 Not Implemented instead of a raw 500. whatsapp-web.js builds a channel media message and calls msg.avParams(), a WhatsApp-Web-page method removed in a recent WA Web build, so the send crashed with TypeError: msg.avParams is not a function (upstream wwebjs#201823, unresolved in the current 1.34.7 release). The engine adapter now guards all media sends — image/video/audio/document (which share one send path) and sticker (a separate path that funnels into the same crash for channels) — for a channel recipient and raises a typed ChannelMediaNotSupportedError (501) before reaching the broken upstream code. Text→channel still works; only media is affected. The Baileys engine is unaffected. Workaround for affected media-to-channel sends until the upstream is fixed: switch the session to the Baileys engine, or pin WWEBJS_WEB_VERSION to a cached WA Web build that predates the avParams removal. [#673]

  • base64 media now takes precedence over url when both are provided on a media send. A send-image/send-video/etc. request carrying both fields previously sent the URL — and because @ValidateIf skipped @IsUrl validation on url whenever base64 was present, a stale url (e.g. an example default left in the body, or a client such as the n8n community node that sends both) was fetched unvalidated and could 404, silently shadowing the supplied base64 image. buildMediaInput now prefers base64, aligning the send path with the already-base64-first persisted message metadata. Both engine adapters (whatsapp-web.js and Baileys) already guard the remote fetch behind an isHttpUrl check, so the change covers both. [#670]

  • Fresh Docker Compose dev installs no longer boot-loop with SQLITE_CANTOPEN. .env.example ships DATABASE_NAME=openwa (a PostgreSQL db-name) which, in the SQLite dev compose, became the SQLite file path — SQLite tried to open a bare file on the read-only container rootfs and retried 9× per boot. The dev compose now forwards a blank DATABASE_NAME default (matching production), .env.example documents it as PostgreSQL-only, and env-validation rejects a bare SQLite name with a clear boot error. [#677] [#680]

  • DATABASE_TYPE=postgres combined with DATABASE_SYNCHRONIZE=true is now rejected at boot. On PostgreSQL the data connection runs migrations every boot, so synchronize immediately dropped the search migration's generated body_ts column and broke /search (501) on every restart; the misconfigured combination now fails fast. [#680]

  • Resolved internal IPs are no longer leaked in SSRF-block error messages. The webhook test response, delivery-failure/DLQ lastError, webhook:error hook payloads, and plugin-download errors now show a generic message instead of the resolved internal address (which was a server-side-recon oracle). The full detail is still logged server-side. [#680]

  • STORE_EPHEMERAL_MESSAGES=false is now honored on Baileys history backfill. Previously only the live inbound path enforced the opt-out, so disappearing-message history was silently persisted + indexed on every connect/reconnect despite the operator's opt-out. [#680]

  • Plugin archive extraction is bounded and fails clean. Per-entry and aggregate actual extracted bytes are now capped (a lying size=0 header can no longer inflate unboundedly), and a corrupt/oversized archive returns a 400 instead of an uncaught 500. [#680]

  • WebSocket auth lifecycle. IP-restricted API keys can now connect from an allowed IP (previously the gateway failed-closed and locked out every IP-restricted key), and a revoked/disabled key's active sockets are now evicted. [#680]

  • Misc hardening. The migration CLI honors MAIN_DATABASE_NAME; secret-file chmod failures are logged (a world-readable secret file no longer stays silent after a rewrite on a chmod-unsupported FS); the fully-expanded IPv4-mapped-loopback SSRF classifier gap is closed (latent, defense-in-depth); storage file-list/export traversal is now async + bounded (no longer blocks the event loop); the dashboard and all four SDKs warn on a non-localhost http:// baseUrl. [#680]