Is FancyGPT safe?
FancyGPT sends saved ChatGPT conversation text to its Firebase Storage bucket when its chat editor opens.
When you use FancyGPT to save a ChatGPT thread, it captures the conversation content from the page and stores it locally for rendering in its editor. When that editor opens, the extension converts the rendered conversation to a text file and uploads it to the fancygpt Firebase Storage bucket.
Who publishes itVISZEN (PTY) LTD - no other listings under this identity
VISZEN (PTY) 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
ChatGPT conversation text uploads to FancyGPT storage
Clicking FancyGPT's "Make your ChatGPT fancy" button reads conversation turns from the page, saves their HTML/roles in storage, opens its chat viewer, converts the chat to text, and uploads it to Firebase bucket `fancygpt.appspot.com`.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You click FancyGPT's "Make your ChatGPT fancy" button while viewing a ChatGPT conversation.
The popup only proceeds when the active tab URL includes chatgpt.com.
The extension reads the visible conversation, stores the messages locally, opens its chat viewer, and uploads a text file of the rendered chat.
The upload happens when `chats.html` loads, before the separate "Save Text file" button is clicked.
- Conversation message HTMLUser: Draft an incident update for Monday; Assistant: Here is a concise status note. (illustrative)
The saved text can include what you wrote to ChatGPT and what ChatGPT returned in that conversation.
- Message roleuser
The extension keeps whether each message came from you or from the assistant, preserving the structure of the conversation.
- Saved thread keyfancygptthread.thread[0].html
The conversation is kept in extension storage before the chat viewer turns it into the uploaded text file.
- Storage object pathchats/2026-7-13/3b241101-e2bb-4255-8caf-4136c566a962.txt
The uploaded file is placed in a dated chats folder with a randomly generated text filename.
Source path from popup click to Firebase Storage upload
Popup click handler injects the page-reading function and opens chats.html
extracted/popup.jslet saveChatElement = document.getElementById("saveChat");saveChatElement.addEventListener("click", async () => { // Extract thread data let [tab] = await chrome.tabs.query({active: true, currentWindow:true}) if (tab.url.includes('chatgpt.com')) { // Save thread chrome.scripting.executeScript({ target: {tabId: tab.id}, function: saveThread }); // Open chats page chrome.tabs.create({ url: "chats.html" }); } else { document.getElementById("incorrectUrlMessage").classList.remove('d-none'); }});Injected function collects ChatGPT turns and stores them locally
extracted/popup.jsfunction saveThread() { chrome.storage.local.set({'fancygptthread': []}, () => { }); let chatThread = []; let chatElements = []; const selectors = [ 'article[data-testid="conversation-turn"]', '[data-testid="conversation-turn"]', 'div[data-message-author-role]', 'div[data-message-id]' ]; for (const selector of selectors) { const found = Array.from(document.querySelectorAll(selector)); if (found.length > 0) { chatElements = found; break; } } if (chatElements.length === 0) { const thread = document.querySelector('#thread'); if (thread) { const articles = Array.from(thread.querySelectorAll('article')); if (articles.length > 0) { chatElements = articles; } } } for (let i = 0; i < chatElements.length; i++){ let currentElement = chatElements[i]; let contentElement = null; const contentSelectors = [ '[data-testid="message-content"]', '[data-message-content="true"]', '.markdown', '.prose', '.whitespace-pre-wrap' ]; for (const selector of contentSelectors) { const found = currentElement.querySelector(selector); if (found) { contentElement = found; break; } } const target = contentElement || currentElement; const targetClone = target.cloneNode(true); const buttonElement = targetClone.querySelectorAll('button'); if (buttonElement) { buttonElement.forEach((button) => { button.remove(); }); } let role = currentElement.getAttribute('data-message-author-role'); if (!role) { const roleNode = currentElement.querySelector('[data-message-author-role]'); if (roleNode) { role = roleNode.getAttribute('data-message-author-role'); } } chatThread.push({ html: targetClone.innerHTML, role: role || null }); } let chatObject = { 'thread' : chatThread } chrome.storage.local.set({'fancygptthread': chatObject}, () => { });}Firebase configuration and upload function
extracted/chats.jsconst firebaseConfig = { apiKey: "AIzaSyDeOs79Zo-ShlDqcIuTqHuXgwxhqdp5M9c", authDomain: "fancygpt.firebaseapp.com", projectId: "fancygpt", storageBucket: "fancygpt.appspot.com", messagingSenderId: "825047094039", appId: "1:825047094039:web:738f0b5579d48db49bdd52", measurementId: "G-3V9N3YJDVE"};function uuidv4() { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) );}async function saveTextFileToStorage() { const turndownService = new TurndownService(); let markdown = turndownService.turndown($('#container').html()); markdown = markdown.replaceAll('Exclude message', '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'); markdown = markdown.replaceAll('\n### Type a snippet title here (Optional)\n', ''); markdown = markdown.replaceAll('FancyGPT lets you beautify your threads, and store them for future reference or sharing with others.\n\n1. Choose a style that you like\n2. Add an optional snippet title\n3. Exclude any messages that you don\'t want\n4. Click and drag on ChatGPT messages to highlight content\n5. Click "Save Image" to download your fancy snippet as an image\n6. Click "Save PDF" to download your fancy snippet as a searchable PDF\n7. Click "Save Text file" to download your fancy snippet as a text file\n\nNote: This extension is reliant on ChatGPT\'s thread format and could break if things change.\n', ''); markdown = markdown.replaceAll('From ChatGPT, designed with sparkles\\_fill FancyGPT', '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nFrom ChatGPT, designed with FancyGPT\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'); const file = new Blob([markdown], { type: 'text/plain' }); firebase.initializeApp(firebaseConfig); let dateObj = new Date(); let month = dateObj.getUTCMonth() + 1; //months from 1-12 let day = dateObj.getUTCDate(); let year = dateObj.getUTCFullYear(); let newdate = year + "-" + month + "-" + day; let storageRef = firebase.storage().ref('chats/' + newdate + '/' + uuidv4() + '.txt'); await storageRef.put(file);}- firebasestorage.googleapis.com
Receives the Firebase Storage upload request for bucket fancygpt.appspot.com.
- fancygpt.appspot.com
The Firebase Storage bucket configured in the extension source as the upload target.
What it can do
Permissions this extension asks for, as declared in version 1.1.13. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Store data in your browser
storage
Store an unlimited amount of data in your browser
unlimitedStorage
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 FancyGPT 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.
- firebasestorage.googleapis.com
FancyGPT sends data to firebasestorage.googleapis.com. 2 other extensions we have analysed send data here.
- fancygpt.appspot.com
FancyGPT sends data to fancygpt.appspot.com. No other extension we have analysed sends data here.