Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save charl-kruger/24ec3b26d28c6e9f9e3084acf3780050 to your computer and use it in GitHub Desktop.

Select an option

Save charl-kruger/24ec3b26d28c6e9f9e3084acf3780050 to your computer and use it in GitHub Desktop.
sufly
## Verdict
**I would not approve the standard `surfly.js` integration for authenticated or transactional banking pages as-is.** It is not just a “support widget”; it is a third-party JavaScript loader plus a co-browsing/proxy system that can interact with authenticated browser state, page contents, navigation, events, files, screenshots, and session control. That threat model is too powerful for core banking unless it is heavily isolated, hardened, independently reviewed, and preferably deployed in a controlled/private/on-prem model.
One important limitation: I could not retrieve the raw live `https://surfly.com/surfly.js` body through the available web reader because it rejected the JavaScript content type, and the execution container could not resolve the host. So this is **not** a line-by-line audit of the minified live asset. For a banking approval, that alone is a gating issue: you should require the exact source, sourcemaps where applicable, a version-pinned artifact, dependency inventory, hashes, and change-control evidence before go-live. The public docs and integration model are still enough to conclude that the default/public SaaS integration is not suitable for sensitive banking flows.
---
## What Surfly is actually doing
The documented integration dynamically injects a remote script from `https://surfly.com/surfly.js` into the page, asynchronously loads the Surfly libraries, and then exposes `Surfly.init()` and other API methods to the host page. The current quickstart explicitly shows a loader that creates a `<script>` element, sets `async = 1`, and sets `src` to `https://surfly.com/surfly.js`; it also notes that the rest of the API is not available until initialization is ready. ([docs.surfly.com][1])
Architecturally, Surfly describes itself as JavaScript plus a **content-rewriting proxy**. In a session, the leader browser’s request goes to the Surfly proxy, the proxy modifies the request to look as if it came from `surfly.com`, sends it to the original site, receives the response, modifies that content so it can load inside an iframe, and synchronizes interaction between leader and follower browsers. Surfly’s own docs say the proxy “overcomes cross-domain policies” and allows co-browsing of logged-in sessions while not sending security tokens/passwords to the follower. ([docs.surfly.com][2])
That is a powerful model. It means a banking site would be adding a third-party runtime and proxy-based browsing layer into the same customer journey where balances, account numbers, payment forms, identity flows, tokens, statements, and fraud controls may exist.
---
## Main security conclusion
**This is not secure enough for a banking platform in its default or typical documented form.**
It may be acceptable only for a constrained use case such as:
**“Agent can view and guide a user on a non-transactional support surface, with sensitive fields masked, no agent control, no cookie/storage transfer, no file sharing, no screenshots/recording, no arbitrary navigation, strict allowlisting, server-side transaction blocking, version-pinned code, and independent audit of the actual Surfly JavaScript and proxy.”**
For anything involving authenticated balances, statements, onboarding KYC, card data, payments, beneficiary creation, loan applications, password reset, OTP entry, PIN entry, or privileged admin actions, I would treat it as **not approved unless isolated into a specially designed support environment**.
---
## Critical findings
### 1. Third-party JavaScript supply-chain risk is fundamental here
The bank page loads executable JavaScript from Surfly’s domain. The documented snippet does not show Subresource Integrity, an immutable versioned asset URL, or a pinned hash; it dynamically loads `https://surfly.com/surfly.js`. ([docs.surfly.com][1])
That matters because third-party JavaScript executes with the privileges of the embedding page. OWASP’s third-party JavaScript guidance describes this class of risk as effectively comparable to XSS: the script can execute in the user’s browser context, and key risks include arbitrary code execution, loss of control over upstream changes, and sensitive-data leakage. ([OWASP Cheat Sheet Series][3])
For a bank, a compromised or maliciously changed `surfly.js` could potentially:
* Read or alter DOM content, including balances, names, account details, form fields, and transaction screens.
* Trigger same-origin requests to banking APIs from the customer’s browser; HttpOnly cookies would still be attached by the browser even if JavaScript cannot read them directly.
* Manipulate payment or beneficiary forms before submission.
* Capture interaction telemetry, form values, navigation state, or screenshots depending on configuration and runtime behavior.
* Bypass the bank’s own release/change-control process because code is being pulled from a third-party origin at runtime.
This is not an allegation that Surfly is malicious. It is an inherent risk of placing mutable third-party JavaScript inside authenticated banking pages.
---
### 2. The proxy model is a deliberate man-in-the-browser / man-in-the-session pattern
Surfly’s technology documentation says the user’s web requests are routed through a Surfly proxy, that the proxy rewrites responses so they can be loaded in an iframe, and that this mechanism is designed to overcome cross-domain restrictions. ([docs.surfly.com][2])
That is exactly the kind of mechanism banks usually design controls to prevent. Banking security often depends on origin boundaries, device/session binding, anti-clickjacking, CSP, secure cookies, SameSite behavior, fraud telemetry, bot detection, step-up authentication, and transaction-signing semantics. A proxy that rewrites content and synchronizes browser activity can interfere with or complicate those controls.
Surfly says security tokens and passwords are not sent to followers, and that followers see only visual changes. ([docs.surfly.com][2]) That is useful, but it is not enough for banking approval. The bank would still need to prove exactly where sensitive data is visible, where it is processed, whether it is transformed before or after leaving the customer browser, what the proxy can see, what is logged, what metadata is stored, and how session state is handled.
---
### 3. Cookie, storage, and form transfer defaults are dangerous for banking
Surfly’s session options document shows `cookie_transfer_enabled` defaults to `true`, and describes it as transferring cookies, localStorage, sessionStorage, and form fields into the session and back, excluding form fields on the back-transfer. It also documents `cookie_transfer_proxying`, which can transfer HttpOnly cookies between the session and original page when configured with a continuation point. ([docs.surfly.com][4])
For banking, this is a major red flag.
Bank authentication state is often stored across Secure/HttpOnly/SameSite cookies, localStorage, sessionStorage, browser-bound tokens, anti-CSRF state, device-binding values, and fraud-analysis identifiers. A co-browsing product that can transfer or proxy these values can undermine assumptions made by the bank’s session-management design.
At minimum, a banking configuration should disable cookie/storage transfer unless there is a very carefully reviewed, narrow, non-authenticated support use case. I would consider these settings unacceptable on core banking pages:
```js
cookie_transfer_enabled: true
cookie_transfer_proxying: true
enable_cookie_backtransfer: true
```
The safe posture for banking is to avoid transferring authentication state into a co-browsing context at all.
---
### 4. Masking is useful, but fragile and not sufficient
Surfly documents field masking and says password fields are masked by default, while other fields require either the `surfly_private` attribute or configuration such as `hide_element_by_selector`. It also documents an important limitation: masked fields inside `GET` forms, or masked values that become part of URLs, may still be trackable through audit logs and browser network activity. ([docs.surfly.com][5])
That limitation is highly relevant for banking. Sensitive values often appear not just in visible fields but also in:
* URLs and query parameters.
* Hidden fields.
* DOM attributes.
* Client-side state stores.
* JavaScript variables.
* Analytics events.
* Error messages.
* Download URLs.
* Statement/document viewers.
* Shadow DOM or dynamically rendered components.
* Autocomplete and browser-managed inputs.
Masking is an implementation detail, not a security boundary. A bank should not rely on CSS selectors or field attributes as the primary control protecting account numbers, card numbers, balances, OTPs, PINs, KYC information, payment details, or statement data.
---
### 5. Default session-control options are too permissive
The documented defaults include several settings that are risky for a bank. Surfly’s options show `host_switching_allowed: true`, `participants_can_request_to_interact: true`, `new_urls_allowed: true`, `non_hosts_can_open_tabs: true`, and `allow_opening_urls_from_query_parameter: true`. The same options list shows `private_session: false`, `password_required: false`, and `admission_enabled: false` by default. ([docs.surfly.com][4])
Surfly’s session API also exposes leader/follower links, PINs, host switching, tab control, control transfer, URL relocation, file upload, screenshots, and broadcast messaging. ([docs.surfly.com][6])
For banking, these defaults are not acceptable. A leaked follower link, weak join process, permissive control transfer, arbitrary navigation, or accidental screen-share into sensitive pages could expose regulated data. Even if Surfly has server-side protections, the integration should start from a denial posture: no arbitrary navigation, no default invitations, no agent control, no participant control request, no host switching, no file upload, no screenshot, and no recording unless there is a specific approved need.
---
### 6. The messaging bridge can become a cross-context attack surface
Surfly documents `sendMessage(message, targetOrigin)` for communication between the original page and proxified version, and explicitly notes that using `targetOrigin = "*"` delivers the message regardless of recipient origin. ([docs.surfly.com][6])
That creates an integration risk. If the bank’s application, Surfly widget, or session extensions use permissive message handling, this can become a cross-origin bridge problem: sensitive data or commands may be sent to the wrong context, or untrusted contexts may influence the banking page.
For a bank, any `postMessage`-style integration must use exact origin checks, strict schema validation, replay protection where needed, and no wildcard target origins for sensitive messages.
---
### 7. Audit logs, screenshots, file sharing, chat, and events can leak sensitive data
Surfly documents audit logging for actions such as button clicks, text inputs, control transfers, documents shared, and pages visited. The docs also list events containing page URLs, form changes, submissions, clicks, participant metadata, IP addresses, names, and control transfers. ([docs.surfly.com][5])
The JS API also exposes events such as user activity, relocation, tab control, file download, messages, and session lifecycle events. Some events include URLs, filenames, user data, origins, and final locations. ([docs.surfly.com][7])
For normal customer support, this may be useful. For banking, it can become a regulated-data leakage path. Page URLs can contain identifiers. File names can reveal statements or claims. Form-change logs can accidentally capture customer input. Screenshots can capture balances, statements, KYC documents, or card details. Chat logs can contain PII or authentication information.
Surfly does provide controls for audit logs, screenshots, video, screen sharing, file sharing, and privacy options, but the existence of these capabilities means the bank needs a strict data-retention and data-minimization design rather than a simple widget integration. ([docs.surfly.com][4])
---
### 8. Client-side “disable submit button” controls are not enough
Surfly’s security documentation includes an example approach for disabling submit buttons while an agent has control. ([docs.surfly.com][5])
That may reduce accidental agent actions, but it is not a banking-grade control. Any control implemented only in browser JavaScript can be bypassed by bugs, DOM changes, race conditions, malicious scripts, browser devtools, API calls, or integration mistakes.
For banking, transactional protection must be enforced server-side. Examples:
* Agents must never be able to initiate or approve payments.
* Agent-controlled sessions must be blocked from payment, beneficiary, card-management, loan-approval, password-reset, OTP/PIN, or statement-download endpoints.
* “Customer in control” must be proven server-side, not inferred from a button state.
* Step-up authentication must occur outside the co-browsing context where possible.
* Transaction signing must bind the exact beneficiary, amount, and account to the customer’s authenticated approval.
---
## Medium-risk findings
### Domain allowlisting is not a sufficient boundary
The docs require domains to be added to a widget key domain list, with examples such as `*example.com` or `*.example.com`. ([docs.surfly.com][1])
That helps prevent casual misuse of a widget key, but it does not solve the core risks. It does not constrain what the loaded JavaScript can do once it is running on an approved banking origin. Wildcard subdomain patterns can also become dangerous if any subdomain is vulnerable to takeover, XSS, or weak hosting controls.
---
### The documented loader lacks visible SRI protection
The snippet does not show an `integrity` attribute or a pinned versioned URL. SRI is specifically designed to let browsers verify that a fetched script matches an expected cryptographic hash and detect unexpected tampering. ([OWASP Foundation][8])
For a banking platform, a mutable third-party script URL without SRI or version pinning is a major supply-chain concern. Dynamic SaaS JavaScript may be operationally convenient, but it is hard to reconcile with banking change-control requirements unless the bank has compensating controls such as self-hosting, a private build, strict CSP, hash monitoring, and contractual change notification.
---
### User metadata can become PII leakage
Surfly allows user data such as name and email to be passed into sessions and displayed in queues/events. ([docs.surfly.com][6])
That means integration teams must treat Surfly session metadata as regulated customer data. Do not pass full customer identifiers, account numbers, phone numbers, national IDs, or unnecessary email addresses unless explicitly approved by privacy and legal teams. Use opaque internal support identifiers instead.
---
## Banking approval recommendation
### Do not approve this configuration
I would reject the following pattern for banking:
```html
<script>
// Standard Surfly loader from surfly.com
// included on authenticated banking pages
</script>
```
especially if combined with:
```js
cookie_transfer_enabled: true,
cookie_transfer_proxying: true,
host_switching_allowed: true,
participants_can_request_to_interact: true,
new_urls_allowed: true,
non_hosts_can_open_tabs: true,
allow_opening_urls_from_query_parameter: true,
filesharing_enabled: true,
screensharing_enabled: true,
audit_logs_enabled: true,
automatic_screenshots_enabled: true,
private_session: false,
password_required: false,
admission_enabled: false
```
That configuration is too permissive for banking.
---
## Minimum hardening profile if the bank still wants Surfly
The safer design is to **not load Surfly at all on the real online-banking app**. Instead, create a separate support surface that contains only the minimum information required for troubleshooting.
A hardened Surfly-style configuration should look conceptually like this, with exact option names validated against your Surfly tenant/version:
```js
Surfly.init({
widget_key: "REDACTED",
// Keep Surfly out of ordinary browsing where possible
embedded_sessions_only: true,
session_start_confirmation: true,
// Join/session protection
private_session: true,
password_required: true,
admission_enabled: true,
default_invitations_enabled: false,
invitations_allowed: false,
// No agent/customer role switching unless formally approved
host_switching_allowed: false,
participants_can_request_to_interact: false,
// Prevent arbitrary navigation
new_urls_allowed: false,
non_hosts_can_open_tabs: false,
allow_opening_urls_from_query_parameter: false,
// Strict URL allowlisting
allowlist: JSON.stringify([
{
pattern: "^https://bank\\.example/(support-safe|help)(/|$)",
type: "all"
}
]),
// Avoid transferring banking auth/session state
cookie_transfer_enabled: false,
cookie_transfer_proxying: false,
enable_cookie_backtransfer: false,
// Disable high-risk data movement
filesharing_enabled: false,
download_trigger_enabled: false,
videochat: false,
screensharing_enabled: false,
automatic_screenshots_enabled: false,
automated_session_recording_enabled: false,
// Logs should be off unless privacy/legal approve exact fields and retention
audit_logs_enabled: false,
chat_logs_enabled: false,
// Mask aggressively, but do not rely on masking as the main boundary
hide_element_by_selector:
"[data-sensitive], .balance, .account-number, .iban, .card-number, .pan, .otp, .pin, .statement, .transaction-history, form[action*='payment'] *"
});
```
That is still not enough by itself. The bank also needs server-side controls.
---
## Required server-side controls
For a banking platform, the server must enforce the real security boundary:
1. **Block transactional endpoints during co-browsing.** Payments, transfers, beneficiary changes, password resets, OTP/PIN changes, card controls, personal-data updates, and document downloads should be blocked or forced into customer-only flows.
2. **Do not trust client-side Surfly state.** Headers such as Surfly detection headers should be treated carefully. Strip spoofable headers at the edge and only trust signals inserted by a controlled reverse proxy or trusted infrastructure.
3. **Use a separate support origin or path.** Example: `support.bank.example`, not the main authenticated banking app.
4. **No sensitive values in URLs.** Surfly’s own docs warn that masked values used in GET forms or URLs can still be exposed through logs/network activity. ([docs.surfly.com][5])
5. **Require exact origin validation for messaging.** No wildcard `targetOrigin: "*"` for sensitive messages.
6. **Use least-privilege agent access.** Agents should authenticate with SSO/MFA, have scoped permissions, and be unable to join sessions without customer approval.
7. **Keep customer secrets out of the session.** OTPs, PINs, passwords, card CVVs, recovery codes, and transaction-signing prompts should never be visible or controllable in a co-browsing session.
8. **Use data-loss prevention on logs and recordings.** Prefer no screenshots, no recording, no chat logs, and no audit payloads containing form values unless explicitly required and redacted.
---
## Evidence I would demand from Surfly before approval
Before using this in a bank, I would require:
* Exact source code for the deployed `surfly.js`, including minified and unminified versions.
* Version-pinned JavaScript artifact, immutable URL, and SRI-compatible hash.
* SBOM for JavaScript, proxy, backend, browser-extension/session components, and dependencies.
* Independent penetration test covering the Surfly proxy, session join links, PINs, WebSocket/session channels, masking, logging, screenshots, file transfer, and `postMessage` bridge.
* Documentation of exactly what data the Surfly proxy can see in authenticated sessions.
* Proof of how masking works: before data leaves the customer browser, inside the proxy, or only in the follower rendering.
* Token/link entropy, TTLs, revocation behavior, brute-force protection, and leak handling.
* Controls for follower-link sharing, admission, identity verification, and agent impersonation.
* CSP guidance for banking pages.
* Confirmation that Surfly can run with no cookie/localStorage/sessionStorage transfer.
* Logs/recordings retention policy, encryption, deletion SLAs, and data residency guarantees.
* A private/on-prem deployment option evaluation, since Surfly’s own docs mention on-prem installation as an option. ([docs.surfly.com][2])
---
## Final risk rating
**Default/public Surfly integration on authenticated banking pages: Critical risk — not approved.**
**Hardened Surfly on a segregated, non-transactional support surface: Potentially approvable after source review, proxy review, strict configuration, independent pen test, legal/privacy review, and server-side transaction blocking.**
**Use inside real online banking flows involving balances, payments, statements, KYC, credentials, OTPs, PINs, or card data: Not recommended.**
The core reason is simple: Surfly’s value proposition is the same thing that makes it risky for banking. It gives a third-party script and proxy enough visibility and control to make co-browsing work. In a low-risk website, that may be acceptable. In a banking platform, that is a privileged session-interposition mechanism and must be treated like a high-risk extension of the bank’s trusted computing base.
[1]: https://docs.surfly.com/surfly/javascript-api/quickstart "Quickstart | Documentation Portal"
[2]: https://docs.surfly.com/surfly/technology/ "Surfly Technology | Documentation Portal"
[3]: https://cheatsheetseries.owasp.org/cheatsheets/Third_Party_Javascript_Management_Cheat_Sheet.html?utm_source=chatgpt.com "Third Party JavaScript Management Cheat Sheet - OWASP"
[4]: https://docs.surfly.com/surfly/session-options "Co-Browsing session options | Documentation Portal"
[5]: https://docs.surfly.com/surfly/tutorials/security-functionalities "Security functionalities | Documentation Portal"
[6]: https://docs.surfly.com/surfly/javascript-api/surfly-session "Sessions | Documentation Portal"
[7]: https://docs.surfly.com/surfly/javascript-api/surfly-events "Events | Documentation Portal"
[8]: https://owasp.org/www-community/controls/SubresourceIntegrity?utm_source=chatgpt.com "Subresource Integrity (SRI) - OWASP Foundation"
@charl-kruger

