Google's Notebook LM locks research into a single provider with no API access, limited podcast controls, and no self-hosting option. Teams that need data sovereignty, model flexibility, or automation have no equivalent.
What This Does
open-notebook is a self-hosted, multi-model alternative to Notebook LM. The Python backend (api/, open_notebook/) handles notebooks, sources, embeddings, chat, and podcast generation. The React/Next.js frontend (frontend/) provides the UI. Docker deployment is supported via docker-compose.yml and two Dockerfiles.
The system supports 18+ AI providers, multi-speaker podcasts, full-text and vector search, and a REST API for automation. It's forked from lfnovo/open-notebook (37k+ stars).
How It Is Wired
Execution starts in api/main.py at the lifespan function, which reaches 27 functions. The API layer (api/routers/) exposes endpoints that delegate to service files like api/notebook_service.py and api/chat_service.py. The core domain logic sits in open_notebook/domain/ with notebook.py as the central hub (25 modules import it, 7 import cycles).
The highest-blast-radius functions are repo_query (called from 58 places) and _make_request (53 places). open_notebook/database/repository.py owns database effects; open_notebook/utils/encryption.py handles secrets and file I/O; api/client.py makes outbound network calls. A typical flow: API request → repo_query → database connection, in 2-3 hops.
The module graph shows a cycle involving open_notebook/ai/models.py and open_notebook/domain/credential.py — changing either risks breaking the other. open_notebook/exceptions.py is a stable hub (26 dependents) with zero instability.
How To Use It
Setup: Clone and run with Docker:
git clone https://github.com/moses-y/open-notebook
cd open-notebook
cp .env.example .env
docker compose up -d
Configuration: Set provider API keys in .env (e.g., OPENAI_API_KEY, ANTHROPIC_API_KEY). The .env.example file documents available variables.
Running it: For local dev, the Makefile has targets; the API entry point is api/main.py (FastAPI). Frontend runs via npm from frontend/.
Real-World Use
A research team self-hosts this to process internal PDFs and web pages. They configure Ollama for local embeddings and OpenAI for chat. Analysts upload sources, generate two-speaker podcast summaries, and query via the REST API to automate weekly briefings. All data stays on their infrastructure.
Code Health & Issues
Static analysis found 107 findings (23 high, 84 medium) across 7 categories:
High – Deep nesting (33 occurrences) in open_notebook/domain/notebook.py, base.py, credential.py; max indentation depth 9.
High – Import cycle members (7) including open_notebook/ai/models.py and domain/credential.py.
High – Hub modules (7) with high blast radius: exceptions.py (26 dependents), notebook.py (25).
High – Duplicated code: 282 repeated 6-line blocks across 90 files.
Medium – Broad exception handling (8), high branching in frontend/src/lib/api/client.ts, oversized files (3).
Security audit: 2 critical findings in .github/workflows/claude-code-review.yml — the workflow checks out untrusted PR code with a privileged token (CLAUDE_CODE_OAUTH_TOKEN) exposed to fork-triggered jobs. 2 high: unpinned GitHub Actions (tag-based, not SHA) and a secret baked into the Dockerfile as ENV. 5 medium: no Dependabot, unpinned base image, no dependency scan, persist-credentials not disabled, no non-root user.
The Bottom Line
Functionally strong: a complete Notebook LM alternative with real model flexibility and a working API. The CI/CD security posture is not production-safe — the PR review workflow is a credential exfiltration risk and must be fixed before public contribution. Worth deploying for internal use after addressing the workflow issues.
What the analyser found
Deployment readiness
7/7
✓Container image
✓CI pipeline
✓Lockfile committed
✓Test suite
✓README
✓License
✓No committed secrets
Composition
465 files
TSX116
Python101
Markdown83
TypeScript79
YAML15
JSON4
ReactNext.jsDjangoExpressTailwindDocker
Architecture
Top-level areas of the codebase, sized by module count. Arrows show how many imports cross from one area into another.
Ranked by severity × confidence × production reach. Reach is the honest discriminator across a collection that is mostly other people's code: the same finding matters more in something that ships.
critical2
high2
medium5
low1
Do not check out pull request code in a privileged workflowcritical
.github/workflows/claude-code-review.yml
privileged trigger checks out the untrusted ref
This is the most reliably exploited Actions pattern: anyone opening a pull request gets code execution with your repository secrets.
Fix: Split into an unprivileged pull_request build and a workflow_run job that only consumes its artifact.
Keep secrets out of workflows a fork can triggercritical
.github/workflows/claude-code-review.yml
CLAUDE_CODE_OAUTH_TOKEN (publish or cloud scope)
Contributor-controlled input reaches a job holding a publish token or cloud credential, so a pull request becomes credential exfiltration.
Fix: Move secret-using steps into a workflow_run job that never checks out pull request code, or gate on an environment with required reviewers.
Pin third-party GitHub Actions to a commit SHAhigh5 occurrences
A tag can be moved, so the action running with your token and secrets is whatever its owner last pushed; this is how tj-actions/changed-files leaked secrets from thousands of repos.
Fix: Replace each @vN with the 40-character commit SHA, keep # vN as a comment, and let Dependabot bump the SHAs.
Stop passing the secret as a build argument or image envhigh
Dockerfile
ENV TIKTOKEN_CACHE_DIR with a baked value
ARG and ENV values are recorded in the image layers, so the credential is readable with docker history by anyone who can pull the image, and deleting the file in a later layer does not remove it.
Fix: Mount it at build time with --mount=type=secret, or inject it at run time.
Enable Dependabot or Renovatemedium
2 manifest(s), no update bot configured
Without a bot a published advisory sits unpatched until someone audits by hand, which across 1,322 repositories means never.
Fix: Commit .github/dependabot.yml covering the repo ecosystems plus github-actions.
Pin the container base image by digestmedium2 occurrences
An untagged or mutable base means today's build and last month's contain different libc and a different CVE set, with no record of which shipped.
Fix: Use image:tag@sha256:<digest> and enable Dependabot's docker ecosystem.
Gate pull requests on a dependency vulnerability scanmedium
.github/workflows
no dependency scan in CI
This is the one gate that would catch a known-vulnerable package before it reaches a build, and no repository in the sample had it.
Fix: Add dependency-review-action on pull_request, or osv-scanner on push and a schedule.
Set persist-credentials: false on checkoutmedium
.github/workflows/test.yml
checkout keeps the token, then dependencies are installed
The token stays in .git/config for every later step, so a malicious postinstall script reads a pushable credential without one ever being passed to it.
Fix: Add with: persist-credentials: false, and pass an explicit token only to the step that pushes.
Add a non-root USER to the imagemedium
Dockerfile
CMD or ENTRYPOINT with no USER directive
A process running as root in the container is root against every mounted volume, and it turns any container escape or writable-mount mistake from a contained problem into a host one.
Fix: Create an unprivileged user, chown what it needs, and end the Dockerfile with USER.
Set timeout-minutes on the workflow jobslow4 occurrences
.github/workflows/build-and-release.yml
4 workflow(s) declare no job timeout
A wedged step runs to the six-hour platform default, which on a two-hourly schedule means three runs overlap behind it.
Fix: Add timeout-minutes with a realistic bound to each job.
Checked deterministically against the repository tree and a bounded set of its files: committed credentials, unpinned actions and base images, missing lockfiles and update bots, workflows that discard failures, published advisories against the declared dependencies, runtime configuration, licensing and notebook reproducibility. No language model is involved in this section.