Skip to main content

Security functionalities

Surfly has some security measures built in. Below you will find an overview of the different security functionalities you can add to your Surfly integration.

To get a feel of the different options, check out our

demo page

.

Reminder

You can implement these settings through the Surfly dashboard or the Javascript API. When you implement settings through the JS API, these will take priority over the settings in the Surfly dashboard.

Blocklist / Allowlist

By adding URLs to the blocklist and/or allowlist, you can restrict your users from navigating to other websites.

OptionDefaultDescription
blocklist“[]”Restrict access to the specific resources. Example: [{"pattern": ".*example\\.com.*"}]
allowlist“[]”Allow access only to the specific resources. Example: [{"pattern": ".*example\\.com.*"}]

Privacy

Privacy options allow to hide information which is exposed on a client side, i.e. by a browser.

OptionDefaultDescription
privacy_user_agentfalseMask data about the user's browser located in the Navigator object. This helps to protect against some malicious websites which abuse this feature to track users.
privacy_canvasfalsePrevent sites from reading the result of canvas drawing operations. This helps to protect against some malicious websites which identify users by drawing shapes and text on a user's webpage and noting the minor differences in the way they are rendered. Note: this can cause problems on sites which use Canvas elements legitimately.
privacy_datefalsePrevent sites from checking the local time on the user's computer or phone. This helps to protect against some malicious websites which abuse this information to uniquely identify users. Note: this can cause problems on sites which use Date legitimately.
privacy_devicesfalsePrevent sites from reading detailed information about a client's devices, such as CPUs, memory, displays, battery and others. This helps to protect against some malicious websites which abuse this information to uniquely identify users.
privacy_embedfalseWhen this option is enabled, the "type" attribute of any of these elements will be changed to "text/surfly-blocked" if it is "application/x-shockwave-flash" or starts with "application/java-" or "application/x-java-". Additionally, the following attributes will be reset:
elementattributes
<embed>"src"
<object>"code", "codebase", "data"
<param>"value"
<applet>"archive", "code"
privacy_ip_addressfalseHide a user's public ip address. This helps preserve user anonymity. If set to true, then the surfly-forwarded HTTP header is not added to requests, and WebRTC is disabled. Note: this can cause problems on sites which use WebRTC legitimately, like videochat applications.
privacy_beaconfalsePrevents sendBeacon POST request from being sent. This helps to protect against some malicious websites which abuse this feature to track users. Note: this can cause problems on sites which use this feature legitimately.
privacy_http_headersfalseWhen this option is enabled, the following headers will be set to a generic value:
  • Accept
  • Accept-Encoding
  • Accept-Language
  • User-Agent
  • Sec-CH-UA-*
The following headers will be sent unchanged:
  • Host
  • Content-Length
  • Cookie
  • Referer
  • Origin
  • Content-Type
  • Cache-Control
  • Range
Finally, any other headers will be removed from the request.

Open URL externally

Sometimes you may want to open a page in a new tab outside of the session. In that case the new tab will not be synchronized with other participants and only current user will see the content. If you want to make a link open in the new browser tab, you can just add the surfly-open-externally attribute to the <a> tag with a link.

<a href="http://example.com" surfly-open-externally>Open in the new tab</a>

Field masking

With field masking, you can mask form fields for followers, so that they can't see the leader's input. By default, Surfly already masks password fields. If you want to mask other fields, you just have to add the surfly_private attribute to fields containing sensitive information.

Please note that there is an exception to field masking when masked fields are present in an HTML form with method="get" or if the field's value is used in the URL. In such cases, although the fields are masked and not visible to followers in the user interface, the values entered in those fields can still be tracked through Audit Logs (relocate_start event) and may be exposed through browser network activity.

<span>Email address</span> <input type="text" size="20" surfly_private />

Element masking

With element masking, you can hide whole HTML elements from followers. All you need to do is add the CSS selectors of these elements to option "hide_element_by_selector" and within a Surfly session followers will no longer be able to see the elements, but instead, grey censored areas.

Please note that there is an exception to element masking when masked elements are present in an HTML form with method="get" or if the field's value is used in the URL. In such cases, although the elements are masked and not visible to followers in the user interface, the values entered in those fields can still be tracked through Audit Logs (relocate_start event) and may be exposed through browser network activity.

You can set this option through the Surfly dashboard or the Javascript API.

On this page you can find a demo implementation of the element masking feature. In this example we've added the CSS selectors of the form and the elements inside which we'd like to hide to option "hide_element_by_selector"

<script>
(function (s, u, r, f, l, y) {
s[f] = s[f] || {
init: function () {
s[f].q = arguments;
},
};
l = u.createElement(r);
y = u.getElementsByTagName(r)[0];
l.async = 1;
l.src = 'https://surfly.com/surfly.js';
y.parentNode.insertBefore(l, y);
})(window, document, 'script', 'Surfly');

var settings = {
// Here you can add all the session options
widget_key: '**your widget key here**',
hide_element_by_selector: '.theOnlyForm, .theOnlyForm *',
};