Copy link
Copy Markdown
Author

Second report: Surfly surfly.js / apiframe.js security assessment for banking use

Based on the artifacts uploaded on 15 May 2026, the position has moved from “high-level architectural concern” to confirmed code-level concern.

Executive verdict

I would not approve this integration for authenticated banking pages, online-banking dashboards, payment flows, onboarding/KYC flows, statements, card management, password/PIN/OTP flows, or any page containing regulated financial data.

The uploaded code confirms that Surfly is not a passive support button. It is a remote JavaScript runtime that:

  • loads mutable code from Surfly infrastructure;
  • injects iframes into the customer page;
  • executes a large Surfly runtime inside the bank page context;
  • collects and restores form state;
  • can collect localStorage and sessionStorage;
  • has cookie-transfer logic;
  • uses broad postMessage command channels;
  • exposes tab/navigation/control/file/chat/video/session commands;
  • initializes Sentry telemetry;
  • creates full-page overlays with very high z-index;
  • grants session iframes microphone, camera, display-capture, and autoplay permissions.

For a normal sales/support website this may be acceptable with configuration controls. For a bank, this is a privileged session-interposition component. It should be treated closer to a browser extension, remote-control tool, session proxy, and third-party analytics/runtime combined.


1. What we have now learned

1.1 The public surfly.js file is only a loader

