Is Copilot: AI Study Assistant | GPT-5, Claude & Gemini for Students safe?
Copilot is low risk. Clicking Summarise or Key Concepts pulls the active tab's HTML, strips a few elements, and sends the content to chromecopilot.ai over WebSocket. The message also includes your account token, the chosen action, and the selected AI model.…
Who publishes itPsyber Solutions - no other listings under this identity
Psyber Solutions - 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
Page HTML sent to chromecopilot.ai for study tools
Clicking Summarise or Key Concepts pulls the active tab's HTML, strips a few elements, and sends the content to chromecopilot.ai over WebSocket.
The message also includes your account token, the chosen action, and the selected AI model.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You choose a page-study action in the extension sidebar.
The relevant controls are the Summarise and Key Concepts buttons.
The extension reads the active page document and sends the filtered HTML to chromecopilot.ai.
The outgoing message also includes your account token, the action name, and the selected model.
The shipped code opens this WebSocket endpoint and sends a file_search message after the connection opens.
- Page document content<html><body><main>Week 3 course notes and assignment details</main></body></html> (illustrative)
This can include the text and markup from the page you asked the extension to summarize.
- Study actionsummarise
This tells the service which sidebar action you selected for the page.
- Account tokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDQyIn0.signature (illustrative)
This links the request to the extension account session used by the sidebar.
- Selected modelGPT3
This records which AI model setting was selected when the request was sent.
The shipped code reads page HTML and sends it to the file-search endpoint
Readable service-worker flow
service_worker.bundle.jsfunction injectReadPageScript(tabId) { chrome.scripting.executeScript( { target: { tabId }, files: ["readPage-contentScript.bundle.js"] }, () => { if (chrome.runtime.lastError) { log(`Failed to inject script: ${chrome.runtime.lastError.message}`); } } );}chrome.tabs.onActivated.addListener((event) => { injectReadPageScript(event.tabId);});chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { if (changeInfo.status === "complete") { injectReadPageScript(tabId); }});Readable content-script flow
readPage-contentScript.bundle.jschrome.runtime.onMessage.addListener((message) => { if (message.type === "extractPageContent") { const originalHtml = document.documentElement.outerHTML; const sanitizedHtml = DOMPurify.sanitize(originalHtml); const parsed = new DOMParser().parseFromString(sanitizedHtml, "text/html"); ["script", "style", "iframe", "nav", "footer", ".ads", ".sensitive"].forEach((selector) => { parsed.querySelectorAll(selector).forEach((node) => node.remove()); }); const filteredHtml = parsed.documentElement.outerHTML; chrome.runtime.sendMessage({ type: "filteredPageContent", content: filteredHtml }); }});Readable sidebar flow
personal-tutor.bundle.jsfunction onSummariseClick() { renderPageAction("Summarise"); requestPageContent("summarise"); hideReadPagePanel(); scrollToResponse();}function onKeyConceptsClick() { renderPageAction("Key Concepts"); requestPageContent("keyConcepts"); hideReadPagePanel(); scrollToResponse();}async function requestPageContent(action) { chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => { const activeTab = tabs[0]; function onRuntimeMessage(message) { if (message.type === "filteredPageContent") { const filteredContent = message.content; sendContentToFileSearch(filteredContent, action); chrome.runtime.onMessage.removeListener(onRuntimeMessage); } } chrome.tabs.sendMessage(activeTab.id, { type: "extractPageContent" }); chrome.runtime.onMessage.removeListener(onRuntimeMessage); chrome.runtime.onMessage.addListener(onRuntimeMessage); });}async function sendContentToFileSearch(filteredContent, action) { const token = await getJwtAccessToken(); const model = (await getSetting("selectedAIModel")) || "GPT3"; if (fileSearchSocket && fileSearchSocket.readyState === WebSocket.OPEN) { fileSearchSocket.close(); } fileSearchSocket = new WebSocket("wss://chromecopilot.ai/ws/file_search/"); fileSearchSocket.onopen = () => { const payload = { type: "file_search", token, content: filteredContent, action, current_model: model }; fileSearchSocket.send(JSON.stringify(payload)); }; fileSearchSocket.onmessage = async (event) => { const response = JSON.parse(event.data); switch (response.message_type) { case "file_search_result": renderFileSearchResult(response.role || "assistant", response.message); scrollToResponse(); break; case "webpage_search_error": renderWebpageSearchError(response.message); setLoading(false); scrollToResponse(); if (fileSearchSocket && fileSearchSocket.readyState === WebSocket.OPEN) { fileSearchSocket.close(); } break; case "end_of_file_message": finishFileSearchMessage(); setLoading(false); scrollToResponse(); if (fileSearchSocket && fileSearchSocket.readyState === WebSocket.OPEN) { fileSearchSocket.close(); } break; case "current_tokens": updateTokenDisplay(response); break; case "no_tokens_free": case "no_tokens": case "no_tokens_professional": await showTokenAlert(response.message, response.message_type); break; default: break; } }; fileSearchSocket.onerror = (event) => log("WebSocket error:", event); fileSearchSocket.onclose = () => log("WebSocket connection closed");}- chromecopilot.ai
Receives the file-search WebSocket connection and the page-content message for the selected sidebar study action.
Chat history sent to chromecopilot.ai on each message
Each sidebar chat message builds a chat_message payload sending the full conversation to chromecopilot.ai, plus your account token, selected model, tutor personality, stored context, and mode flags like web explainer and translation.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You send a message in the extension sidebar chat.
The same sender is also used by selected-text explanation and translation flows.
The extension sends the current conversation history and stored context to chromecopilot.ai.
The payload includes message roles and content, model settings, tutor personality, personal context, and feature flags.
The shipped code opens this WebSocket endpoint and sends chat_message payloads after the connection is authenticated.
- Conversation history[{"role":"user","content":"Explain this paragraph"},{"role":"assistant","content":"Here is a simpler explanation"}] (illustrative)
This includes the messages currently held by the sidebar chat, not only the newest text you just sent.
- Stored personal contextI am studying biology and prefer beginner explanations. (illustrative)
This can include personal notes or preferences saved in the extension and attached to chat requests.
- Account tokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDQyIn0.signature (illustrative)
This links the chat request to the extension account session used by the sidebar.
- Model and tutor settingsmodel=GPT3, personality=personality-1-container, level=Beginner, translate=false (illustrative)
This records the model, tutor personality, explanation level, and translation mode used for the response.
- Web explainer flagtrue
This marks requests that come from page or selected-text explanation flows.
The shipped code sends the full message array to the chat endpoint
Readable connection flow
personal-tutor.bundle.jsasync function ensureChatWebSocket() { if (chatSocket && chatSocket.readyState === WebSocket.OPEN) { return; } const token = await getJwtAccessToken(); if (!token) { log("Aborting WebSocket connection due to missing JWT Access Token."); return; } chatSocket = new WebSocket("wss://chromecopilot.ai/ws/chat-feature/"); chatSocket.addEventListener("open", () => authenticateChatSocket(token)); chatSocket.addEventListener("message", (event) => handleChatSocketMessage(event.data)); chatSocket.addEventListener("close", () => log("WebSocket connection closed.")); chatSocket.addEventListener("error", (event) => { showConnectionErrorInActiveTab(event); chatSocket.close(); document.getElementById("text-box").style.display = "none"; document.getElementById("chat-gpt-4-switch").style.display = "none"; });}function authenticateChatSocket(token) { if (chatSocket && chatSocket.readyState === WebSocket.OPEN) { const authMessage = JSON.stringify({ type: "auth", token }); chatSocket.send(authMessage); }}Readable chat sender flow
personal-tutor.bundle.jsfunction sendNormalMessage(text) { const input = shadowRoot.querySelector("#message-input"); const sendButton = shadowRoot.querySelector(".send-to-ai"); const message = { type: "chat", role: "user", content: decodeHtml(text) }; renderChatMessage(message.role, message.content).then(() => { sendChatMessage(message); updateSendButtonState("", sendButton); input.value = ""; resizeTextArea(true); persistChatLog(); });}async function sendChatMessage(message, renderUserMessage = true, isWebExplainer = false, level = "", fromSelection = false, skipHistoryPush = false, isTranslate = false) { if (!chatUiReady) { showChatRoom(); await loadCurrentChatLog(); chatUiReady = true; setActiveView("chatroom"); } autoScrollEnabled = true; selectedModel = (await getSetting("selectedAIModel")) || "GPT3"; await ensureChatReady(); if (!skipHistoryPush) { messageHistory.push(message); } if (!renderUserMessage) { renderChatMessage(message.role, message.content, true, isWebExplainer, level, fromSelection); } if (currentRoom && !currentRoom.firstMessageSent) { const titlePayload = { type: "room-title", summary: message.content }; if (await ensureChatReady()) { chatSocket.send(JSON.stringify(titlePayload)); } currentRoom.firstMessageSent = true; markRoomTitleSent(currentRoom.roomId); } const token = await getJwtAccessToken(); const personality = await getTutorPersonality(); const personalContext = await getUserContext(); const payload = { token, type: "chat_message", messages: messageHistory.map(({ content, role }) => ({ content, role })), model: selectedModel, personality, context: personalContext, isWebExplainer, level, isTranslate }; if (chatSocket && chatSocket.readyState === WebSocket.OPEN) { const serialized = JSON.stringify(payload); if (await ensureChatReady()) { chatSocket.send(serialized); const welcome = shadowRoot.querySelector(".copilot-welcome-container"); if (welcome) { welcome.classList.remove("activated-container"); welcome.classList.add("deactivated-container"); } } } persistChatLog();}Readable stored context reader
personal-tutor.bundle.jsasync function getUserContext() { try { const response = await browser.runtime.sendMessage({ action: "getSetting", key: "userContext" }); return response.success && response.value ? response.value : ""; } catch (error) { return ""; }}- chromecopilot.ai
Receives the chat WebSocket connection and chat_message payloads from the extension sidebar.
What it can do
Permissions this extension asks for, as declared in version 3.0.2. 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
<all_urls>
Store data in your browser
storage
Act on the current tab, but only after you click the extension
activeTab
See the address and title of every tab you have open
tabs
Run its own code inside the pages you visit
scripting
Show a panel beside the page
sidePanel