Skip to content

fix(engine): bound SSRF DNS resolution by a deadline; detect media URLs case-insensitively - #404

Merged
rmyndharis merged 2 commits into
mainfrom
fix/ssrf-dns-timeout-and-media-url-case
Jun 21, 2026
Merged

fix(engine): bound SSRF DNS resolution by a deadline; detect media URLs case-insensitively#404
rmyndharis merged 2 commits into
mainfrom
fix/ssrf-dns-timeout-and-media-url-case

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

Summary

Two small outbound-fetch hardening fixes.

Bound SSRF DNS resolution by a deadline

resolveSafeFetchTarget resolved a hostname with an unbounded dns.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 via SSRF_DNS_TIMEOUT_MS — and fails closed with SsrfBlockedError on expiry. The in-flight lookup is left to settle with its late result swallowed (no unhandledRejection). Because the deadline lives inside resolveSafeFetchTarget, 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 data string as a URL only when it had a lowercase http:///https:// prefix, so a mixed-case scheme (e.g. HTTPS://…) was mistaken for base64 instead of being fetched through the SSRF-guarded loadRemoteMedia path — diverging from the Baileys engine. Extracted isHttpUrl (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: isHttpUrl treats mixed-case http(s) as URLs and base64/other as non-URLs.
  • Full gate: lint 0 · build OK · 1081 unit · 26 e2e pass.

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.

…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.
@rmyndharis
rmyndharis merged commit cae5e00 into main Jun 21, 2026
5 checks passed
@rmyndharis
rmyndharis deleted the fix/ssrf-dns-timeout-and-media-url-case branch June 21, 2026 12:55
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>
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