BACK TO SEARCH
roomi-fields/notebooklm-mcpcritical

Google NotebookLM over MCP + a local HTTP REST API. Citation-backed Q&A, audio/video/content generation, multi-account rotation. For Claude Code, Codex, Cursor, n8n, Zapier, Make.

This MCP server automates Google NotebookLM by providing a REST API and MCP interface for citation-backed Q&A, content generation (audio, video, infog...

purpose: This MCP server automates Google NotebookLM by prothreat: network exposed
TypeScript · 80 · May 21, 2026 · May 21, 2026 · GITHUB ↗
RISK SCORE
0/ 100 risk
high findings+125
medium findings+75
capped at100
VULNERABILITY ANALYSIS · 10 findings in 10 blocks5 HIGH · 5 MEDIUM
HIGH1 finding
src/http-wrapper.ts:952
952app.get('/content/download', async (req: Request, res: Response) => {
953  try {
954    const { content_type, output_path, notebook_url, session_id } = req.query;
955    ...
956    const result = await toolHandlers.handleDownloadContent({
957      content_type: content_type as ...,
958      output_path: typeof output_path === 'string' ? output_path : undefined,
959      ...
960    });
src/http-wrapper.ts:952

// Network-exposed MCP server; an attacker can send HTTP requests to this endpoint.

EXPLAINThe download_content endpoint accepts an output_path query parameter that is passed directly to the handler without any validation or sanitization. This allows an attacker to specify an arbitrary file path for writing downloaded content, enabling arbitrary file write outside the intended scope.
IMPACTAn attacker could overwrite critical system files, write malicious files (e.g., scripts in startup directories), or exfiltrate data by writing to a location they control. On a network-exposed server, this is a high-severity arbitrary file write vulnerability.
FIXValidate that output_path is within an allowed directory (e.g., a dedicated downloads folder). Use path.resolve and ensure it starts with the allowed base path. Reject paths containing '..' or symbolic links.
HIGH1 finding
src/http-wrapper.ts:128
128app.post('/batch-to-vault', async (req: Request, res: Response) => {
129  ...
130  const { ..., vault_dir, ... } = req.body;
131  if (!vault_dir || typeof vault_dir !== 'string') {
132    return res.status(400).json({ ... });
133  }
134  ...
135  const result = await toolHandlers.handleBatchToVault({
136    ...,
137    vault_dir,
138    ...
139  });
src/http-wrapper.ts:128

// Network-exposed MCP server; an attacker can send HTTP requests to this endpoint.

EXPLAINThe batch-to-vault endpoint accepts a vault_dir parameter from the request body without any validation or sanitization. This directory path is used to write answer files (markdown + JSON). An attacker can specify an arbitrary directory, leading to arbitrary file write.
IMPACTAn attacker can write files to any location on the filesystem, potentially overwriting system files, planting malicious scripts, or filling up disk space. This is a high-severity arbitrary file write vulnerability on a network-exposed server.
FIXValidate that vault_dir is within an allowed base directory. Use path.resolve and ensure it starts with the allowed path. Reject paths containing '..' or symbolic links.
HIGH1 finding
src/http-wrapper.ts:684
684app.post('/content/sources', async (req: Request, res: Response) => {
685  try {
686    const { source_type, file_path, url, text, title, notebook_url, session_id, show_browser } =
687      req.body;
688    ...
689    const result = await toolHandlers.handleAddSource({
690      source_type,
691      file_path,
692      ...
693    });
src/http-wrapper.ts:684

// Network-exposed MCP server; an attacker can send HTTP requests to this endpoint.

EXPLAINThe add_source endpoint accepts a file_path parameter from the request body without any validation or sanitization. This file path is used to read a file from the local filesystem and upload it as a source to NotebookLM. An attacker can specify an arbitrary file path, leading to arbitrary file read.
IMPACTAn attacker can read any file on the server's filesystem (e.g., /etc/passwd, SSH keys, configuration files) and upload it to NotebookLM, effectively exfiltrating sensitive data. This is a high-severity arbitrary file read vulnerability on a network-exposed server.
FIXValidate that file_path is within an allowed directory (e.g., a designated uploads folder). Use path.resolve and ensure it starts with the allowed base path. Reject paths containing '..' or symbolic links.
HIGH1 finding
src/config.ts:231
231loginEmail: process.env.LOGIN_EMAIL || config.loginEmail,
232loginPassword: process.env.LOGIN_PASSWORD || config.loginPassword,
src/config.ts:231-232

// Network-exposed MCP server; environment variables may be leaked through various channels.

EXPLAINThe configuration reads LOGIN_EMAIL and LOGIN_PASSWORD from environment variables and stores them in the CONFIG object in plaintext. These credentials are used for auto-login to Google NotebookLM. Environment variables can be exposed through process listings, debug logs, or if the server is compromised. The credentials are also passed to browser automation scripts, potentially exposing them to other processes.
IMPACTAn attacker who gains access to the server's environment or memory can retrieve Google account credentials, leading to account takeover and access to NotebookLM data.
FIXUse a secrets manager or encrypted credential storage. Avoid storing credentials in plaintext in memory. Consider using OAuth tokens instead of password-based authentication.
HIGH1 finding
src/http-wrapper.ts:1153
1153function tryKillPort(port: number): boolean {
1154  const isWindows = process.platform === 'win32';
1155  try {
1156    if (isWindows) {
1157      const output = execSync(`netstat -ano | findstr :${port} | findstr LISTENING`, {
1158        encoding: 'utf-8',
1159        timeout: 5000,
1160      });
1161      ...
1162      for (const pid of pids) {
1163        try {
1164          execSync(`taskkill /F /PID ${pid}`, { timeout: 5000 });
1165          ...
1166        } catch {}
1167      }
1168    } else {
1169      execSync(`fuser -k ${port}/tcp`, { timeout: 5000 });
1170      ...
1171    }
1172  } catch {
1173    return false;
1174  }
1175}
src/http-wrapper.ts:1153

// Network-exposed MCP server; exploitation requires control over port or PID values.

EXPLAINThe tryKillPort function uses execSync with string interpolation of the port number into shell commands. Although the port is a number from a trusted source (the server's own port configuration), the function is called with user-controlled port values if an attacker can influence the HTTP_PORT environment variable or if the function is reused elsewhere. Additionally, the PID extracted from netstat output is interpolated into a shell command without sanitization, allowing command injection if an attacker can control the netstat output (e.g., via a compromised process).
IMPACTAn attacker who can influence the port number or the PID (e.g., by running a process with a crafted name) could execute arbitrary shell commands on the server. This is a high-severity shell injection vulnerability.
FIXUse execFile instead of execSync to avoid shell interpretation. Pass arguments as an array. Validate that the PID consists only of digits before using it in a command.
MEDIUM1 finding
src/http-wrapper.ts:30
30const app = express();
31app.use(express.json({ limit: '10mb' }));
32...
33app.use((_req, res, next) => {
34  res.header('Access-Control-Allow-Origin', '*');
35  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
36  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
37  if (_req.method === 'OPTIONS') {
38    return res.sendStatus(200);
39  }
40  next();
41});
src/http-wrapper.ts:30

// Network-exposed MCP server; no authentication on any endpoint.

EXPLAINThe HTTP server has no authentication middleware. All endpoints are publicly accessible without any API key, token, or session check. The CORS configuration allows any origin, making the server accessible from any website. This exposes all tools to unauthenticated network attackers.
IMPACTAn attacker can call any tool without authentication, including destructive operations, data exfiltration, and credential theft. The server is fully exposed to the network.
FIXImplement authentication (e.g., API key in Authorization header, OAuth2, or mutual TLS). Restrict CORS to trusted origins.
MEDIUM1 finding
src/http-wrapper.ts:243
243app.post('/cleanup-data', async (req: Request, res: Response) => {
244  try {
245    const { confirm, preserve_library } = req.body;
246    const result = await toolHandlers.handleCleanupData({ confirm, preserve_library });
247    res.json(result);
248  } catch (error) { ... }
src/http-wrapper.ts:243

// Network-exposed MCP server; no authentication required.

EXPLAINThe cleanup-data endpoint allows deleting all server data (including credentials, sessions, and library) with only a 'confirm' boolean parameter. There is no authentication or authorization check. An attacker can call this endpoint to wipe the server's state, causing denial of service.
IMPACTAn attacker can destroy all stored data, including authentication credentials, notebook library, and session data, effectively resetting the server and causing service disruption.
FIXRequire authentication for destructive operations. Add a confirmation mechanism that cannot be easily automated (e.g., a random token).
MEDIUM1 finding
src/http-wrapper.ts:684
684app.post('/content/sources', async (req: Request, res: Response) => {
685  try {
686    const { source_type, file_path, url, text, title, notebook_url, session_id, show_browser } =
687      req.body;
688    ...
689    const result = await toolHandlers.handleAddSource({
690      source_type,
691      url,
692      ...
693    });
src/http-wrapper.ts:684

// Network-exposed MCP server; an attacker can send HTTP requests to this endpoint.

EXPLAINThe add_source endpoint accepts a url parameter from the request body without validation. This URL is used to fetch content from external websites and add it as a source to NotebookLM. An attacker can provide arbitrary URLs, including internal network addresses (e.g., http://169.254.169.254/latest/meta-data/ for cloud metadata), leading to Server-Side Request Forgery (SSRF).
IMPACTAn attacker can probe internal network services, access cloud metadata endpoints, or scan internal infrastructure. This can lead to information disclosure or further exploitation.
FIXValidate the URL against an allowlist of permitted domains (e.g., only notebooklm.google.com). Block private IP ranges and loopback addresses. Use a URL parser to reject malformed URLs.
MEDIUM1 finding
src/http-wrapper.ts:270
270app.post('/notebooks', async (req: Request, res: Response) => {
271  try {
272    const { url, name, description, topics, content_types, use_cases, tags } = req.body;
273    if (!url || !name || !description || !topics) {
274      return res.status(400).json({ ... });
275    }
276    const result = await toolHandlers.handleAddNotebook({
277      url,
278      name,
279      description,
280      topics,
281      content_types,
282      use_cases,
283      tags,
284    });
src/http-wrapper.ts:270

// Network-exposed MCP server; an attacker can send HTTP requests to this endpoint.

EXPLAINThe add_notebook endpoint accepts a url parameter without any validation beyond checking it is truthy. The URL is stored and later used to navigate to NotebookLM pages. An attacker can provide arbitrary URLs, potentially leading to open redirect or SSRF if the URL is fetched by the server.
IMPACTAn attacker could inject malicious URLs that cause the server to navigate to arbitrary sites, potentially leading to phishing or data exfiltration.
FIXValidate that the URL is a valid NotebookLM URL (hostname notebooklm.google.com) and uses HTTPS. Use a URL parser to ensure proper format.
MEDIUM1 finding
src/http-wrapper.ts:527
527app.post('/notebooks/auto-discover', async (req: Request, res: Response) => {
528  try {
529    const { url } = req.body;
530    if (!url) { ... }
531    try {
532      const parsedUrl = new URL(url);
533      if (parsedUrl.hostname !== 'notebooklm.google.com') {
534        return res.status(400).json({ ... });
535      }
536    } catch { ... }
537    const autoDiscovery = new AutoDiscovery(sessionManager);
538    let metadata;
539    try {
540      metadata = await autoDiscovery.discoverMetadata(url);
541    } catch (error) { ... }
src/http-wrapper.ts:527

// Network-exposed MCP server; an attacker can send HTTP requests to this endpoint.

EXPLAINThe auto-discover endpoint validates that the URL hostname is 'notebooklm.google.com', but this check can be bypassed using URL parsing inconsistencies (e.g., using a different port, userinfo, or alternative representations). The URL is then passed to AutoDiscovery.discoverMetadata, which likely fetches the URL, enabling SSRF to internal services if the hostname check is bypassed.
IMPACTAn attacker could bypass the hostname check and force the server to make requests to internal network resources, leading to information disclosure or further attacks.
FIXUse a strict URL parser that normalizes the URL before validation. Validate the entire URL, not just the hostname. Consider using a blocklist for private IP ranges.
5/21/2026
Findings are produced by automated LLM analysis and may include false positives or miss issues. Verify independently before acting.