Is Prompt Generator safe?
Prompt Generator lets vocabulary.live read active-tab DOM, localStorage, tab metadata, selected text, and screenshots.
Its popup embeds a vocabulary.live iframe that can ask the extension for the current tab's URL and title, selected text, page text or HTML, localStorage values, and a visible-tab screenshot. The extension can also open a sidebar or modal for that remote iframe and return similar active-page data through postMessage.
Who publishes itPrius Lab - 3 other listings from the same operator, 2 of them carrying a finding
Prius Lab - 3 other listings from the same operator, 2 of them carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
3 other listings published from this account, 28k+ users between them. 2 of them carry a finding.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Remote prompt page can collect active-tab content
Opening Prompt Generator loads a vocabulary.live page inside the extension UI.
Messages from that page can ask the extension to read the active tab's URL, title, selected text, element text/HTML, site local-storage values, and a screenshot.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You open or use the Prompt Generator extension UI.
That action loads the remote prompt page inside the extension popup or sidebar.
The extension accepts commands from that page and can return active-tab content to it.
The available commands include tab details, selected text, element text, element HTML, local storage, and screenshots.
The iframe navigation loads the remote prompt-generator page into the extension UI.
- Active page URLhttps://intranet.example.com/projects/alpha?ticket=INC-4831
Lets the remote page know which site and exact page you have open.
- Page titleQuarterly Roadmap - Internal Wiki
Adds readable context about the page you are viewing.
- Selected textCustomer renewal terms for account 78429
Can reveal text you highlighted on the active page.
- Element text or HTML<section id="account-summary">Balance due: $4,812.17</section>
Can reveal page content matched by a selector chosen by the remote page.
- Site local-storage valueworkspace_state={"tenant":"acme","status":"active"}
Can reveal data the active website saved in your browser for a named key.
- Visible-tab screenshotdata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfq
Can capture what is visible in the current browser tab as an image.
The popup loads vocabulary.live and treats its messages as commands
Remote prompt URL used by the popup
popup.js// 2024 Prius Lab'use strict';var appURL = (new URL("https://vocabulary.live/prompt-generator"));Popup iframe loader and response channel
files/popupModules.jsfunction load_iframe() { var iframe = document.createElement('iframe'); iframe.id = "ExtensioniFrame"; iframe.src = appURL; iframe.style.overflow = "hidden"; iframe.style.cssText = 'width:100%;height:100%;'; // iframe.style.display = 'none'; document.body.appendChild(iframe); // Hide loader and show the iframe when it is fully loaded var loadingAnimation = document.getElementById('loading-animation'); iframe.addEventListener('load', function () { loadingAnimation.style.display = 'none'; iframe.style.display = 'block'; });}//*************************************************************************************************/// Function to post a message to the iframefunction postMessageToPlugin(msg) { const iframe = document.getElementById("ExtensioniFrame"); const iframeWindow = (iframe.contentWindow || iframe.contentDocument); iframeWindow.postMessage(JSON.stringify(msg), "*");}Origin check and command dispatcher for iframe messages
files/popupModules.js// Event listener function to handle messages from the bubble pluginfunction handleMessage(event) { // Check if the event origin matches the allowed origin if (event.origin !== "https://" + appURL.hostname) { console.log("Chrome Extension: postMessage's targetOrigin not allowed, STOP."); return; } // Ignore the event if no data is present if (!event.data) { return; } // Parse the received message const reqMessage = JSON.parse(event.data); // Define the available message actions const messageActions = { tabInfo: () => returnTabInfo(), selectedText: () => returnSelectedText(), elementText: () => getElementText(reqMessage.selector), elementHTML: () => getElementHTML(reqMessage.selector), showAlert: () => showAlert(reqMessage.alertText), openNewTab: () => openNewTab(reqMessage.url), openIframeModal: () => openIframeModal(reqMessage.url), openIframeSidebar: () => openIframeSidebar(reqMessage.url, reqMessage.width), closePopup: () => window.close(), copyToClipboard: () => copyToClipboard(reqMessage.text), injectJS: () => injectJS(reqMessage.filename), writeToLocalStorage: () => reqMessage.context === "extension" ? writeToLocalStorage(reqMessage.key, reqMessage.value) : writeToLocalStorageInTab(reqMessage.key, reqMessage.value), readFromLocalStorage: () => reqMessage.context === "extension" ? readFromLocalStorage(reqMessage.key) : readFromLocalStorageInTab(reqMessage.key), deleteFromLocalStorage: () => reqMessage.context === "extension" ? deleteFromLocalStorage(reqMessage.key) : deleteFromLocalStorageInTab(reqMessage.key), clearLocalStorage: () => reqMessage.context === "extension" ? clearLocalStorage() : clearLocalStorageInTab(), setBadgeTextAndColor: () => setBadgeTextAndColor(reqMessage.text, reqMessage.color), setIcon: () => setIcon(reqMessage.path), fillInput: () => populateInputField(reqMessage.selector, reqMessage.value), clickElement: () => clickOnElement(reqMessage.selector), captureScreenshot: () => captureScreenshot() }; // Execute the requested action if it exists in the messageActions object if (messageActions.hasOwnProperty(reqMessage.requestType)) { messageActions[reqMessage.requestType](); }}The popup command handlers read active-tab data and post results back
Active-tab script execution helper
files/popupModules.js// Helper function to query the current active tabfunction queryActiveTab(callback) { chrome.tabs.query({ currentWindow: true, active: true }, callback);}// Helper function to execute a script in the current active tabfunction executeScriptInActiveTab(func, args, callback) { queryActiveTab(([tab]) => { chrome.scripting.executeScript({ target: { tabId: tab.id }, func: func, args: args, }, callback); });}Active-tab URL and title response
files/popupModules.js// Function: return tab infofunction returnTabInfo() { queryActiveTab((tabs) => { var msg = { returnType: "tabInfo", url: tabs[0].url, title: tabs[0].title, headers: false, ip: false, dns: false, cookies: false }; postMessageToPlugin(msg); });}Selected text response
files/popupModules.js// Function: return selected textfunction returnSelectedText() { executeScriptInActiveTab(getSelection, null, (selection) => { if (selection) { var msg = { returnType: "textSelection", text: selection[0].result }; postMessageToPlugin(msg); } });}// Function: get selected text (injection function)function getSelection() { return window.getSelection().toString();}Element text and HTML responses
files/popupModules.js// Function: get the text of an element from the DOM of current tabfunction getElementText(selector) { executeScriptInActiveTab(getElementTextFromDOM, [selector], (result) => { if (result) { var msg = { returnType: "getElementText", selector: selector, text: result[0].result }; postMessageToPlugin(msg); } });}// Function: get element text (injection function)function getElementTextFromDOM(selector) { var element = document.querySelector(selector); return element ? element.innerText : "";}// Function: get the HTML of an element from the DOM of current tabfunction getElementHTML(selector) { executeScriptInActiveTab(getElementHTMLFromDOM, [selector], (result) => { if (result) { var msg = { returnType: "getElementHTML", selector: selector, HTML: result[0].result }; postMessageToPlugin(msg); } });}// Function: get element HTML (injection function)function getElementHTMLFromDOM(selector) { var element = document.querySelector(selector); return element ? element.outerHTML : "";}Visible-tab screenshot response
files/popupModules.js// Function: captureScreenshotfunction captureScreenshot() { chrome.tabs.captureVisibleTab(null, { format: "png" }, function (dataUrl) { var msg = { returnType: "screenshot", dataUrl: dataUrl }; postMessageToPlugin(msg); });}Active-tab localStorage response
files/popupModules.js// Function: read from local storage in tab contextfunction readFromLocalStorageInTab(key) { executeScriptInActiveTab(readFromLocalStorageInTabContext, [key], (result) => { if (result) { var msg = { returnType: "localVariable", key: key, value: result[0].result }; postMessageToPlugin(msg); } });}// Function: read from local storage in tab context (injection function)function readFromLocalStorageInTabContext(key) { return window.localStorage.getItem(key).toString();}The sidebar path injects a remote iframe into the active tab
Background message handlers for sidebar and modal injection
background.jsconst messageHandlers = { openIframeSidebar: (request) => openIframeSidebar(request.url, request.width), openIframeModal: (request) => openIframeModal(request.url),};const UNINSTALL_URL = "https://vocabulary.live/unistall-page/prompt-generator"; chrome.runtime.setUninstallURL(UNINSTALL_URL);chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { const handler = messageHandlers[request.type]; if (handler) { handler(request); } else { console.log("Chrome Extension: unknown request type"); }});Background functions that inject modal.js into the active tab
background.jsfunction openIframeSidebar(url, width) { chrome.tabs.query({ currentWindow: true, active: true }).then(([tab]) => { functions.injectCSSAndExecuteScript(tab.id, ["files/modal.css"], ["files/modal.js"], () => { chrome.scripting.executeScript({ target: { tabId: tab.id }, func: openSidebar, args: [url, width], }); }); isPopupVisible ? chrome.runtime.sendMessage({ type: 'closePopup' }) : null; });}function openIframeModal(url) { chrome.tabs.query({ currentWindow: true, active: true }).then(([tab]) => { functions.injectCSSAndExecuteScript(tab.id, ["files/modal.css"], ["files/modal.js"], () => { chrome.scripting.executeScript({ target: { tabId: tab.id }, func: openModal, args: [url], }); }); isPopupVisible ? chrome.runtime.sendMessage({ type: 'closePopup' }) : null; });}Injected sidebar iframe creation
background.jsfunction openSidebar(url, width) { var existingSidebar = document.getElementById("chrls-sidebar-modal"); if (!existingSidebar) { var chrlsSidebarModal = document.createElement("div"); chrlsSidebarModal.id = "chrls-sidebar-modal"; chrlsSidebarModal.style.width = width; chrlsSidebarModal.style.right = "-" + width; var chrlsSidebarContent = document.createElement("iframe"); chrlsSidebarContent.id = "chrls-iframe"; chrlsSidebarContent.src = url; chrlsSidebarModal.appendChild(chrlsSidebarContent); var spinnerContainer = document.createElement("div"); spinnerContainer.className = "loading-animation"; spinnerContainer.id = "chrls-loading-animation"; var spinner = document.createElement("div"); spinner.className = "chrls-spinner"; spinnerContainer.appendChild(spinner); chrlsSidebarModal.appendChild(spinnerContainer); document.body.appendChild(chrlsSidebarModal); var loadingAnimation = document.getElementById('chrls-loading-animation'); var iframe = document.getElementById('chrls-iframe'); iframe.addEventListener('load', function () { loadingAnimation.style.display = 'none'; iframe.style.display = 'block'; }); let isResizing = false; document.addEventListener('mousedown', (e) => { const sidebar = document.getElementById("chrls-sidebar-modal"); if (e.target === sidebar && e.clientX < sidebar.getBoundingClientRect().left + 10) { isResizing = true; document.addEventListener('mousemove', handleResize); document.body.style.pointerEvents = 'none'; } }); document.addEventListener('mouseup', () => { if (isResizing) { isResizing = false; document.removeEventListener('mousemove', handleResize); document.body.style.pointerEvents = ''; } }); function handleResize(e) { const sidebar = document.getElementById("chrls-sidebar-modal"); const sidebarRight = window.innerWidth - e.clientX; sidebar.style.width = `${sidebarRight}px`; } } var chrlsSidebarModal = document.getElementById("chrls-sidebar-modal"); chrlsSidebarModal.style.right = "-" + chrlsSidebarModal.clientWidth + "px"; setTimeout(function () { chrlsSidebarModal.style.transform = "translateX(-" + chrlsSidebarModal.clientWidth + "px)"; } , 200);};Injected modal command dispatcher
files/modal.jsfunction handleMessage(event) { const chrlsIFrame = document.getElementById("chrls-iframe"); const iframeUrl = chrlsIFrame.src; // Check if the origin of the event matches the iframe's URL if (iframeUrl.indexOf(event.origin) === -1) { console.log("Chrome Extension: postMessage's targetOrigin not allowed, STOP."); console.log("Chrome Extension: event.origin: " + event.origin); console.log("Chrome Extension: url: " + iframeUrl); return; } // Ignore the event if no data is present if (!event.data) { return; } // Parse the received message const reqMessage = JSON.parse(event.data); // Define the available message actions const messageActions = { copyToClipboard: () => copyToClipboard(reqMessage.text), tabInfo: () => returnTabInfo(), elementText: () => getElementText(reqMessage.selector), elementHTML: () => getElementHTML(reqMessage.selector), showAlert: () => showAlert(reqMessage.alertText), openNewTab: () => openNewTab(reqMessage.url), writeToLocalStorage: () => writeToLocalStorage(reqMessage.key, reqMessage.value), readFromLocalStorage: () => readFromLocalStorage(reqMessage.key), deleteFromLocalStorage: () => deleteFromLocalStorage(reqMessage.key), clearLocalStorage: () => clearLocalStorage(), fillInput: () => populateInputField(reqMessage.selector, reqMessage.value), clickElement: () => clickOnElement(reqMessage.selector), closePopup: () => closeModal() }; // Execute the requested action if it exists in the messageActions object if (messageActions.hasOwnProperty(reqMessage.requestType)) { messageActions[reqMessage.requestType](); }}Injected modal response channel
files/modal.js//post message to popup iframe (to be used in functions below)function postMessageToPlugin(msg) { var msg = JSON.stringify(msg); //get extension iframe var chrlsIFrame = document.getElementById("chrls-iframe"); chrlsIFrame.contentWindow.postMessage(msg, "*");}Injected modal tab and selection responses
files/modal.js//function: get infos about current tab and post to iframefunction returnTabInfo() { var msg = { "returnType": "tabInfo", "url": window.location.href, "title": document.title }; postMessageToPlugin(msg); //on click or URL change, post new url to iframe function handleEvent(event) { var msg = { "returnType": "tabInfo", "url": window.location.href, "title": document.title }; postMessageToPlugin(msg); } // Attach the event listeners window.addEventListener('popstate', handleEvent); window.addEventListener('click', handleEvent);}//function: post selected text to iframe on selectiondocument.addEventListener('selectionchange', function () { var selectedText = window.getSelection().toString(); var msg = { "returnType": "textSelection", "text": selectedText }; postMessageToPlugin(msg);});Injected modal DOM responses
files/modal.js//function: get element text and post to iframefunction getElementText(selector) { var element = document.querySelector(selector); if (!element) return console.log("Chrome Extension: no element found for selector: " + selector); var elementText = element.innerText; var msg = { "returnType": "getElementText", "selector": selector, "text": elementText }; postMessageToPlugin(msg); //on click or URL change, post new text to iframe function handleEvent(event) { var element = document.querySelector(selector); if (!element) return console.log("Chrome Extension: no element found for selector: " + selector); var elementText = element.innerText; var msg = { "returnType": "getElementText", "selector": selector, "text": elementText }; postMessageToPlugin(msg); } // Attach the event listeners window.addEventListener('popstate', handleEvent); window.addEventListener('click', handleEvent);}//function: get element HTML and post to iframefunction getElementHTML(selector) { var element = document.querySelector(selector); if (!element) return console.log("Chrome Extension: no element found for selector: " + selector); var elementHTML = element.outerHTML; var msg = { "returnType": "getElementHTML", "selector": selector, "HTML": elementHTML }; postMessageToPlugin(msg); //on click or URL change, post new HTML to iframe function handleEvent2(event) { var element = document.querySelector(selector); if (!element) return console.log("Chrome Extension: no element found for selector: " + selector); var elementHTML = element.outerHTML; var msg = { "returnType": "getElementHTML", "selector": selector, "HTML": elementHTML }; postMessageToPlugin(msg); } // Attach the event listeners window.addEventListener('popstate', handleEvent2); window.addEventListener('click', handleEvent2);}Injected modal localStorage response
files/modal.js//read from chrome local storagefunction readFromLocalStorage(key) { var result = window.localStorage.getItem(key); var msg = { "returnType": "localVariable", "key": key, "value": result }; postMessageToPlugin(msg);};- vocabulary.live
Hosts the prompt-generator page embedded by the extension and receives postMessage responses from the extension UI.
What it can do
Permissions this extension asks for, as declared in version 1.0.3. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
See the address and title of every tab you have open
tabs
Act on the current tab, but only after you click the extension
activeTab
Run its own code inside the pages you visit
scripting
Where it sends data
Destinations our analysis observed Prompt Generator 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.
- vocabulary.live
Prompt Generator sends data to vocabulary.live. 3 other extensions we have analysed send data here.