Is View Rendered Source safe?

Medium risk

View Rendered Source sends the analyzed page URL, user-agent, and locale to Beam Analytics each time the extension is invoked.

Each time a user opens View Rendered Source — via toolbar click, keyboard shortcut, or context menu — the extension's popup page loads a Beam Analytics script that fires a POST request to lb1.beamanalytics.io. The payload includes up to 150 characters of the analyzed tab's URL (embedded in the page title), the browser's user-agent string, locale, and viewport width. No user opt-out control is present in the extension UI.

47Risk
Who publishes it

Peg Leg Ltd - no other listings under this identity

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Jon
Declared legal entity
Peg Leg Ltd
Registered address
71-75 Shelton Street, Covent Garden, London WC2H 9JQ, GB
Registered contact
Jonathan Hogg

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

Analyzed Page URLs Sent to Beam Analytics

MediumunwantedCWE-359Dynamic sandbox

Launching View Rendered Source opens its analysis page, sets that page's title to the tab URL, and loads Beam Analytics, which posts JSON to lb1.beamanalytics.io/api/log with the title, tab ID, language, user agent, width, and site token.

What actually happens

You did this

You launch View Rendered Source for the current tab.

The launch can come from the toolbar button, the Alt+U shortcut, or the page context menu.

The extension did this

The extension opens its analysis page and loads Beam Analytics on that page.

Before Beam sends its analytics event, the page title is set to include the analyzed tab URL.

Fields Beam Analytics can receive from the extension page

  • Analyzed page URL
    View Rendered Source: https://intranet.example.com/payroll/reports?quarter=Q2 (illustrative)

    Tells the analytics service which page you asked to inspect. URLs may include account pages, document names, search terms, or hostnames.

  • Extension page parameter
    tabID=482

    Links the analytics event to the extension's analysis tab for your current browser session.

  • Browser user agent
    Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36

    Describes your browser and operating system, adding device context to the analytics event.

  • Browser language
    en-US

    Adds your preferred browser language to the same event as the analyzed URL.

  • Window width
    1440

    Adds screen-size context that can help distinguish desktop and mobile-like browsing environments.

  • Sender network address
    203.0.113.42 (illustrative)

    The analytics endpoint can observe the network address that delivered the request.

Captured request

POSThttps://lb1.beamanalytics.io/api/log
Headers
Content-Type
application/json

The code that does this

The launch path, URL-to-title assignment, and Beam Analytics POST

Readable version

User launch opens the analysis page

background.js
function launchVRS() {	chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {					//get current tab position so we can open new tab next to it (at index + 1)		var tabPosition = tabs[0].index;		var tabID = tabs[0].id;				chrome.tabs.sendMessage(tabs[0].id, {"message": "Gimme the rendered DOM"}, function(response) {			if(response) {					var renderedObjURL = response.payload;					var renderedStorageKey = "VRS|" + tabs[0].id;					var doctypeKey = "VRSDOCTYPE|" + tabs[0].id;					var UAKey = "VRSUA|" + tabs[0].id;										//add reference in storage to blobURL of rendered page					chrome.storage.local.set({ [renderedStorageKey]: renderedObjURL });					chrome.storage.local.set({ [doctypeKey]: response.doctype });					chrome.storage.local.set({ [UAKey]: response.userAgent });					chrome.tabs.create({ url: 'viewrenderedsource.html?tabID=' + tabID, index: tabPosition + 1});			} else {				//no response from content.js -- not much we can do...			}		});	});}//user clicked Browser Action button or used keyboard shortcutchrome.action.onClicked.addListener(function (info) {	launchVRS();});// create context menu (right click menu)chrome.contextMenus.create({	id: "vrs_context_menu",	title: "View Rendered Source (Alt+U)", 	contexts:["page"]});// Listen for the context menu item clickchrome.contextMenus.onClicked.addListener(function(info, tab) {	if (info.menuItemId === "vrs_context_menu") {		launchVRS(); 	}});// Listen for the keyboard shortcutchrome.commands.onCommand.addListener(function(command) {	if (command === "_execute_action") {		launchVRS();	}});

The selected tab URL is copied into the document title

viewrenderedsource.js
chrome.tabs.get(parseInt(tabID), function (tab) {		//for some reason there's no way to query if a tab still exists so have to look for error		if (chrome.runtime.lastError) {			alert('Error: Original tab has been closed. Reload original tab and launch extension again.');			return		}		//get URL of tab that opened us so we can fetch raw source		rawURL = tab.url;		d.title = 'View Rendered Source: ' + rawURL.substring(0, 150);		currentURL.innerHTML = 'URL: ' + rawURL;		currentURLHref.href = rawURL;		var doctypeKey = "VRSDOCTYPE|" + tabID;		var userAgentKey = "VRSUA|" + tabID;		var renderedDOMKey = "VRS|" + tabID;		chrome.storage.local.get(doctypeKey, function(result) {			doctype = result[doctypeKey];		});		chrome.storage.local.get(userAgentKey, function(result) {			userAgent = result[userAgentKey];			//Detect if mobile			if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(userAgent)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(userAgent.substr(0,4))) {								// fetchTypeRendered.innerHTML = 'Chrome, Mobile <div class="tooltip" style="width:15px; height:15px; font-size:14px; font-weight:800">i <span class="tooltiptext">To render as a mobile device, <a href="https://developers.google.com/web/tools/chrome-devtools/device-mode/emulate-mobile-viewports" target="_blank">change the device in Chrome DevTools</a> and re-launch extension</span></div>';				// fetchAsMobile.checked = true;			} else {				// fetchTypeRendered.innerHTML = 'Chrome, Desktop <div class="tooltip" style="width:15px; height:15px; font-size:14px; font-weight:800">i <span class="tooltiptext">To render as a mobile device, <a href="https://developers.google.com/web/tools/chrome-devtools/device-mode/emulate-mobile-viewports" target="_blank">change the device in Chrome DevTools</a> and re-launch extension</span></div>';			}					});		//now get Blob URL of rendered source 		chrome.storage.local.get(renderedDOMKey, function(result) {			var renderedBlobURL = result[renderedDOMKey];			fetchSource(renderedBlobURL, 'rendered');			fetchSource(rawURL, 'raw');		});	});

Beam constructs and sends the JSON analytics request

beam.min.js
! function() {  var e = document.currentScript.getAttribute("data-api") || "https://lb1.beamanalytics.io/api/log";  window.beam = function(t) {    if (!/^localhost$|^127(\.[0-9]+){0,2}\.[0-9]+$|^\[::1?\]$/.test(location.hostname) && "file:" !== location.protocol && !window.__nightmare && !window.navigator.webdriver && !window.Cypress) {      var a = {},        n = navigator.languages && navigator.languages.length ? navigator.languages[0] : navigator.userLanguage || navigator.language || navigator.browserLanguage || "en",        r = Object.fromEntries(new URLSearchParams(window.location.search).entries()),        i = "";      0 > document.referrer.indexOf(location.origin) && (i = document.referrer);      var s = window.beam_token || document.currentScript.getAttribute("data-token");      if (window.beam_token = window.beam_token || s, s) {        a.width = window.innerWidth, a.hostname = location.hostname, a.pathname = t || location.pathname, a.referrer = i, a.user_agent = window.navigator.userAgent, a.locale = n, a.params = r, a.token = s, a.title = document.title, a.hash = location.hash;        var o = new XMLHttpRequest;        o.open("POST", e, !0), o.setRequestHeader("Content-Type", "application/json"), o.send(JSON.stringify(a))      }    }  };  var t = function(e) {    beam()  };  window.addEventListener("hashchange", t);  var a = window.history;  if (a.pushState) {    var n = a.pushState;    a.pushState = function() {      n.apply(this, arguments), beam()    }, window.addEventListener("popstate", t)  }  beam()}();

External analytics destination

    • lb1.beamanalytics.io

    Beam Analytics logging endpoint that receives the extension page analytics event.

What it can do

Permissions this extension asks for, as declared in version 4.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Read and change your data on every site you visit

    http://*/*

  • Read and change your data on every secure site you visit

    https://*/*

  • Add items to the right-click menu

    contextMenus

  • Store data in your browser

    storage

Where it sends data

Destinations our analysis observed View Rendered Source contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • lb1.beamanalytics.io

    View Rendered Source sends data to lb1.beamanalytics.io. No other extension we have analyzed sends data here.

Updated 30 September 2026ejgngohbdedoabanmclafpkoogegdpob