Skip to content

feat: optional proxy for link-preview (Open Graph) fetches - #358

Open
eliasmeireles wants to merge 1 commit into
asternic:mainfrom
eliasmeireles:feat/og-fetch-proxy
Open

feat: optional proxy for link-preview (Open Graph) fetches#358
eliasmeireles wants to merge 1 commit into
asternic:mainfrom
eliasmeireles:feat/og-fetch-proxy

Conversation

@eliasmeireles

@eliasmeireles eliasmeireles commented Aug 15, 2026

Copy link
Copy Markdown

Some sites answer a captcha or geo-block page instead of the real page when the request arrives from outside the country they serve. getOpenGraphData then reads that page's metadata, and the preview card goes out showing the block page's title instead of the product or article.

A concrete case, same URL and the same WhatsApp/2.23.20.0 User-Agent, fetched at the same moment from two hosts:

Origin Result
Canada (OVH) redirected to /az-request-verify, <title>Captcha Magalu</title>, 24 KB, no og: metadata
Brazil (Oracle São Paulo) real product page, 315 KB, og:title present

The card the user received read "Captcha Magalu". This is decided by source IP, not by headers — the User-Agent added in #345 does not change the outcome above.

What this adds

OG_FETCH_PROXY routes only the Open Graph fetch through a proxy. It is off by default, so behaviour is unchanged unless the variable is set.

It is deliberately not the conventional HTTPS_PROXY. globalHTTPClient is used by exactly one call site (fetchOpenGraphData via helpers.go), and a dedicated name keeps a generic proxy setting from silently capturing traffic it was never meant to. WhatsApp session traffic keeps whatsmeow's own transport and its existing per-user proxy_url / /session/proxy configuration, untouched.

Two details worth review:

  • Lazy resolution. globalHTTPClient is a package-level var, so it is built before main() calls godotenv.Load(). Reading the variable on first use instead of at init keeps both the process environment and .env working.
  • SSRF guard interaction. With a proxy set, DialContext targets the proxy, which commonly sits on a private address, so isPrivateOrLoopback has to allow that one host. This shifts responsibility rather than removing it: the real target is no longer resolved locally, so the proxy is what must deny internal ranges. That trade-off is called out in the code comment and in the README, since an operator pointing this at a permissive proxy would be widening SSRF exposure.

An invalid value is logged and ignored rather than failing startup — a broken preview card is a smaller problem than a server that refuses to boot.

@eliasmeireles
eliasmeireles marked this pull request as ready for review August 15, 2026 13:57
@eliasmeireles

Copy link
Copy Markdown
Author
image

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