SurfSense is a desktop app in the spirit of Google's NotebookLM that keeps your documents on your machine. Drop in files or folders and an AI agent answers questions with citations, and it goes further than NotebookLM by working on the files themselves: redlining a Word contract with tracked changes, filling PDF forms, building spreadsheets with formulas and charts, and producing slides, web pages, mind maps, flashcards and podcasts. Edits always produce a new file, so the original is never changed.
You choose the model: a local one (it can download GGUF models from Hugging Face and run them itself) or any OpenAI-compatible API. Network access is off by default and granted per destination, so nothing leaves the machine until you allow a specific host. Recordings are transcribed locally and scanned pages go through OCR.
The app is Apache-2.0, with about 16,600 stars, at v2.2.4. The hosted web version shut down on 18 October 2026, so it is now a desktop app for Windows, macOS on Apple Silicon and Linux. A set of first-party plugins for reading Google, YouTube, TikTok, Amazon and other sites sits under a separate source-available licence and needs a SurfSense licence, with a free 30-day trial.
- Repository: github.com/MODSetter/SurfSense
- Licence: Apache-2.0 (Apache License 2.0)
- Language: Python. Stars: 16.6K. Forks: 1,596. Last push: Oct 25, 2026.
- Scan: safe, Oct 26, 2026, commit c2231c5
Who it is for
People who work with documents they cannot upload to a cloud service, such as contracts, client files or research notes, and anyone who wants NotebookLM-style answers from a local model.
Getting started
1. macOS or Linux: install the desktop app from its GitHub release
curl -fsSL https://www.surfsense.com/install.sh | sh2. Windows (PowerShell)
irm https://www.surfsense.com/install.ps1 | iexBoth scripts download from the project's GitHub release and check every file against its SHA256SUMS; the Windows one also checks the installer's signature. On Linux they use sudo to add an apt repository or install the rpm, or fall back to an AppImage in your home folder. Ordinary installers are on surfsense.com/downloads.
Safety scan
We cloned MODSetter/SurfSense at commit c2231c5 on Oct 26, 2026 and ran the checks described on the GitHub Tools page: credential patterns, decode-and-execute code, install-time scripts, committed binaries, risky CI workflows, every host the code talks to, known vulnerabilities in pinned dependencies, and project hygiene. A person read every hit. This is what we found.
- The one secret hit is YouTube's public InnerTube web key in backend/plugins/proprietary/youtube/innertube.py, the key YouTube's own site sends to every visitor; the code labels it as such. The 4 bare-IP URLs are tests of a guard that blocks private addresses.
- Network access goes through one consent check (docs/architecture/egress.md): every outside host is off until you allow it. Per the project's telemetry contract, usage statistics are opt-in and unticked, and update checks carry the app version and platform but no identifier.
- The four pattern hits are the curl | sh installer and comments about it. surfsense_local/packaging/install/install.sh verifies downloads against SHA256SUMS and uses sudo only to add the apt repository or install the deb or rpm.
- backend/plugins/proprietary holds the licensed plugins (Google Search and Maps, YouTube, TikTok, Instagram, Amazon, Walmart, Indeed, Reddit, web crawl) under the Business Source License, with a licence check through Keygen. The scraping code is built to use proxies and captcha-solving services (CapSolver or 2Captcha), which may conflict with those sites' terms.
- 144 advisories, 12 critical. Most are in the desktop backend's surfsense_local/backend/uv.lock (74, 6 critical, mainly Pillow image-decoding bugs and certifi), which ships with the app; the rest are in the Electron shell, the front end and the website. 13 workflows, none using pull_request_target; 2 of 27 third-party actions pinned; security policy and code of conduct present.
What the scanner counted
| Check | Result |
|---|---|
| Secrets | 1 candidate found and read; see the notes above. |
| Suspicious code | 4 pattern hits found and read; every one is listed under the raw findings. |
| Install-time code | 2 npm lifecycle scripts. 2 installer scripts (one fetches and runs a remote script; one can call sudo) |
| Committed binaries | None. |
| CI workflows | 13 workflows. None use pull_request_target. 25 of 27 third-party actions pinned to a tag rather than a commit. |
| Network hosts | 40 distinct hosts referenced from source; most often e.com, www.tiktok.com, www.google.com, www.amazon.com. 4 URLs to a bare IP address, listed under the raw findings. |
| Known vulnerabilities | 144 advisories across 1,974 pinned packages: 12 critical, 52 high, 64 moderate, 16 low. backend/plugins/bundles/core/cli/uv.lock: 21 packages, 1 advisories; backend/plugins/bundles/core/manifest/uv.lock: 13 packages, 0 advisories; backend/plugins/bundles/core/sdk/uv.lock: 11 packages, 0 advisories; backend/plugins/bundles/example/requirements.txt: 1 packages, 0 advisories; backend/plugins/registry/check/uv.lock: 39 packages, 0 advisories; backend/uv.lock: 94 packages, 1 advisories; surfsense_local/backend/uv.lock: 170 packages, 74 advisories; surfsense_local/electron/pnpm-lock.yaml: 441 packages, 27 advisories; surfsense_local/frontend/pnpm-lock.yaml: 1,010 packages, 35 advisories; www/pnpm-lock.yaml: 667 packages, 23 advisories. |
| Project hygiene | Has security policy, licence file, contributing guide. Missing automated dependency updates, CodeQL. |
| OpenSSF Scorecard | Not scored: the project is not in Scorecard's weekly index. |
The raw findings
Every hit the scanner wrote out, with a link to the exact line at the scanned commit. Secrets candidates are redacted.
Secret candidates (1, redacted)
| Where | Rule | Match |
|---|---|---|
| backend/plugins/proprietary/youtube/innertube.py:79 | google-api-key | AIzaSy…cW8 (39 chars) |
Pattern hits (4)
| Where | Rule | Match |
|---|---|---|
| surfsense_local/packaging/install/install.ps1:202 | download-piped-to-shell | throw "This script installs SurfSense on Windows, not $os. On macOS and Linux: curl -fsSL https://www.surfsense.com/install.sh | sh" |
| surfsense_local/packaging/install/install.sh:4 | download-piped-to-shell | # curl -fsSL https://www.surfsense.com/install.sh | sh |
| surfsense_local/packaging/install/install.sh:52 | download-piped-to-shell | # Under `curl ... | sh` stdin is the rest of this script, so no command reads it. |
| www/features/downloads/install-script-redirects.ts:2 | download-piped-to-shell | * The one-line installers: `curl -fsSL https://www.surfsense.com/install.sh | sh` |
URLs to bare IP addresses (4)
| Where | Rule | Match |
|---|---|---|
| backend/plugins/proprietary/web_crawl/tests/test_net_guard.py:19 | ip-literal-url | assert is_publicly_routable("https://8.8.8.8/") is True |
| backend/plugins/proprietary/web_crawl/tests/test_net_guard.py:59 | ip-literal-url | assert is_publicly_routable("http://100.64.0.1/") is False |
| backend/plugins/proprietary/web_crawl/tests/test_net_guard.py:65 | ip-literal-url | assert is_publicly_routable("http://224.0.0.1/") is False |
| surfsense_local/backend/tests/unit/llm/capability/test_capability_of.py:138 | ip-literal-url | "http://100.101.102.103:1234/v1", |
npm lifecycle scripts (2)
surfsense_local/electron/package.jsonpostinstall:install-electronwww/package.jsonpostinstall:fumadocs-mdx
Installer scripts (2)
- surfsense_local/packaging/install/install.ps1, 245 lines, fetches and runs a remote script; talks to github.com, www.surfsense.com
- surfsense_local/packaging/install/install.sh, 466 lines, uses sudo; talks to github.com, www.surfsense.com
Worst known vulnerabilities (24 of 144)
| Advisory | Severity | Package | Summary |
|---|---|---|---|
| GHSA-hq85-5cpj-mr62 | critical | uv@0.12.21 | uv's bundled CA store trusts an emSign root whose intermediate CA keys are compromised |
| GHSA-6q48-6c7w-9gw2 | critical | certifi@2026.7.22 | certifi bundles emSign root CAs with compromised intermediate CA keys |
| GHSA-332p-7hrc-92wf | critical | pillow@12.3.0 | Pillow: Heap buffer overflow in _tiffWriteProc when saving TIFF to BytesIO |
| GHSA-5x8w-8jj5-q52h | critical | pillow@12.3.0 | Pillow: Heap buffer overflow in FlashPix (FPX) JPEG decoding leads to code execution |
| GHSA-8g5g-8vmr-9782 | critical | pillow@12.3.0 | Pillow: Heap buffer overflow in ImagingJpegDecode |
| GHSA-v9pv-5gc8-w2m6 | critical | pillow@12.3.0 | Pillow multi-frame loaders can reuse stale native image storage after mode transitions |
| GHSA-w889-685w-c8fh | critical | pillow@12.3.0 | Pillow: Argument Injection in Ghostscript call, potentially leading into RCE |
| GHSA-pqg4-j6r4-53mv | critical | shell-quote@1.9.0 | shell-quote: `quote()` command injection via a line terminator in a token after a `{ comment }` token |
| GHSA-jqcg-44mw-7w3h | critical | proxy-addr@2.0.7 | proxy-addr vulnerable to IP spoofing via IPv4-mapped IPv6 trust subnet |
| GHSA-cg2g-xh64-v4q7 | critical | dompurify@3.4.16 | DOMPurify SAFE_FOR_TEMPLATES bypass: split template expressions survive in attached shadow root content on DOM output mo… |
| GHSA-2464-fhr4-w9xw | critical | next@16.3.6 | Next.js: Unauthenticated Remote Code Execution in Image Optimization API when SVG files are used |
| GHSA-673q-mjm6-r5xr | critical | next@16.3.6 | Next.js: Unauthenticated Remote Code Execution in `use cache` functions returning typed arrays |
| GHSA-x3q2-h9hx-4r4j | high | docling@2.125.0 | Docling has arbitrary file read/write (and command execution when shell-escape is enabled) when rendering untrusted TikZ… |
| GHSA-x3q2-h9hx-4r4j | high | docling-slim@2.125.0 | Docling has arbitrary file read/write (and command execution when shell-escape is enabled) when rendering untrusted TikZ… |
| GHSA-v4x8-3wr5-3wq8 | high | filelock@3.32.5 | filelock ReadWriteLock: SQLite reopens the lock database by name, so a planted symlink or journal file makes the lock us… |
| GHSA-22hg-r758-rrw9 | high | pillow@12.3.0 | Pillow: Infinite loop in MIC n_frames when the embedded TIFF |
| GHSA-27h8-gf7j-v5hc | high | pillow@12.3.0 | Pillow: NULL dereference in ImagingExpand via rank filters on zero-size images |
| GHSA-3j72-p6qg-6xp7 | high | pillow@12.3.0 | Pillow: TarIO negative size field causes infinite CPU-spin loop |
| GHSA-58r4-6rv3-gfh8 | high | pillow@12.3.0 | Pillow: Out-of-bounds access in _imagingmorph on a zero-width image |
| GHSA-5xm2-6676-qcp9 | high | pillow@12.3.0 | Pillow: APNG background disposal allocates full canvas before decompression bomb check |
| GHSA-65qm-jvmh-6pqg | high | pillow@12.3.0 | Pillow: GIF seek keeps rejected size after DecompressionBombError, allowing DoS |
| GHSA-78g4-xqwv-q3g8 | high | pillow@12.3.0 | Pillow: Bundled libopenjp2 2.5.4 in all wheels since 11.1.0 carries OSV-2025-219 heap-buffer-overflow write reachable fr… |
| GHSA-7mwm-ghpf-9vrm | high | pillow@12.3.0 | Pillow: Double free in ImagingScaleAffine on calloc failure via Image.transform |
| GHSA-9v9v-9qvf-h2hj | high | pillow@12.3.0 | Pillow: Heap over-read in WMF/EMF rendering via 16-bit dimension truncation (Windows builds) |
By the numbers
| Stars | 16.6K |
|---|---|
| Forks | 1,596 |
| Contributors | 128 |
| Commits | 11.7K |
| Open issues | 61 |
| Open pull requests | 7 |
| Releases | 43 |
| Latest release | v2.2.4 |
| Licence | Apache-2.0 |
| Main language | Python |
| Project age | 2 years |
| Last push | Oct 25, 2026 |
| Tracked files | 4,749 |
| Lines of code | 662.7K |
| Checkout size | 106 MB |
Lines by language: Python 279.3K, JSON 179.8K, TypeScript 126.2K, Markdown 52.7K, HTML 11.7K, JavaScript 6,566.
Questions
Is SurfSense free?
The app is free and open source under Apache-2.0, and works fully with your own local or remote model. The first-party web plugins (Google Search, YouTube, Amazon and others) need a SurfSense licence after a free 30-day trial; third-party MCP plugins need none.
Does SurfSense work offline?
Yes. With a local model it runs air-gapped, and the index stays on your disk. Anything that needs the network, such as downloading a model from Hugging Face, a remote model API or a plugin, asks for your permission for that specific host first.
What happened to the SurfSense web app?
The hosted web app shut down on 18 October 2026. The project asked users to export their workspaces before then and import them into the desktop app, which is where development continues.
This post is part of GitHub Tools, where every repository is cloned and scanned before it is written up. The scan is a snapshot of one commit on one day; the repository has moved on since, so check it before you install.