The uploaded surfly.js is not the main application. It is a small bootstrap file. It injects CSS, creates an iframe called surfly-api-frame, writes a blank document into it, and then loads:

/static/bundles/widget/apiframe.js

from window.SURFLY_COBRO_ORIGIN. The loader also exposes the iframe globally as window.surflyApiFrame.

The headers show this loader is served as application/javascript, with cache-control: max-age=86400, last modified on 11 May 2026, and served by surfly.com. The hash you uploaded for the loader was:

37e440af35c3a7d0267f3834f3a2a9c2926c1c7066ce6d461dab767737ca0c8a

Security meaning: the bank would not just be including a static file. It would be including a loader that pulls the real runtime from Surfly. Without SRI/version pinning, this is a mutable third-party dependency chain.


1.2 The real runtime is apiframe.js

The uploaded apiframe.js is the meaningful file. It is minified and has no source map available, so this is not a perfect source-level audit. But the code is clear enough to identify several major behaviours. The file includes bundled runtime code, Sentry, translation/polyfill/event-emitter code, session-control logic, state-transfer logic, cookie/storage-transfer logic, popup/overlay code, and Surfly/Webfuse API setup.

The missing source map is important. It means we still cannot produce a complete SBOM or exact source-to-runtime trace. For banking approval, Surfly should provide source maps or unminified source under NDA.