Surfly.init(settings, function (init) {
if (init.success && !Surfly.isInsideSession) {
Surfly.session()
// Show a follower link
.on('session_started', function (session, event) {
document.querySelector('#followerLink').href = session.followerLink;
})
// Start a Surfly session as a leader
.startLeader();
}
});
</script>

Customizing masked elements

We add a special CSS class surfly-masked to all masked elements. You can change the look of masked elements by adding CSS rules for this class.

Control dependent appearance

During your Surfly co-browsing session it's possible to pass control of the page between participants. It's also possible to change the behaviour of your website while co-browsing, depending on who has control. We can use the example of filling out a payment form as a proof of concept to demonstrate how this might be useful...

Let's say you are co-browsing with a customer and the agent has taken over control, but you don't want them to be able to submit forms or orders. With the .on() method we can disable the submit button when the agent is given control, only allowing the customer to confirm the payment:

<script>
(function (s, u, r, f, l, y) {
s[f] = s[f] || {
init: function () {
s[f].q = arguments;
},
};
l = u.createElement(r);
y = u.getElementsByTagName(r)[0];
l.async = 1;
l.src = 'https://surfly.com/surfly.js';
y.parentNode.insertBefore(l, y);
})(window, document, 'script', 'Surfly');

Surfly.init({ widget_key: '**your widget key here**' }, function (init) {
if (init.success) {
// display the default Surfly button
Surfly.button();

// ADD THE PART BELOW TO THE BASIC SURFLY SCRIPT
var sess;
if (!Surfly.currentSession) {
sess = Surfly.session();
} else {
sess = Surfly.currentSession;
}
sess.on('tab_control', function (session, event) {
var element = document.getElementById('order_button');
// Specify that you DON'T want the limitations to apply to the leader
if (event.leaderIndex === event.controlIndex) {
element.disabled = false;
element.style.backgroundColor = '#87cefa';
}
// Apply the limitations to everyone else when they are in control
else {
element.disabled = true;
element.style.backgroundColor = '#e6fff2';
}
});
// UNTIL HERE
}
});
</script>

Audit logs

A highly sought after feature by companies adhering to the strictest of audit and compliance policies, is Audit Logs. Button clicks, text inputs, control-transfers, documents shared, websites/web-pages visited, and whatnot, you can log it all.

Enabling Audit Logs

You can enable logging through the audit_logs_enabled: true option, simply through your dashboard or using our JavaScript or REST APIs.

