Is Prompt Generator safe?

High risk

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.

Prius Labv1.0.3Chrome Web Store
72Risk
Who publishes it

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.

Publisher
Prius Lab
Declared legal entity
Prius Lab
Registered address
Robocha st 10a 3, Shostka, Сумська область 41106, UA
Registered contact
Vitalii Doroshenko

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
What actually happens
You did this

You open or use the Prompt Generator extension UI.

That action loads the remote prompt page inside the extension popup or sidebar.

The extension did this

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.

Captured request
GEThttps://vocabulary.live/prompt-generator

The iframe navigation loads the remote prompt-generator page into the extension UI.

Active-tab data the remote page can request
  • Active page URL
    https://intranet.example.com/projects/alpha?ticket=INC-4831

    Lets the remote page know which site and exact page you have open.

  • Page title
    Quarterly Roadmap - Internal Wiki

    Adds readable context about the page you are viewing.

  • Selected text
    Customer 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 value
    workspace_state={"tenant":"acme","status":"active"}

    Can reveal data the active website saved in your browser for a named key.

  • Visible-tab screenshot
    data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfq

    Can capture what is visible in the current browser tab as an image.

The code that does this

The popup loads vocabulary.live and treats its messages as commands

Readable version

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.js
function 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 code that does this

The popup command handlers read active-tab data and post results back

Readable version

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 code that does this

The sidebar path injects a remote iframe into the active tab

Readable version

Background message handlers for sidebar and modal injection

background.js
const 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.js
function 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.js
function 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.js
function 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);};
External host involved in the command channel
    • 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.

Updated 30 September 2026fbdejpleglglcaopdnhlehijeikclpgo