2. Confirmed libraries and embedded components

The uploaded apiframe.js does not expose a clean package.json, but fingerprinting shows the following components with reasonable confidence.

Component Confidence Security relevance
@sentry/browser / Sentry JS SDK Confirmed Error telemetry, breadcrumbs, request/context capture
Sentry SDK version 7.120.4 Confirmed Version visible in bundle
Sentry GlobalHandlers Confirmed Captures global errors
Sentry RewriteFrames Confirmed Rewrites stack frames to ~/static/bundles/widget/apiframe.js
Sentry Breadcrumbs Confirmed Instruments console, DOM, fetch, history, XHR
fbemitter-style EventEmitter High confidence Internal event bus
node-polyglot-style translation system High confidence UI translations
punycode Confirmed URL/domain conversion
object.entries / object-keys / intrinsic/polyfill helpers High confidence Bundled compatibility/polyfill code

The most security-relevant finding is Sentry. The bundle initializes Sentry with a real DSN and release hash:

https://510f80906f4d429ba882cf65fdfbbca6@sentry.io/1545691
release: 4ed92455697daa4e4bf0aed94cffe820676bf8f7

and sets a logger=jsapi tag.

The bundle also shows Sentry SDK metadata generation using sentry.javascript.browser and package metadata of the form npm:@sentry/browser, with version 7.120.4.


3. Highest-risk code behaviours

3.1 Same-page iframe injection without sandboxing

The loader creates an iframe with:

r.id = "surfly-api-frame";
r.name = "surfly-api-frame";
r.className = "surfly-invisible";
window.surflyApiFrame = r;

It then appends the iframe, writes an HTML document into it, and loads apiframe.js into that document.

This matters because the iframe is not created with a restrictive sandbox attribute. It is effectively a privileged helper frame injected into the banking page. The script source is Surfly, but the script executes inside a document created by the bank page.

Banking danger: this gives Surfly’s runtime a deep foothold inside the authenticated page environment. It is not equivalent to embedding a simple isolated SaaS iframe from another origin.


3.2 Form-state collection and restoration

The apiframe.js code walks the page and collects state from:

input
select
textarea
[contentEditable]
same-origin iframes
scroll position

It serializes values, checked states, disabled states, selected states, and contentEditable inner HTML. The only explicit form-field exclusion visible in the snippet is _csrf.

It also contains restore logic that writes those values back into the page:

localStorage
sessionStorage
scroll
input
contentEditable
iframe

and restores input values, checked states, disabled states, selected states, and innerHTML.

Banking danger: this is a direct risk to account numbers, balances, payment forms, beneficiary forms, card forms, loan forms, KYC fields, OTP-like fields if not properly isolated, and any sensitive data rendered into editable areas. Masking is not enough if the runtime can collect raw DOM/form state before or outside the masking boundary.


3.3 Web storage collection: localStorage and sessionStorage

The bundle explicitly collects browser storage when cookie transfer is enabled and cookie proxying is not active:

Object.entries(u.mainWindow.localStorage || [])
Object.entries(u.mainWindow.sessionStorage || [])

It filters out keys beginning with surfly_, then sends storage as part of a restore_cookies widget message with targetOrigin: "*".

A separate helper shows the filter and output shape:

{ type: "localStorage", value: ... }
{ type: "sessionStorage", value: ... }

and again only excludes keys starting with surfly_.

Banking danger: many banking SPAs store sensitive or security-relevant state in web storage: feature flags, anti-fraud IDs, device binding hints, customer context, OAuth/OIDC transient state, API cache data, user profile data, UI state, or sometimes even tokens in weaker implementations. Surfly’s code path can collect and replay that state.

This is a major no-go unless storage transfer is disabled and independently verified.


3.4 Cookie-transfer and session-continuation logic

The runtime has explicit restore_cookies handling. It checks the source origin, references cookie_transfer_enabled and cookie_transfer_proxying, and sends a cookie_transfer_done event on failure or completion.

When opening a session link, the code also checks:

this.settings.cookie_transfer_enabled && this.settings.cookie_transfer_proxying

and, if enabled, rewrites the session continuation path to:

/surfly_cookie_transfer/start/<token>

otherwise it logs a cookie-transfer failure.

Banking danger: session cookies and continuation are core banking security assets. Even if Surfly’s intention is legitimate, any mechanism that transfers, proxies, restores, or continues authenticated session state creates a serious threat to session binding, fraud detection, device trust, CSRF assumptions, SameSite design, and step-up authentication.

For banking, cookie transfer/proxying should be treated as prohibited by default.


3.5 postMessage channel with wildcard target origin support

The runtime processes widget messages if:

targetOrigin === "*"

or if the target origin equals the main window origin. It then dispatches messages either to the user event emitter or to internal widget handlers such as restore_cookies.

The storage-collection path sends a message with:

targetOrigin: "*"
target: "widget"
msg: "restore_cookies"
cookies: ...
storage: ...

Banking danger: wildcard message handling is a design smell in high-assurance contexts. Even if other checks exist, the bank should not accept a sensitive integration where important control or state-transfer messages can be routed through wildcard-origin semantics. For banking, every message must have strict origin, schema, session, nonce, and command validation.