Apart from this, you will need to specify the S3 bucket details: s3_audit_log_bucket:,s3_access_key:&s3_access_secret:`. You can also choose any other cloud bucket that is Amazon S3 standards compatible.

Instead of a cloud bucket you can also choose to store these at your own location by adding a Webhook with Type: session.log pointing to your endpoint from the Surfly dashboard.

Once done, at the end of each co-browsing session a JSON format file will be generated and stored at the location of your choice. Hence, you are in control of your data at all times.

Standard Information

As standard, the Audit logs contain below information for each co-browsing session:

  • Session start info
    • MetaData added by user/integration
    • IP address
    • Start URL of session
    • Start time
    • Name (if known)
  • Participant joined the session
    • IP address
    • Client Index
    • Time
    • Name (if known)
  • Control transfer to participant
    • Request granted Or control given/taken
  • User opened new tab
  • User switched an active tab
  • User shared a document
    • Full document name
  • User annotated a document
    • Annotation type
    • Author
  • User downloaded a file
    • File name
  • User navigated to a URL
    • Full URL of the page navigated to
  • User changed a Form
    • Changes made to any input value(s) of a page by a user
  • Form submission by a user
  • User clicks
  • Session ended by User (time and duration)

These details meet the standards of scrutiny required by most enterprises. After all, these are as good as screen-recordings of a session. Even better when we also consider the time-stamped (UTC) documentation of each event and negligible data storage costs compared to the other. Furthermore, another major difference is that audit logs filter out potentially sensitive data.

Customized Logs

We do understand the slight possibility where you may still need any other specific data in your session-logs. Using our JavaScript API, you can easily customize your Audit logs by supplementing any further information to them. For example, a simple line of code like below might help you to add specific info to the Audit logs, perhaps about products placed into the shopping cart.

Session.log('Added item: ' + product_ID + ' in the basket with value ' + product_value);

Check out our Javascript API reference for more information.

Sensitive Information

No sensitive data is going to be stored in the logs. Any sensitive information, regardless of it being part of a web-page (ex.-Banking account details, Patient health record) or inputs made by a user (ex.-Login credentials, Address) can easily be masked using our Field/Element Masking. This will then not only be masked from a Follower/Agent in the session, but also in the Audit logs.

Example

Below is a simple example of a session and its corresponding Audit log. The video is from the Agent's screen. The session is initiated by a 'Customer', who is unable to locate the 'Log in' button on a page. After the 'Agent' joins the session, Customer transfers control to the Agent, who then clicks the Login button. After this, Agent transfers the control back to the Customer to fill in the Login details. You can see the 'Username' is masked from the Agent's screen using “Element Masking”. Customer enters the login details, submits them and then ends the session. The details are neither seen by Agent, nor do they go in the Audit logs.

Below you can see the details captured in the Audit log of this session:

{
"session_logs": [
{
"type": "session_created",
"details": {
"ip": "2a02:a456:dd7e:1:15f7:ff40:c565:bee2",
"created_from": "widget",
"meta": {},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"start_url": "https://en.wikipedia.org/wiki/Main_Page"
},
"time": "2020-01-20T14:35:40.493861"
},
{
"type": "leader_joined",
"details": {
"client_index": 0,
"ip": "2a02:a456:dd7e:1:15f7:ff40:c565:bee2",
"username": "Customer",
"email": "ashish@surfly.com",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:35:41.247252879"
},
{
"type": "control_gained",
"details": {
"username": "Customer"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:35:41.312029362"
},
{
"type": "relocate_start",
"details": {
"url": "https://en.wikipedia.org/wiki/Main_Page"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:35:42.816457965"
},
{
"type": "follower_joined",
"details": {
"client_index": 1,
"username": "Agent",
"email": "agent@company.com",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:35:55.812508071"
},
{
"type": "control_switch",
"details": {
"client_index": 1,
"controller": "follower"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:01.589625694"
},
{
"type": "control_gained",
"details": {
"username": "Agent"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:01.689092429"
},
{
"type": "click",
"details": {
"xpath": "/html/body/div[5]/div/div/ul/li[5]/a",
"attributes": {
"href": "/w/index.php?title=Special:UserLogin&returnto=Main+Page"
},
"node_name": "a"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:06.028908114"
},
{
"type": "relocate_start",
"details": {
"url": "https://en.wikipedia.org/w/index.php?title=Special:UserLogin&returnto=Main+Page"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:06.864622806"
},
{
"type": "control_switch",
"details": {
"client_index": 0,
"controller": "leader"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:09.081672476"
},
{
"type": "control_gained",
"details": {
"username": "Customer"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:09.081954447"
},
{
"type": "click",
"details": {
"is_masked": true,
"node_name": "input"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:11.419794341"
},
{
"type": "click",
"details": {
"xpath": "//*[@id=\"wpPassword1\"]",
"attributes": {
"id": "wpPassword1",
"class": "loginPassword mw-ui-input",
"name": "wpPassword"
},
"node_name": "input"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:12.737161527"
},
{
"type": "input_change",
"details": {
"xpath": "//*[@id=\"wpPassword1\"]",
"attributes": {
"id": "wpPassword1",
"class": "loginPassword mw-ui-input",
"name": "wpPassword"
},
"parent_form_attributes": {
"action": "/w/index.php?title=Special:UserLogin&returnto=Main+Page",
"method": "post",
"class": "mw-htmlform mw-ui-vform mw-ui-container",
"name": "userlogin"
},
"value": "XXXX",
"node_name": "input"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:12.88054532"
},
{
"type": "input_change",
"details": {
"xpath": "//*[@id=\"wpPassword1\"]",
"attributes": {
"id": "wpPassword1",
"class": "loginPassword mw-ui-input",
"name": "wpPassword"
},
"parent_form_attributes": {
"action": "/w/index.php?title=Special:UserLogin&returnto=Main+Page",
"method": "post",
"class": "mw-htmlform mw-ui-vform mw-ui-container",
"name": "userlogin"
},
"value": "XXXX",
"node_name": "input"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:13.123398077"
},
{
"type": "submit",
"details": {
"form_data": {
"wpFromhttp": "1",
"force": "",
"wpLoginToken": "882bed3be7480480577f044ed2040d3b5e25bad6+\\",
"wpPassword": "XXXX",
"wpRemember": "1",
"title": "Special: UserLogin",
"wploginattempt": "Login",
"wpName": "XXXX",
"authAction": "login",
"wpEditToken": "+\\",
"wpForceHttps": "1"
},
"attributes": {
"action": "/w/index.php?title=Special: UserLogin&returnto=Main+Page",
"method": "post",
"class": "mw-htmlformmw-ui-vformmw-ui-container",
"name": "userlogin"
},
"xpath": "/html/body/div[3]/div[3]/div[3]/div/div[2]/form",
"node_name": "form"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14: 36: 14.173353896"
},
{
"type": "click",
"details": {
"xpath": "//*[@id=\"wpLoginAttempt\"]",
"attributes": {
"id": "wpLoginAttempt",
"class": "mw-htmlform-submit mw-ui-button mw-ui-primary mw-ui-progressive",
"name": "wploginattempt"
},
"node_name": "button"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:14.173554662"
},
{
"type": "relocate_start",
"details": {
"url": "https://en.wikipedia.org/w/index.php?title=Special:UserLogin&returnto=Main+Page"
},
"session_id": "fwoSaTzY9ZtqT5qqsXpMDFxUyg",
"time": "2020-01-20T14:36:15.518458391"
},
{
"type": "session_end",
"details": {
"duration": "0:00:38.553785"
},
"time": "2020-01-20T14:36:19.048121"
}
]
}