Skip to main content

Securing the HTTP API

The HTTP wrapper exposes 45 routes that read your notebooks, ask questions as you, and delete things. Treat the port as equivalent to your NotebookLM session, because that is what it hands out.

The defaults, as of 4.0.0​

ControlDefaultEffect
Bind address127.0.0.1Only this machine can reach the API
Cross-origindeniedA web page cannot call the API unless its origin is allow-listed
TokennoneNot required while the server is loopback-only

Running npm run start:http with no configuration is safe on a single-user machine: nothing off the machine can reach it, and no page you visit can read it.

Exposing it to other machines​

A bind wider than loopback requires a token. Without one the server refuses to start rather than serving your library to the network.

# Generate a token
openssl rand -hex 32

# Run exposed
HTTP_HOST=0.0.0.0 NOTEBOOKLM_API_KEY=<token> npm run start:http

Every route then requires the token:

curl -H "Authorization: Bearer <token>" http://host:3000/notebooks
# or
curl -H "X-API-Key: <token>" http://host:3000/notebooks

/health stays reachable without one so monitors and container health checks keep working, and answers them with {"status":"ok","version":"…"} only. The full report, which names your active notebook, needs the token.

The bundled stdio proxy and npm run doctor:http both send NOTEBOOKLM_API_KEY when it is set in their own environment.

Letting a browser page call the API​

Cross-origin requests are refused unless you name the origin:

NOTEBOOKLM_ALLOWED_ORIGINS=https://app.example,https://staging.example npm run start:http

There is no wildcard. A page served from an origin you did not list gets 403, whether it is reading or writing.

Files the caller can name​

Three parameters take a filesystem path: file_path on add_source, output_path on download_content, and vault_dir on the batch writer. An agent driving this server acts on text it was handed, so a poisoned document can tell it which file to add as a source. Those paths are treated as hostile.

Every one of them is resolved first, then checked. Symlinks are followed as far as the path exists, and containment is compared segment by segment, so a link pointing out of an allowed directory and a sibling like /data-evil are both refused.

The session store is never reachable, whatever else is allowed:

PathHolds
<data>/browser_state/the live Google session
<data>/chrome_profile/the logged-in browser profile
<data>/chrome_profile_instances/per-account profiles
<data>/accounts.jsonstored account credentials

That matters because the data directory is otherwise a legitimate place to read and write: downloads land there. Without the rule, add_source could upload your own Google session into a notebook, and share_notebook could then make it public.

By default a caller may name a path under the working directory or the data directory. Narrow it when the server runs anywhere untrusted:

VariableNarrows
NOTEBOOKLM_UPLOAD_ROOTwhere add_source may read
NOTEBOOKLM_DOWNLOAD_ROOTwhere download_content may write
NOTEBOOKLM_VAULT_ROOTwhere the batch writer may write

A vault is somewhere you chose, often outside the project, so vault_dir has no default containment — but it is still refused the session store.

Docker​

The container binds 0.0.0.0 — a published port is useless otherwise — so it requires NOTEBOOKLM_API_KEY and exits with an explanation if it is missing.

docker run -e NOTEBOOKLM_API_KEY=$(openssl rand -hex 32) -p 3000:3000 …

Reporting a vulnerability​

Use Security → Report a vulnerability on the repository. That channel is private until a fix ships.

Past advisories:

  • GHSA-p69m-c5rf-g2c3 — ≤ 3.2.0 bound 0.0.0.0, sent Access-Control-Allow-Origin: *, and required no credentials, so any page the user visited could drive the API. Fixed in 4.0.0.
  • CVE-2026-61647 — path traversal in the vault batch writer (affected 1.6.0 – 2.0.2). Fixed in 2.0.3.