3.6 Session-control, tab-control, navigation, upload, chat, and video commands

The bundle defines a large command/event surface including:

UPLOAD_FILE
SEND_CHAT_MESSAGE
BROADCAST_MESSAGE
OPEN_POPUP
OPEN_SIDE_PANEL
FOLLOW_PARTICIPANT

and many tab/session events such as:

SESSION_CREATED
SESSION_STARTED
VIEWER_JOINED
PARTICIPANT_JOINED
TAB_CONTROL_REQUESTED
CONTROL
TAB_CONTROL
TAKE_SCREENSHOT
FILE_DOWNLOAD

The runtime also exposes methods that post control messages to the Surfly iframe for tab control, relocation, pause/resume, and videochat mode/fullscreen changes.

Banking danger: these are exactly the capabilities that must be blocked around payments, beneficiaries, statements, profile changes, card controls, login recovery, and KYC. A co-browsing agent must not be able to trigger or influence transaction flow. Relying on front-end discipline is not enough.


3.7 Powerful iframe permissions

When creating the main session iframe, the code sets:

microphone
camera
display-capture
autoplay

allowed for the Surfly co-browsing origin.

Banking danger: display capture and media permissions are not inherently malicious, but they are high-risk in regulated banking. They create additional channels for data exposure and social-engineering abuse. Even if user-consented, this needs strict business justification and technical restriction.


3.8 Full-page overlays and high z-index UI

The loader CSS gives #surfly-api-frame a very high z-index:

z-index:2147483548!important

and supports surfly-blocker and surfly-popup states that can cover the full viewport.

The runtime also includes blocker and popup CSS with fixed full-screen overlays and popup iframes.

Banking danger: full-page overlays are expected for support UX, but in banking they overlap with clickjacking, phishing, transaction-confusion, and agent-assisted fraud risks. A malicious, compromised, or incorrectly configured runtime could obscure or imitate banking UI.


3.9 Third-party telemetry via Sentry

The runtime initializes Sentry and includes integrations that capture browser context. The visible code includes Sentry Breadcrumbs, which instruments console, DOM, XHR, fetch, history, and Sentry events.

The Sentry global handlers capture onerror and error context; the HTTP context integration can add request URL, referrer, and user-agent data to events.

Banking danger: telemetry can leak URLs, element selectors, error messages, stack traces, request metadata, console arguments, and user interaction breadcrumbs. This may include account identifiers, customer IDs, internal route names, transaction references, or sensitive page structure.

Even if Surfly/Sentry configure filters, this is another third-party data path that the bank must inventory, contractually control, redact, and test.


4. Threat scenarios specific to banking

Scenario A: Remote script compromise

If Surfly’s hosted script or build pipeline is compromised, the attacker does not need to exploit the bank directly. The bank has already invited Surfly’s JavaScript into the page. A malicious update could:

  • read account balances and customer details from the DOM;
  • alter payment or beneficiary form fields;
  • call banking APIs from the customer’s browser;
  • capture typed data before masking;
  • exfiltrate local/session storage;
  • manipulate UI overlays;
  • interfere with step-up flows.

Impact: catastrophic.
Likelihood: not knowable from public code, but the impact is high enough that banks normally require version pinning, SRI, self-hosting, or private deployment review.


Scenario B: Sensitive form leakage through state transfer

The runtime collects inputs, selects, textareas, contenteditable HTML, same-origin iframe state, and scroll state. Only _csrf is visibly excluded in the searched code.

A banking page might contain:

  • account numbers;
  • IBANs;
  • card details;
  • transfer amounts;
  • beneficiary names;
  • ID/passport numbers;
  • income/employment data;
  • loan application data;
  • address/phone/email data;
  • security-question answers;
  • one-time passcodes in ordinary input fields.

Impact: regulated-data exposure and potential fraud.
Conclusion: unacceptable unless Surfly is completely excluded from pages containing sensitive fields.


Scenario C: Storage/token leakage

The runtime can collect localStorage and sessionStorage, excluding only keys prefixed with surfly_.

Even if the bank follows best practice and keeps session tokens in HttpOnly cookies, storage may still contain sensitive state or useful attack context. If the bank has any SPA tokens, device IDs, customer context, cached API data, feature flags, or fraud metadata in web storage, this becomes a leakage path.

Impact: account compromise risk, privacy breach, fraud-control degradation.
Conclusion: storage transfer must be disabled and verified.


Scenario D: Session boundary weakening through cookie transfer

The bundle contains cookie-transfer and proxying logic, including a /surfly_cookie_transfer/start/<token> flow when cookie transfer and proxying are enabled.

Banking session controls depend on strict assumptions about where cookies live, how they are bound, how SameSite works, and how fraud systems interpret device/browser continuity.

Impact: undermines session-management assumptions.
Conclusion: cookie transfer/proxying should not be permitted in authenticated banking.


Scenario E: Agent-assisted transaction risk

The runtime exposes control, relocation, tab-control, file, chat, video, popup, and broadcast commands.

Even if agents are trusted, banking fraud often involves social engineering, insider risk, or accidental misuse. A support agent must not be placed in a position where they can influence payments, beneficiaries, card changes, loan submissions, password resets, or OTP/PIN entry.

Impact: unauthorized or disputed transactions.
Conclusion: transactional endpoints must be server-side blocked during co-browsing.


Scenario F: Telemetry leakage to Sentry

The bundle initializes Sentry with a real DSN and release hash, and the Sentry code instruments browser activity.

If an exception occurs on a banking page, captured context could include route URLs, DOM breadcrumbs, console arguments, request URLs, and metadata.

Impact: leakage of customer identifiers, internal app routes, transaction references, or page structure.
Conclusion: the bank must require telemetry disablement, redaction, or a private Sentry/project boundary with contractual controls and test evidence.


5. Banking risk rating

Area Risk Reason
Third-party script supply chain Critical Remote mutable code runs in banking page context
Form-state handling Critical Inputs, textareas, selects, contenteditable, iframes, and scroll state can be serialized/restored
Storage handling Critical localStorage and sessionStorage collection path exists
Cookie/session transfer Critical Cookie restore and proxying/continuation paths exist
Transaction integrity Critical Control/navigation/tab APIs can influence session flows
Data privacy High/Critical PII and financial data exposure through DOM, storage, telemetry, logs, screenshots, chat, file events
Telemetry High Sentry is embedded and browser breadcrumbs are present
UI overlay/phishing High Full-page overlays and very high z-index
Source transparency High No source map available, no full SBOM
Config-hardening dependency High Safety depends heavily on correct Surfly configuration and correct bank integration

Overall: Critical risk for authenticated banking use.


6. Is it “secure enough” for a banking platform?

For public marketing pages

