Is NotebookLM Tools safe?
NotebookLM Tools captures Google session tokens and email from NotebookLM pages and stores them to make authenticated API calls on the user's behalf.
When a user visits notebooklm.google.com, the extension injects a script that reads authentication tokens (SNlM0e, bl token, f.sid) and the user's email address from the page's WIZ_global_data object. These credentials are stored persistently in chrome.storage.local, keyed by email, and used by the extension's API client to send authenticated requests to NotebookLM's batchexecute endpoint — including operations that create, delete, and modify notebooks and sources.
Who publishes itYTuong.me - 5 other listings from the same operator, 1 of them carrying a finding
YTuong.me - 5 other listings from the same operator, 1 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
5 other listings published from this account, 82k+ users between them. 1 of them carries a finding.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
NotebookLM Tools collects Google session fields
Opening NotebookLM injects a script reading a Google auth token and email into page localStorage; the content script forwards them to storage keyed by email.
No logged-in profile was available, but the injector matched the source shape.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-522
- Source
- Dynamic sandbox
You open a NotebookLM page while the extension is installed.
The extension is configured to run its content script on notebooklm.google.com.
The extension copies Google session fields from the page into browser extension storage.
It injects a page script, reads the page's global session data, relays it through localStorage, then stores the account record by email.
- Google auth tokenya29.a0AfH6SMC8mN7kQw2rXbP4sL9vZ0pT6jE1rAaK (illustrative)
This is a session credential from the signed-in Google page. If another component can read it, it can represent the active session.
- Signed-in emailalex.researcher@example.com (illustrative)
This ties the collected session fields to your Google account identity.
- NotebookLM app valueboq_notebooklm-web-server_20260710.01_p0 (illustrative)
This adds application context to the account record the extension saves.
- Session side valuesbl=boq_notebooklm-web-server_20260710.01_p0; f.sid=-5821749081723342217 (illustrative)
These values provide additional session context alongside the token and email.
- Google account index0
This indicates which signed-in Google account slot the page is using.
The extension keeps the collected session record locally under the Google email. Token values are redacted here as credentials.
- Location
- localStorage key YT_NLM, then chrome.storage.local account config
{ "YT_NLM": { "bl": "boq_notebooklm-web-server_20260710.01_p0 (illustrative)", "app": "boq_notebooklm-web-server_20260710.01_p0 (illustrative)", "fsid": "-5821749081723342217 (illustrative)", "email": "alex.researcher@example.com (illustrative)", "token": "<redacted>", "authuser": 0 }, "YT_NOTEBOOKLM_TOOLS_CONFIG_NLM_ACCOUNTS": { "accounts": { "alex.researcher@example.com": { "bl": "boq_notebooklm-web-server_20260710.01_p0 (illustrative)", "app": "boq_notebooklm-web-server_20260710.01_p0 (illustrative)", "fsid": "-5821749081723342217 (illustrative)", "email": "alex.researcher@example.com (illustrative)", "token": "<redacted>", "authuser": 0, "lastRefreshed": "2026-07-12T05:11:13.756Z" } }, "accountOrder": [ "alex.researcher@example.com" ], "activeAccount": "alex.researcher@example.com" }}Shipped code path that collects and stores the session fields
Page script reads WIZ_global_data and writes localStorage
deobfuscated/inject.jstry { var YTUONG_NLM_PAYLOAD = {} // SNlM0e: token if (window?.WIZ_global_data?.SNlM0e) { YTUONG_NLM_PAYLOAD.token = window?.WIZ_global_data?.SNlM0e } // qwAQke: app if (window?.WIZ_global_data?.qwAQke) { YTUONG_NLM_PAYLOAD.app = window?.WIZ_global_data?.qwAQke } // cfb2h: bl if (window?.WIZ_global_data?.cfb2h) { YTUONG_NLM_PAYLOAD.bl = window?.WIZ_global_data?.cfb2h } // FdrFJe: f.sid if (window?.WIZ_global_data?.FdrFJe) { YTUONG_NLM_PAYLOAD.fsid = window?.WIZ_global_data?.FdrFJe } // oPEP7c: email if (window?.WIZ_global_data?.oPEP7c) { YTUONG_NLM_PAYLOAD.email = window?.WIZ_global_data?.oPEP7c } // Extract authuser from URL var urlParams = new URLSearchParams(window.location.search) YTUONG_NLM_PAYLOAD.authuser = parseInt(urlParams.get('authuser') || '0', 10) || 0 window.localStorage.setItem('YT_NLM', JSON.stringify(YTUONG_NLM_PAYLOAD))} catch (e) { console.error('[NLM Tools] Failed to extract auth data:', e)}Content script injects the page script and forwards the payload
deobfuscated/assets/chunk-6b4dd0be.js(function(e, i) { console.log("NotebookLM inject"), s().then(async () => { let o = {}; try { const r = window.localStorage.getItem("YT_NLM"); r && (o = JSON.parse(r)); } catch (r) { console.error("[NLM Tools] Corrupted auth storage:", r), window.localStorage.removeItem("YT_NLM"); } try { const r = await N("YTUONG_NLM_PAYLOAD", o, "background"); console.log("Response from background script:", r); } catch (r) { console.error("[NLM Tools] Failed to send auth payload:", r); } const t = (await chrome.storage.local.get("YT_NOTEBOOKLM_TOOLS_INTERFACE_LANGUAGE")).YT_NOTEBOOKLM_TOOLS_INTERFACE_LANGUAGE; if (t && new URL(window.location.href).searchParams.get("hl") !== t) { console.log("Auto-applying interface language:", t), a(t); return; } }).catch((o) => { console.error("Script injection failed:", o); }), e.runtime.onMessage.addListener((o, n, t) => { switch (o.type) { case "RELOAD_PAGE": window.location.reload(); break; case "UPDATE_INTERFACE_LANGUAGE": a(o.languageCode); break; } }); function a(o) { const n = new URL(window.location.href); n.searchParams.set("hl", o), n.toString() !== window.location.href && (window.location.href = n.toString()); } function s() { return new Promise((o, n) => { var t = document.createElement("script"); t.src = e.runtime.getURL("inject.js"), t.onload = function() { console.log("Inject script loaded successfully"), this.remove(), o(); }, t.onerror = function() { console.error("Failed to load inject script"), this.remove(), n(new Error("Script loading failed")); }, (document.head || document.documentElement).appendChild(t); }); }})(chrome);Background handler stores account payloads keyed by email
deobfuscated/assets/chunk-6eba50c8.jsh("YTUONG_NLM_PAYLOAD", async ({ data: e }) => { try { if (!(e == null ? void 0 : e.token) || !(e == null ? void 0 : e.email)) return console.warn("[NLM Tools] Invalid payload, skipping"), { success: false, error: "Invalid payload" }; const n = await d.get(m) || {}, t = { ...n.accounts || {} }; t[e.email] = { ...e, lastRefreshed: (/* @__PURE__ */ new Date()).toISOString() }; const o = n.activeAccount || e.email, a = n.accountOrder ? [...n.accountOrder] : []; a.includes(e.email) || a.push(e.email); const r = { accounts: t, activeAccount: o, accountOrder: a }; return await d.set(m, r), t[o] && await d.set(C, t[o]), await v(), { success: true }; } catch (n) { return console.error("[NLM Tools] Failed to store payload:", n), { success: false, error: n.message }; }});NotebookLM page scope declared in the manifest
{ "content_scripts": [ { "js": [ "assets/index.js-loader-56a17ce3.js", "assets/content.jsx-loader-8a8227ba.js" ], "matches": [ "*://notebooklm.google.com/*" ] } ], "web_accessible_resources": [ { "matches": [ "*://notebooklm.google.com/*" ], "resources": [ "*", "assets/chunk-38df7042.js", "assets/chunk-9bfc7485.js", "assets/chunk-725317a4.js", "assets/chunk-6b4dd0be.js", "assets/chunk-c4a3a80c.js", "assets/chunk-b8cdbf8b.js", "assets/chunk-4ca06da3.js", "assets/chunk-a12ead9f.js", "assets/chunk-8db09c21.js", "assets/chunk-dd74c6e0.js", "assets/chunk-8d3650e6.js", "assets/chunk-d45ab30c.js", "assets/chunk-f676d28d.js" ], "use_dynamic_url": false } ], "host_permissions": [ "*://notebooklm.google.com/*", "http://*/*", "https://*/*" ]}Google token and email stored in extension storage
Opening NotebookLM pages, the extension injects a script that reads Google page-data for a token and account email, sends it to the background worker, and stores it under an accounts object keyed by email in extension storage.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You open a NotebookLM page where the extension is allowed to run.
The extension reads Google page-data values for your token and email, then stores the account record in extension storage.
- Google tokenya29.a0AfH6SMAq6mJfP8YwR9pK2xQ7L3nB5cD4eF6gH7iJ8kL9mN0pQ (illustrative)
This value is tied to your Google-backed NotebookLM session and is stored by the extension instead of staying only in the page.
- Google account emailalex.chen@example.com (illustrative)
This ties the stored token record to your Google account and is also used as the lookup key for the account list.
- Account selector number1 (illustrative)
This records which signed-in Google profile the page was using when the token was collected.
- Refresh timestamp2026-07-12T05:12:25.956Z (illustrative)
This records when the extension last refreshed the stored account record.
The page-injected script copies token and email from Google page data
Readable view of the same page script
inject.jstry { var YTUONG_NLM_PAYLOAD = {} // SNlM0e: token if (window?.WIZ_global_data?.SNlM0e) { YTUONG_NLM_PAYLOAD.token = window?.WIZ_global_data?.SNlM0e } // qwAQke: app if (window?.WIZ_global_data?.qwAQke) { YTUONG_NLM_PAYLOAD.app = window?.WIZ_global_data?.qwAQke } // cfb2h: bl if (window?.WIZ_global_data?.cfb2h) { YTUONG_NLM_PAYLOAD.bl = window?.WIZ_global_data?.cfb2h } // FdrFJe: f.sid if (window?.WIZ_global_data?.FdrFJe) { YTUONG_NLM_PAYLOAD.fsid = window?.WIZ_global_data?.FdrFJe } // oPEP7c: email if (window?.WIZ_global_data?.oPEP7c) { YTUONG_NLM_PAYLOAD.email = window?.WIZ_global_data?.oPEP7c } // Extract authuser from URL var urlParams = new URLSearchParams(window.location.search) YTUONG_NLM_PAYLOAD.authuser = parseInt(urlParams.get('authuser') || '0', 10) || 0 window.localStorage.setItem('YT_NLM', JSON.stringify(YTUONG_NLM_PAYLOAD))} catch (e) { console.error('[NLM Tools] Failed to extract auth data:', e)}The content script forwards the payload to the background service worker
Readable view of the same bridge code
assets/chunk-6b4dd0be.js(function(e, i) { console.log("NotebookLM inject"), s().then(async () => { let o = {}; try { const r = window.localStorage.getItem("YT_NLM"); r && (o = JSON.parse(r)); } catch (r) { console.error("[NLM Tools] Corrupted auth storage:", r), window.localStorage.removeItem("YT_NLM"); } try { const r = await N("YTUONG_NLM_PAYLOAD", o, "background"); console.log("Response from background script:", r); } catch (r) { console.error("[NLM Tools] Failed to send auth payload:", r); } const t = (await chrome.storage.local.get("YT_NOTEBOOKLM_TOOLS_INTERFACE_LANGUAGE")).YT_NOTEBOOKLM_TOOLS_INTERFACE_LANGUAGE; if (t && new URL(window.location.href).searchParams.get("hl") !== t) { console.log("Auto-applying interface language:", t), a(t); return; } }).catch((o) => { console.error("Script injection failed:", o); }), e.runtime.onMessage.addListener((o, n, t) => { switch (o.type) { case "RELOAD_PAGE": window.location.reload(); break; case "UPDATE_INTERFACE_LANGUAGE": a(o.languageCode); break; } }); function a(o) { const n = new URL(window.location.href); n.searchParams.set("hl", o), n.toString() !== window.location.href && (window.location.href = n.toString()); } function s() { return new Promise((o, n) => { var t = document.createElement("script"); t.src = e.runtime.getURL("inject.js"), t.onload = function() { console.log("Inject script loaded successfully"), this.remove(), o(); }, t.onerror = function() { console.error("Failed to load inject script"), this.remove(), n(new Error("Script loading failed")); }, (document.head || document.documentElement).appendChild(t); }); }})(chrome);The background handler stores account records keyed by email
Readable view of the background storage handler
assets/chunk-6eba50c8.jsh("YTUONG_NLM_PAYLOAD", async ({ data: e }) => { try { if (!(e == null ? void 0 : e.token) || !(e == null ? void 0 : e.email)) return console.warn("[NLM Tools] Invalid payload, skipping"), { success: false, error: "Invalid payload" }; const n = await d.get(m) || {}, t = { ...n.accounts || {} }; t[e.email] = { ...e, lastRefreshed: (/* @__PURE__ */ new Date()).toISOString() }; const o = n.activeAccount || e.email, a = n.accountOrder ? [...n.accountOrder] : []; a.includes(e.email) || a.push(e.email); const r = { accounts: t, activeAccount: o, accountOrder: a }; return await d.set(m, r), t[o] && await d.set(C, t[o]), await v(), { success: true }; } catch (n) { return console.error("[NLM Tools] Failed to store payload:", n), { success: false, error: n.message }; }});