Created
May 15, 2026 08:51
-
-
Save charl-kruger/24ec3b26d28c6e9f9e3084acf3780050 to your computer and use it in GitHub Desktop.
sufly
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
| ## 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" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Third report: Surfly
apiframe.jstelemetry andpostMessageriskExecutive summary
The uploaded
apiframe.jsconfirms two security-relevant behaviours that are not appropriate for core banking pages: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, anduser_datato the Sentry scope.The
postMessagetransport is mostly sent toSURFLY_COBRO_ORIGIN, but the Surfly protocol layer explicitly acceptsparams.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.jscontains a hardcoded Sentry DSN:It also contains the release value:
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 oflogger = jsapi.Practically, events are sent to Sentry’s envelope endpoint for project
1545691, derived from the DSN. The effective destination is:The code includes Sentry transport logic that sends envelopes over
fetchor 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 SentrysetTagswith:It also sets:
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:
on the Sentry scope:
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:
This is one of the most important findings.
If the bank passes user data such as:
then that data can be attached as Sentry
extradata 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:
The uploaded code shows
onerrorcapture is active, whileonunhandledrejectionis disabled in Surfly’s Sentry init configuration.3.2 URL, referrer, and user-agent
The Sentry
HttpContextintegration adds request context using browser location, document referrer, and navigator user-agent.In a banking SPA, this is risky because URLs can reveal:
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:
The code avoids keypress breadcrumbs for
INPUT,TEXTAREA, andcontentEditable, 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:
3.5 XHR and fetch breadcrumbs
The bundle instruments XHR and fetch. Breadcrumbs include:
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.iouser_settings: JSON.stringify(...)widget_keytagsessionIdtagsettingsextrauser_dataextraSentry conclusion
For banking, Surfly’s Sentry must be one of:
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:
That distinction is the core issue.
6. Browser-level
postMessagetargetThe central message sender builds a message:
and sends it with:
That means the actual browser
postMessagetarget is not generally"*". It is sent to the Surfly co-browsing origin.That is better than a raw:
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:
Then it dispatches the message either as a user message or as an internal widget command:
This is the line that should be stressed:
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_storagehandler checks cookie-transfer settings, collectslocalStorageandsessionStorage, optionally collects cookies, and then sends:That is a major banking concern because the same message contains:
Even though the outer browser
postMessageis sent toSURFLY_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:
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
sendMessageAPI defaults to the main window origin:But because
nis caller-supplied, an integrator could pass"*":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
messageevents and first checks: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
messageevents from arbitrary origins.But it does not remove the concern about:
because the wildcard is inside the trusted Surfly message protocol after the browser-origin check has passed.
For banking, the rule should be stricter:
12. Why
params.targetOrigin === "*"is dangerous in bankingThe 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
sendMessageaccepts 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
postMessagestill appears to be restricted toSURFLY_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:combined with:
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 +
postMessagebanking riskThe Sentry and
postMessagefindings amplify each other.Surfly’s runtime:
params.targetOrigin === "*";targetOrigin: "*";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:
Minimum scrub requirements:
The current code shows all the relevant scope attachment points exist.
14.2
postMessageremediationSurfly should provide a hardened build/configuration where:
The most important single remediation is:
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
postMessagetarget is mostly restricted toSURFLY_COBRO_ORIGIN, which is good, but the internal Surfly router explicitly accepts:and the runtime itself sends sensitive widget messages with:
targetOrigin: "*"including the
restore_cookiespath 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.