Possibly acceptable, if no customer data, no authenticated state, no sensitive forms, and strict CSP/vendor monitoring are used.

For authenticated online banking

No. Not acceptable as-is.

The code has too much access to page state, storage, session mechanics, UI, navigation, and telemetry.

For support pages inside banking

Only possibly acceptable if isolated.

A safer pattern would be a separate support origin or support-only app that deliberately excludes balances, statements, transaction history, payments, beneficiaries, credentials, KYC documents, OTP/PIN fields, and card data.

For payments, transfers, beneficiary management, login recovery, OTP/PIN, card controls, or statements

Hard no.

Those flows should not include Surfly or any similar co-browsing runtime.


7. Minimum mandatory controls if the bank still wants to use Surfly

These are not “nice to have.” They are the minimum I would require before considering approval.

7.1 Do not load Surfly globally

Surfly must not be included in the base banking shell, global tag manager, common layout, or authenticated SPA root.

It should be loaded only on a dedicated, low-risk support surface.


7.2 Disable cookie and storage transfer

Required settings/concepts:

cookie_transfer_enabled: false
cookie_transfer_proxying: false
enable_cookie_backtransfer: false

Then verify at runtime that:

  • no collect_web_storage message is sent;
  • no restore_cookies message is sent;
  • no /surfly_cookie_transfer/start/ path is used;
  • no banking cookies, localStorage, or sessionStorage values leave the page.

7.3 Block Surfly on sensitive routes

The bank should hard-block Surfly from:

/login
/mfa
/otp
/pin
/password
/reset
/payments
/transfers
/beneficiaries
/cards
/statements
/documents
/kyc
/profile
/limits
/loans
/applications
/admin

This must be enforced server-side and in routing, not just by hiding the button.


7.4 Server-side transaction blocking during co-browse

When a co-browsing session is active or suspected:

  • block payment initiation;
  • block beneficiary creation/editing;
  • block card control changes;
  • block password/PIN/OTP changes;
  • block profile changes;
  • block document downloads;
  • block statement exports;
  • require the user to exit co-browse and re-authenticate or step up.

Do not rely on client-side button disabling.


7.5 Disable or strictly constrain agent control

For banking, the default posture should be:

host_switching_allowed: false
participants_can_request_to_interact: false
new_urls_allowed: false
non_hosts_can_open_tabs: false
allow_opening_urls_from_query_parameter: false
filesharing_enabled: false
download_trigger_enabled: false
screensharing_enabled: false
automatic_screenshots_enabled: false
automated_session_recording_enabled: false
videochat: false

Exact option names should be verified against Surfly’s current config, but the policy is clear: view-only, no transaction control, no files, no screenshots, no arbitrary navigation.


7.6 Disable or isolate Sentry telemetry

The bank should require one of:

  • Sentry disabled entirely in the Surfly runtime for the bank tenant;
  • Sentry routed to a bank-controlled project with strict retention;
  • complete redaction of URLs, breadcrumbs, DOM selectors, console args, request data, and user metadata;
  • written proof of beforeSend filtering and test evidence.

The current bundle clearly initializes Sentry and browser telemetry code exists.


7.7 Require version pinning or self-hosting

The bank should not load:

https://surfly.com/surfly.js

as an unpinned mutable runtime in production banking.

Acceptable alternatives would be:

  • bank-hosted reviewed artifact;
  • private Surfly deployment;
  • immutable versioned Surfly URL;
  • SRI hash with emergency rotation process;
  • formal change notification and approval;
  • daily hash monitoring and alerting.

The current loader is served with a one-day cache and points to the next bundle dynamically.


8. What I would ask Surfly directly

Before any banking approval, ask Surfly for written answers and evidence:

  1. Can cookie_transfer_enabled, cookie_transfer_proxying, and all storage transfer be completely disabled for a tenant?
  2. Can Sentry be disabled or routed to a bank-controlled telemetry endpoint?
  3. Can they provide unminified apiframe.js, source maps, and SBOM?
  4. Can they provide exact data-flow diagrams for cookies, localStorage, sessionStorage, form values, screenshots, files, chat, telemetry, and recordings?
  5. Does masking occur before data leaves the customer browser, or only before display to the follower?
  6. Can Surfly guarantee that no DOM/form/storage values are collected on excluded pages?
  7. How are session links, leader links, follower links, PINs, and restoration tokens generated, stored, expired, and revoked?
  8. Can the bank force view-only mode with no control transfer?
  9. Can the bank prevent all navigation outside a strict allowlist?
  10. Can the bank deploy Surfly privately or on-prem?
  11. What penetration tests cover postMessage, cookie transfer, storage transfer, iframe isolation, and agent-control abuse?
  12. What logs include URLs, form events, metadata, screenshots, filenames, chat, or customer identifiers?

If they cannot answer these with evidence, the integration should not proceed.


9. Final recommendation

Do not use this Surfly integration inside the real authenticated banking application.

A restricted deployment could be considered only if it is:

  • isolated to a non-sensitive support surface;
  • view-only;
  • no control transfer;
  • no cookie transfer;
  • no storage transfer;
  • no transactional pages;
  • no payments/beneficiaries/cards/statements/KYC/credentials;
  • no screenshots/recording/files/video unless separately approved;
  • telemetry disabled or bank-controlled;
  • strict route allowlisting;
  • server-side transaction blocking;
  • independently penetration-tested;
  • supported by unminified source, source maps, and SBOM.

The most important finding from the uploaded code is this:

Surfly has code paths to observe, serialize, transfer, and restore browser state inside the user’s session. In banking, browser session state is not just UI state. It is part of the security boundary.

That makes the standard integration unsuitable for core banking.

@charl-kruger

Copy link
Copy Markdown
Author

Third report: Surfly apiframe.js telemetry and postMessage risk

Executive summary

The uploaded apiframe.js confirms two security-relevant behaviours that are not appropriate for core banking pages:

  1. Surfly embeds Sentry telemetry and sends error/session/client-report data to a Sentry project at sentry.io, using a visible DSN and release value. The code also attaches Surfly configuration, widget key, session ID, settings, and user_data to the Sentry scope.

  2. The postMessage transport is mostly sent to SURFLY_COBRO_ORIGIN, but the Surfly protocol layer explicitly accepts params.targetOrigin === "*". That wildcard is used in the same internal messaging system that handles widget messages, cookie/storage restoration, session ending, and user messages.

The key concern is not just “there is Sentry” or “there is postMessage.” The concern is that both exist inside a co-browsing runtime that can interact with a banking session, browser storage, cookies, forms, navigation, tabs, and session control.


1. Sentry telemetry: where it goes

The uploaded apiframe.js contains a hardcoded Sentry DSN:

https://510f80906f4d429ba882cf65fdfbbca6@sentry.io/1545691

It also contains the release value:

4ed92455697daa4e4bf0aed94cffe820676bf8f7

The Sentry initialization is guarded by a replacement check, and if the DSN is present it initializes Sentry with that DSN and release. The code also installs GlobalHandlers, disables unhandled promise rejection capture in that specific global handler configuration, installs a frame-rewrite integration, and sets a Sentry tag of logger = jsapi.

Practically, events are sent to Sentry’s envelope endpoint for project 1545691, derived from the DSN. The effective destination is:

https://sentry.io/api/1545691/envelope/?sentry_key=510f80906f4d429ba882cf65fdfbbca6

The code includes Sentry transport logic that sends envelopes over fetch or XHR as POST requests, and builds envelope URLs from the DSN.

Banking meaning

This is a third-party telemetry egress path. Even if it is “only error reporting,” it can receive operational metadata from a customer’s banking session whenever Sentry captures an error, message, session update, or client report.

For banking, that means Sentry must be treated as a downstream processor/subprocessor and a potential leakage channel.


2. What Surfly attaches to Sentry

2.1 Surfly init settings

During Surfly.init(...), the code stores the user-provided settings and immediately calls Sentry setTags with:

user_settings: JSON.stringify(this._userSettings)
inside_session: this.isInsideSession

It also sets:

widget_key: n.widget_key

when a widget key is provided.

This is highly relevant. Anything passed into Surfly.init(...) can become Sentry metadata. If a bank’s integration team passes customer identifiers, CRM IDs, branch IDs, account context, risk segment, customer tier, support case details, or sensitive configuration into the Surfly settings object, that data can be attached to Sentry events.

2.2 Session ID and effective settings

When a session is created or queued, the code sets:

sessionId
settings

on the Sentry scope:

setTag("sessionId", this._sessionId)
setExtra("settings", this.settings)

This means later Sentry events can include the Surfly session ID and effective session configuration. In a banking platform, support-session IDs and session configuration are security-relevant metadata, especially if they can be correlated with customer journeys, agents, timestamps, or support cases.

2.3 User data passed to session start

Before starting a session, the code calls:

setExtra("user_data", n)

This is one of the most important findings.

If the bank passes user data such as:

customer name
email
phone number
customer ID
account number
CRM ID
case number
risk score
VIP/private-banking flag
KYC status
product holdings
branch relationship manager

then that data can be attached as Sentry extra data on subsequent events.

For a banking deployment, this should be treated as not acceptable unless Surfly can prove Sentry is disabled or the data is scrubbed before transmission.


3. What Sentry may capture automatically

The bundle includes Sentry integrations for browser breadcrumbs and HTTP context. The Sentry code instruments console, DOM, fetch, history, and XHR, and creates breadcrumbs that are attached to later Sentry events.

3.1 Error events

The global error handler builds Sentry events from browser errors, including:

message
URL / filename
line
column
stack trace
error object
mechanism: onerror
handled: false

The uploaded code shows onerror capture is active, while onunhandledrejection is disabled in Surfly’s Sentry init configuration.

3.2 URL, referrer, and user-agent

The Sentry HttpContext integration adds request context using browser location, document referrer, and navigator user-agent.

In a banking SPA, this is risky because URLs can reveal:

customer IDs
account IDs
application IDs
statement IDs
transaction references
payment-flow state
KYC workflow state
internal route names

Even relative paths can be sensitive if they include identifiers or query strings.

3.3 Console breadcrumbs

The Sentry breadcrumb integration captures console calls. The breadcrumb contains the console level, joined message, and original arguments.

If the bank’s app or the Surfly integration logs API responses, customer objects, tokens, form values, error payloads, or debug objects, those can become Sentry breadcrumbs.

3.4 DOM breadcrumbs

The DOM breadcrumb logic builds element selectors using tag name, IDs, classes, and attributes such as:

aria-label
type
name
title
alt

The code avoids keypress breadcrumbs for INPUT, TEXTAREA, and contentEditable, so it does not appear to directly capture typed characters as DOM breadcrumbs. But it can still capture sensitive element names, labels, IDs, and class names.

For banking, selectors such as these can reveal business logic:

#beneficiary-account-number
input[name="otp"]
button[title="Approve transfer"]
.card-limit-form
.kyc-document-upload

3.5 XHR and fetch breadcrumbs

The bundle instruments XHR and fetch. Breadcrumbs include:

method
URL
status code

The instrumentation also sees request bodies internally as hints, even if the breadcrumb data object itself is primarily method, URL, and status.

This matters because banking APIs often contain sensitive identifiers in paths or query parameters, even when request bodies are not included.


4. Sentry risk rating for banking

Sentry data path Risk Why
Hardcoded Sentry DSN to sentry.io High Third-party telemetry egress from banking pages
user_settings: JSON.stringify(...) Critical Can expose integration config and accidental PII
widget_key tag High Tenant/widget identifier leaves page
sessionId tag High Correlates errors to Surfly sessions
settings extra High Exposes effective Surfly configuration
user_data extra Critical Can expose customer PII if integrator passes it
URL/referrer/user-agent High Banking URLs often contain sensitive route context
Console breadcrumbs High Debug logs often leak sensitive objects
DOM breadcrumbs Medium/high Can reveal form/page semantics
XHR/fetch breadcrumbs Medium/high API URLs and status codes expose workflow details

Sentry conclusion

For banking, Surfly’s Sentry must be one of:

disabled entirely,
routed to a bank-controlled Sentry project,
subject to strict beforeSend and beforeBreadcrumb scrubbing,
or removed in a private/on-prem build.

As uploaded, the code sends telemetry to Surfly/Sentry infrastructure and attaches too much integration/session metadata to be acceptable on sensitive banking pages.


5. postMessage: restricted or wildcarded?

The answer is nuanced but important:

Browser transport layer: mostly restricted to SURFLY_COBRO_ORIGIN.
Surfly protocol layer: explicitly wildcard-tolerant through params.targetOrigin === "*".

That distinction is the core issue.


6. Browser-level postMessage target

The central message sender builds a message:

{
  sessionId: this._sessionId,
  type: e,
  params: n
}

and sends it with:

postMessage(r, u.mainWindow.SURFLY_COBRO_ORIGIN)

That means the actual browser postMessage target is not generally "*". It is sent to the Surfly co-browsing origin.

That is better than a raw:

postMessage(data, "*")

However, this is not enough for a banking-grade design because the Surfly message object contains its own routing field called params.targetOrigin.


7. The critical issue: params.targetOrigin === "*"

The internal widget-message handler accepts a message if:

t.params.targetOrigin === "*"
||
t.params.targetOrigin === u.mainWindow.location.origin

Then it dispatches the message either as a user message or as an internal widget command:

