You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Bulk batch ids are unique per session, not globally. A batch id claimed by one session no longer prevents another session from using the same id — the uniqueness constraint is now scoped to (session, batchId), matching the per-session lookup, so an explicit cross-session reuse no longer fails with a 500. Reusing an id within the same session is still rejected with a clear 400. Existing databases are migrated in place. (#531)
A message arriving while a session is being deleted is no longer persisted as an orphan. The inbound-message handler re-checks that the session is still live after its asynchronous processing, so a message that races a session deletion can't leave behind a messages row (which has no cascade) for a session that no longer exists. (#531)
Per-session stats return a consistent lastActive timestamp on SQLite and PostgreSQL.GET /stats/sessions/:id previously emitted a different topChats[].lastActive format depending on the database (an ISO date-time on PostgreSQL versus the stored text on SQLite); it is now formatted to a stable YYYY-MM-DD HH:MM:SS on both. (#533)
The uuid id default now works on PostgreSQL 12 and older. Id generation relies on a gen_random_uuid() column default, which is a core built-in only from PostgreSQL 13; on older servers it lives in the pgcrypto extension. The migration now enables pgcrypto first, so a fresh deploy against PostgreSQL ≤ 12 no longer fails on startup or first insert. (#533)
The audit-log listing no longer loads the whole table for a large limit.GET /audit clamps its page size to a maximum of 200, so an oversized limit can't pull the entire audit_logs table into a single response. (#536)
Migration reverts are idempotent on a synchronize-bootstrapped database. The baileys_stored_messages and webhook_delivery_failures migrations now drop their indexes with IF EXISTS, so a down() no longer errors when the named indexes were never created. (#536)
Bulk send always releases its in-flight marker. A batch whose session engine was missing, or that threw mid-processing, previously left a stale entry in an in-memory tracking map; the marker is now released on every exit path. (#536)
Security
Hook re-entrancy is now blocked for sandboxed plugins too. A plugin running in the worker-thread sandbox could re-fire the hook it was handling by issuing a capability call (for example, sending a message from within a message:sending handler), because the re-entrancy guard did not span the worker boundary — looping the event back into the plugin without bound. The host now runs each worker-initiated capability call inside the in-flight hook context, so such a re-fire is short-circuited exactly as it already was for in-process plugins. (#532)
Docker container teardown is constrained to OpenWA-managed services. The POST /infra/restart endpoint passed its profilesToRemove list straight to container removal, which resolved containers by a name substring — so an unrecognized or empty profile could stop and remove an unrelated container. Teardown is now restricted to the managed allowlist (postgres, redis, minio) and container resolution requires an exact openwa-<service> name match. (#534)
Failed API-key authentication attempts are now recorded in the audit log. Rejected or denied keys (invalid, disabled/expired, IP- or session-scope-denied, or insufficient role) previously left no audit entry; the gateway now logs an api_key_auth_failed event with the client IP, method, path, and reason, giving administrators a forensic trail for credential probing. Audit logging stays best-effort and never affects the request outcome. (#535)
The SSRF guard blocks the deprecated IPv6 site-local range (fec0::/10). Webhook and server-side media URLs are now rejected when they resolve into fec0::/10, closing a gap alongside the already-blocked unique-local and link-local ranges. (#536)
Session-scoped MCP tools require a session id before authorization. A session-scoped tool invoked without a session id is now rejected, so a session-restricted API key can't be used to drive such a tool against a session outside its scope. (#536)
Contact-card vCards are sanitized on both engines. Sending a contact whose name or number contained CR/LF could inject extra vCard fields on the whatsapp-web.js engine; both adapters now build the vCard through one shared sanitizing helper (CR/LF stripped, digits-only waid). (#537)