fix(engine): bound SSRF DNS resolution by a deadline; detect media URLs case-insensitively - #404
Merged
Merged
Conversation
…Ls case-insensitively - ssrf-guard: the hostname lookup in resolveSafeFetchTarget was unbounded, so a hanging/slow resolver could pin a worker. It now races a deadline (default 10s, SSRF_DNS_TIMEOUT_MS override) and fails closed with SsrfBlockedError on expiry; the in-flight lookup's late result is swallowed (no unhandledRejection). Applies to every caller (webhook delivery, media fetch, and registration). - wwebjs adapter: extract isHttpUrl (case-insensitive /^https?:\/\//i) and use it for remote-media detection at both send sites, matching the Baileys engine — a mixed-case scheme (e.g. HTTPS://) now routes through the SSRF-guarded fetch instead of being mistaken for base64.
mmr94
added a commit
to mmr94/unisoft-OpenWA
that referenced
this pull request
Jun 24, 2026
Le fork avait 315 commits / 6 versions de retard. Ce merge corrige la cause racine d'un bug d'envoi de média WhatsApp : MessageMedia.fromUrl() sans timeout (fetch interne + DNS non borné) bloquait Chromium jusqu'au protocolTimeout, vu côté Kehila comme un NETWORK_ERROR en boucle. Upstream remplace ce chemin par loadRemoteMedia() (fetch borné + SSRF-gardé, rmyndharis#404) et ajoute POST /:id/force-kill pour tuer un moteur Chromium bloqué. Conflits résolus en préservant la feature maison d'hibernation de sessions ET les fixes de fiabilité upstream (rmyndharis#404 SSRF/DNS, rmyndharis#405 no-resurrect, rmyndharis#410 superseded engine, rmyndharis#415 reconcile-ready) : - session.service.ts (9 zones) : intentionalStops + stoppingSessions combinés dans onDisconnected ; hibernate/wake/ensureEngineReady/markActivity cohabitent avec forceKill ; cleanup hibernation greffé dans onModuleDestroy parallèle. - session.controller.ts : endpoints wake + force-kill ; transformSession délègue à SessionResponseDto.fromEntity (mapper complété avec lastSent). - bulk-message.service.ts : markActivity + persistSentMessage conservés. - events.gateway / spec / docs : fusion des deux côtés. Corrections post-merge : registre HOOK_EVENT_REGISTRY complété (events hibernation), await manquant sur getEngine dans getChatHistory, mocks ensureEngineReady/markActivity dans bulk-message.spec. Vérifié : nest build OK ; session.service.spec 105/105 ; bulk-message.spec 15/15. (Échecs sqlite3 résiduels = binding natif absent en local, sans rapport avec le code.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two small outbound-fetch hardening fixes.
Bound SSRF DNS resolution by a deadline
resolveSafeFetchTargetresolved a hostname with an unboundeddns.lookup, so a hanging or very slow resolver could pin a worker indefinitely (the per-request fetch timeout did not cover the DNS phase). The lookup now races a deadline — default 10s, overridable viaSSRF_DNS_TIMEOUT_MS— and fails closed withSsrfBlockedErroron expiry. The in-flight lookup is left to settle with its late result swallowed (nounhandledRejection). Because the deadline lives insideresolveSafeFetchTarget, it covers every caller (webhook delivery, server-side media fetch, and webhook registration) with no call-site changes. Healthy resolvers are unaffected (10s is generous for DNS).Case-insensitive remote-media detection (whatsapp-web.js)
The wwebjs adapter treated a media
datastring as a URL only when it had a lowercasehttp:///https://prefix, so a mixed-case scheme (e.g.HTTPS://…) was mistaken for base64 instead of being fetched through the SSRF-guardedloadRemoteMediapath — diverging from the Baileys engine. ExtractedisHttpUrl(case-insensitive/^https?:\/\//i) and used it at both send sites, matching Baileys.Tests
ssrf-guard.spec.ts: a never-resolving lookup is rejected within the deadline (SSRF_DNS_TIMEOUT_MS=30); all existing resolution/blocklist tests still pass.whatsapp-web-js.adapter.spec.ts:isHttpUrltreats mixed-case http(s) as URLs and base64/other as non-URLs.Risk
Low. The DNS deadline only triggers on a genuinely hung/slow resolver (fails closed, the safe direction). The media-URL change broadens a too-narrow check; a base64 payload cannot validly begin with the ASCII bytes
http/https, so no realistic base64 is misclassified.