t.params.target === "user"
  ? this._emit(h.MESSAGE, t.params)
  : t.params.target === "widget" &&
    (
      od.hasOwnProperty(t.params.msg)
        ? od[t.params.msg].call(this, t)
        : console.log("Got unknown widget message", t)
    )

This is the line that should be stressed:

t.params.targetOrigin === "*"

In a banking-grade message bridge, that should not be acceptable for sensitive commands. A hardened implementation should require the exact expected origin, plus exact source-window validation, schema validation, command allowlisting, session nonce/correlation, and rejection of wildcard routing.


8. Wildcard used for cookie/storage restoration

The wildcard is not theoretical. The uploaded code uses it for internal widget messages.

The collect_web_storage handler checks cookie-transfer settings, collects localStorage and sessionStorage, optionally collects cookies, and then sends:

this._sendApiMessage("widget_message", {
  srcOrigin: u.mainWindow.location.origin,
  targetOrigin: "*",
  target: "widget",
  msg: "restore_cookies",
  cookies: o,
  storage: i
})

That is a major banking concern because the same message contains:

srcOrigin
targetOrigin: "*"
target: "widget"
msg: "restore_cookies"
cookies
storage

Even though the outer browser postMessage is sent to SURFLY_COBRO_ORIGIN, the Surfly protocol-level message says: “this widget message is valid for any target origin.”

For banking, cookie and storage transfer should not exist on sensitive pages, and wildcard protocol routing should not be accepted for commands that can move browser state.


9. Wildcard used for session ending

The session end flow also uses:

targetOrigin: "*",
target: "widget",
msg: "end_session"

when inside a Surfly session.

This is lower risk than cookie/storage restoration, but it confirms the wildcard is part of the design pattern, not a one-off.


10. Public API can also send wildcard payloads

The public sendMessage API defaults to the main window origin:

targetOrigin: n || u.mainWindow.location.origin

But because n is caller-supplied, an integrator could pass "*":

session.sendMessage(message, "*")

The code shown does not prevent that.

In a banking integration, the API should reject "*" outright for any message that crosses between bank page, Surfly iframe, proxied page, agent/follower context, or session widget.


11. Inbound origin and source checks

The code does include an inbound browser-message check. It listens for message events and first checks:

i.origin === s.SURFLY_COBRO_ORIGIN

Then it checks that the source is either the session window or the expected top-frame parent reference, depending on whether it is inside a Surfly session. Only then does it dispatch to Mi[...].

That is a positive control. It means the system is not simply accepting browser message events from arbitrary origins.

But it does not remove the concern about:

t.params.targetOrigin === "*"

because the wildcard is inside the trusted Surfly message protocol after the browser-origin check has passed.

For banking, the rule should be stricter:

A message from Surfly’s origin is not automatically safe.
The message payload also needs strict validation.
Wildcard targetOrigin should never authorize sensitive routing.

12. Why params.targetOrigin === "*" is dangerous in banking

The danger is not that every website on the internet can directly post messages into the session. The danger is that Surfly’s own privileged message bridge accepts wildcard-targeted protocol messages for actions that can involve browser/session state.

In banking, that creates several risks:

12.1 Confused-deputy risk

A trusted Surfly frame can become a deputy that accepts broadly targeted commands and routes them into the bank page/session context. If any upstream Surfly component, agent-side context, embedded widget, or proxied session frame is compromised or misbehaves, the wildcard check reduces the precision of routing controls.

12.2 State-transfer risk

The wildcard appears directly in the path that can send cookies and web storage for restoration.

For banking, browser storage and cookies are part of the security boundary. A wildcard protocol target should never be involved in moving them.

12.3 Integration misuse risk

Because sendMessage accepts a caller-provided target origin, bank developers could accidentally use "*" in custom integration code.

That mistake would be easy to miss in review because the outer postMessage still appears to be restricted to SURFLY_COBRO_ORIGIN.

12.4 Weak auditability

A banking auditor looking only for postMessage(..., "*") might conclude the code is mostly restricted. But the real issue is subtler:

postMessage(..., SURFLY_COBRO_ORIGIN)

combined with:

params.targetOrigin === "*"

That means the wildcard exists at the protocol layer, not necessarily the browser transport layer. This is exactly the kind of issue auditors can miss if they do not review the application-level message router.


13. Combined Sentry + postMessage banking risk

The Sentry and postMessage findings amplify each other.

Surfly’s runtime:

  • attaches user settings, widget key, session ID, settings, and user data to Sentry;
  • collects breadcrumbs from console, DOM, XHR, fetch, and history;
  • uses a message bridge for session/widget communication;
  • allows params.targetOrigin === "*";
  • has cookie/storage restoration paths using targetOrigin: "*";
  • sends browser-level messages to Surfly-controlled origins;
  • creates and controls session iframes.

In a bank, that is too much privilege and too many data paths for a third-party script on authenticated pages.


14. Required remediation before any banking consideration

14.1 Sentry remediation

Surfly should provide a banking build or tenant configuration where:

Sentry is disabled entirely,
or Sentry is routed to a bank-owned project,
or all Sentry events are scrubbed before transmission.

Minimum scrub requirements:

remove user_settings
remove widget_key
remove sessionId
remove settings
remove user_data
strip query strings and fragments from URLs
remove referrer
remove console arguments
remove DOM selector attributes
remove XHR/fetch URLs or reduce them to route templates
disable client reports unless approved
disable session tracking unless approved

The current code shows all the relevant scope attachment points exist.

14.2 postMessage remediation

Surfly should provide a hardened build/configuration where:

params.targetOrigin === "*" is removed,
targetOrigin: "*" is never generated,
sendMessage("*") is rejected,
incoming event.origin must equal the exact expected origin,
incoming event.source must equal the exact expected window,
every message type is schema-validated,
every command is allowlisted,
session nonce/correlation is mandatory,
cookie/storage-transfer messages are disabled or removed,
restore_cookies cannot be called on banking pages,
end_session and other widget commands require exact target origin.

The most important single remediation is:

Remove support for params.targetOrigin === "*".

Not “avoid using it.” Remove it.


15. Final banking conclusion

The uploaded code confirms that Surfly’s runtime sends telemetry to Sentry and uses a wildcard-tolerant internal message router.

The browser-level postMessage target is mostly restricted to SURFLY_COBRO_ORIGIN, which is good, but the internal Surfly router explicitly accepts:

t.params.targetOrigin === "*"

and the runtime itself sends sensitive widget messages with:

targetOrigin: "*"

including the restore_cookies path that can include cookies and web storage.

For ordinary support websites, this may be an acceptable engineering tradeoff. For a banking platform, it is not.

Recommendation: do not deploy this Surfly runtime on authenticated banking pages or transactional flows unless Surfly provides a hardened build with Sentry disabled or bank-controlled, cookie/storage transfer removed, and all wildcard params.targetOrigin === "*" handling eliminated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment