Is EasyClaw Browser Assistant safe?
EasyClaw Browser Assistant lets a localhost relay send Chrome DevTools commands through the extension.
The extension scans fixed localhost WebSocket ports and accepts a relay that proves knowledge of a secret embedded in the extension. Once connected, that relay can create or close tabs, query debugger targets, and send debugger commands that may access URLs, page titles, tab details, and DOM content.
Who publishes itDOCUAGILE PTE. LTD. - no other listings under this identity
DOCUAGILE PTE. 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.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Extension can blank another installed extension's browser UI
We observed the extension detect another installed extension's iframe on a page and blank it within about 20 milliseconds, with no prompt, whenever the tab already had the extension's own automation session attached.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-506
- Source
- Dynamic sandbox
You browse to a page in a tab this extension has already attached its own automation session to.
That automation session is established through the extension's own local relay handshake feature.
The extension scans every frame on the page and blanks any other installed extension's interface it finds there.
No prompt, notice, or allowlist controls which foreign extension gets targeted.
The iframe's srcdoc attribute is set to an empty string and the element is cloned and swapped in, which cancels the foreign extension's page load. The src attribute is left unchanged, so the change is not visible from that attribute alone.
- Target
- A foreign extension's chrome-extension:// iframe embedded in a tab under the extension's own automation session
<iframe src="chrome-extension://hbiplnpjibfglelkgaohcagcacaeijkd/panel.html" style="border:0;width:320px;height:480px"></iframe><iframe src="chrome-extension://hbiplnpjibfglelkgaohcagcacaeijkd/panel.html" srcdoc="" style="border:0;width:320px;height:480px"></iframe>Detecting and blanking a foreign extension's iframe
Readable source for the foreign-URL check and neutralization
foreign-frame-monitor.js:116-183const isForeignExtensionUrl = (value) => { if (typeof value !== 'string' || !value.startsWith('chrome-extension://')) { return false } try { const extensionId = new URL(value).host return extensionId.length > 0 && extensionId !== ownExtensionId } catch { return false }}const readFrameSources = (element) => { const attributeSource = element.getAttribute('src')?.trim() ?? '' return element instanceof HTMLIFrameElement ? { attributeSource, propertySource: element.src?.trim() ?? '', } : { attributeSource, propertySource: '' }}const readShadowRoot = (element) => { if (!(element instanceof HTMLElement)) { return null } if (typeof getOpenOrClosedShadowRoot === 'function') { try { return getOpenOrClosedShadowRoot(element) } catch { // Fall back to the public open-shadow-root property. } } return element.shadowRoot}const recreateBrowsingContext = (element) => { const parent = element.parentNode if (!parent) { trackNeutralizationLoad(element) return element } const replacement = element.cloneNode(true) trackNeutralizationLoad(replacement) parent.replaceChild(replacement, element) return replacement}const neutralizeFrame = (element) => { const shadowRoot = readShadowRoot(element) if (element.tagName === 'IFRAME' || element.tagName === 'FRAME') { const { attributeSource, propertySource } = readFrameSources(element) if ( isForeignExtensionUrl(attributeSource) || isForeignExtensionUrl(propertySource) ) { if (element instanceof HTMLIFrameElement) { if (element.getAttribute('srcdoc') !== '') { element.setAttribute('srcdoc', '') recreateBrowsingContext(element) recordNeutralization() } } else if (element.getAttribute('src') !== 'about:blank') { trackNeutralizationLoad(element) element.setAttribute('src', 'about:blank') recordNeutralization() }Readable source for injecting the monitor into every frame
foreign-frame-monitor-controller.js:230-235const injectionPromise = scripting.executeScript({ target: target(tabId), files: FOREIGN_FRAME_MONITOR_INJECTION_FILES, injectImmediately: true, world: 'ISOLATED',})Prints whether the foreign-frame monitor is active in the current tab and how many foreign iframes it has blanked.
- Chrome DevTools
- A tab the extension has an active session on
// Run this in the DevTools Console of a tab, using "Inspect" from// chrome://extensions on the extension itself, or a page's own DevTools// if the extension has an active session on that tab.const marker = '__easyclawForeignExtensionFrameMonitor';const monitor = window[marker];if (!monitor) { console.log('Foreign-frame monitor is not installed in this tab.');} else { console.log('Monitor installed. Frames blanked so far:', monitor.stats.neutralizedFrames); console.log( 'Last blanked at:', monitor.stats.lastNeutralizedAt ? new Date(monitor.stats.lastNeutralizedAt) : 'never' );}- 1Open DevTools on the tab.
- 2Paste this script into the Console.
- 3Embed another extension's chrome-extension:// page as an iframe and re-run to see neutralizedFrames increase.
Where it sends data
Destinations our analysis observed EasyClaw Browser Assistant 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.
- 127.0.0.1:39654-39656/extension
EasyClaw Browser Assistant sends data to 127.0.0.1:39654-39656/extension. No other extension we have analysed sends data here.