Skip to content

fix(message): persist bulk-sent messages, sanitize SSRF errors, honor cross-instance cancel#340

Closed
rmyndharis wants to merge 1 commit into
mainfrom
fix/bulk-send-routing
Closed

fix(message): persist bulk-sent messages, sanitize SSRF errors, honor cross-instance cancel#340
rmyndharis wants to merge 1 commit into
mainfrom
fix/bulk-send-routing

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

Problem

Bulk batches called the engine's send methods directly, bypassing the single-send persistence path. Three consequences:

  1. No DB persistence. Bulk-sent messages were never written to the messages table, so they were invisible to chat history and statistics (the engine echo fires the webhook/WS but does not write the DB).
  2. Internal-address disclosure. On a blocked-destination (SSRF) failure for a media URL, String(error) — which names the refused internal host/IP — was stored verbatim in the batch result and returned by the batch-status endpoint.
  3. Cancellation was process-local. It was tracked only in an in-memory map, so a cancel issued by another replica or after a restart was ignored, and the loop kept sending.

Fix

  • Persistence. Bulk reuses the shared outgoing-message persistence (saveOutgoingMessage, now public) after each successful send — best-effort, so a persistence failure never flips a sent message to failed. Confirmed no double-write (the engine echo does not persist).
  • Sanitization. A pure sanitizeBatchError maps a blocked-destination error to a generic SEND_BLOCKED code; the internal address is no longer stored, returned, or logged verbatim.
  • Cross-instance cancel. processBatch re-reads batch.status from the database at its save cadence — and once more before the final write — so a cancel from another instance/restart stops the batch and is never clobbered back to a completed status. The status is read before each save so a concurrent CANCELLED is never overwritten.

Scope notes

Implemented additively (bulk-only) rather than re-routing through message.service's full send pipeline, to avoid changing bulk's plugin-hook/typing-humaniser behaviour. Multi-replica cancel is honoured at the save cadence (the existing checkpoint granularity), matching the batch's periodic-save design.

Compatibility

No API or response-shape change; non-breaking. SemVer: patch.

Tests

  • sanitizeBatchError: SSRF → generic message (no internal address); ordinary error passes through under SEND_FAILED.
  • processBatch: persists each sent message; stops when the batch is cancelled in the DB by another instance; does not clobber a CANCELLED that lands after the last cadence read.
  • Full backend suite 733/733; dashboard build + lint clean.

… cross-instance cancel

Bulk batches called the engine send methods directly, so each message bypassed the single-send persistence path: the rows never reached the messages table (invisible to chat history and statistics), a blocked-destination (SSRF) error was stored verbatim in the batch result — exposing the internal address it refused, readable via GET batch status — and a cancellation was only honored by the process that created the batch.

Bulk sends now reuse the shared outgoing-message persistence, a blocked-destination error is reported as a generic code (and no longer logged verbatim), and processBatch re-reads the batch status from the database at its save cadence and before the final write, so a cancel issued by another instance (or after a restart) stops the batch and is not clobbered back to a completed status.
rmyndharis added a commit that referenced this pull request Jun 19, 2026
* fix(webhook): deliver session lifecycle events and key webhook hardening (#335)

* fix(security): pin outbound webhook and media fetches to validated IP (#338)

* fix(plugins): persist plugin enable/config and restore (#339)

* fix(message): persist bulk-sent messages, sanitize SSRF (#340)

* fix(engine): return the real id for forwarded messages (#341)

* fix(security): harden outbound requests, IPv6 SSRF (#344)

* fix(security): secret-file perms, key pepper, allowedIps (#345)

* fix(storage): bound tar imports, contain storage keys (#346)

* fix(session): reconcile late acks, serialize reactions (#348)

* fix(contract): webhook timeout, bounded shutdown, 501 (#350)

* feat(session): force-kill a stuck session (#352)

* merge #343

* merge #351

* chore(release): v0.4.3 — CHANGELOG, version bump (package.json/dashboard/swagger), README + docs
@rmyndharis

Copy link
Copy Markdown
Owner Author

Shipped in v0.4.3 (integrated via the release PR #354 and tagged v0.4.3). Thanks! 🎉

@rmyndharis rmyndharis closed this Jun 19, 2026
@rmyndharis rmyndharis deleted the fix/bulk-send-routing branch June 19, 2026 18:33
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.

1 participant