Open sourcePersonal projectmacOS
Null
A browser where nothing is sent, nothing is stored and nothing is tracked, unless you explicitly choose otherwise.

Pinned sites and tabs in the left source list, recent notes as cards on the new-tab surface. The whole window is glass over macOS vibrancy.
Open source under MPL 2.0. macOS first. Tauri 2 + Rust + React, using the system WebView rather than a bundled engine. Not funded, not monetised, not for sale.
In simple terms
Null is a web browser I wrote for macOS. The name is the thesis: null is what a function returns when there is nothing to return, and that is the right default for a browser.
There is no account system, no sync service and no telemetry endpoint. It does not phone home on launch, does not check for updates unless asked, and sends no crash reports anywhere. Bookmarks, history, notes and settings sit on your own machine in files you can open with a text editor.
A panel lists every outbound request a page makes, grouped by origin, and you can block any origin from it. Blocked requests are still logged, so you see what was refused rather than only what got through.
The part I want on record: it shipped with a full AI layer, and I took it out.
The impact. Six rules about what the app may never do are enforced in the code and checked at review, so the privacy claim is a property of the build rather than a page in a policy. The strongest product decision in it was deleting a feature I had already shipped.
Why removing the AI made it a better browser
The feature was real, not a stub
Local models by default, your own API key held in the system keychain, chat grounded in the tab you were reading, summarise. It worked, and people liked it.
It quietly broke the one promise
A browser that can read the page for you is a browser with a reason to send the page somewhere. Every safeguard I kept adding was managing a risk that only existed because the feature did.
The useful half survived without it
What people actually wanted from the AI layer was to keep what they read. That is now page capture and notes written to markdown on your own disk, which needs no model at all.
It made the rules enforceable
Once nothing in the app wanted to send anything, "sends nothing by default" stopped being an aspiration and became an invariant you can fail a code review over.
What it is
The name is the thesis. null is what a function returns when there is nothing to return, and that is the correct default for a browser. Null is a macOS desktop browser built on Tauri 2 in Rust with a React and TypeScript interface, rendering through the system WebView, so pages look the way they would in Safari while the browser around them is written to different defaults.
There is no account system, no sync service and no telemetry endpoint. It does not phone home on launch, does not check for updates unless asked, and ships no crash reports anywhere. Bookmarks, history, notes and settings live on the machine in SQLite, plain markdown and localStorage, readable with sqlite3, grep or any text editor.
The part I want on record: it had AI, and I removed it
Null shipped a full AI layer. Local Ollama by default, bring-your-own Anthropic key in the OS keychain, chat grounded in the current tab, summarize, multi-turn conversation history in SQLite. Four milestones of work. It is gone, and the milestone list in the repo says so rather than pretending it never happened.
The reasoning is short. A browser that holds an API key is a browser you have to trust, and after living with it the only part that consistently earned its place was the part that needed no model at all: getting a clean copy of the page out of the browser and into somewhere else. So that is what is left. Null captures; you decide what reads it.
Invariant 3 was rewritten from “inference is local by default” to no inference in the browser, and migration 006 drops the conversation tables. Putting a model back, local or remote, now needs a decision recorded in PHILOSOPHY.md before any code. Deleting working software you spent months on is the judgment I am most pleased with on this project.
The six invariants
Not defaults. Invariants: code that violates one is a bug, and every pull request that touches networking or storage answers three questions from the diff alone. What does this store? What does this transmit? What does this remember?
- 1Zero telemetry. No analytics, no crash reporting to a server, no usage statistics, no phone-home of any kind.
- 2No default cloud connections. It must start up and browse without contacting any service beyond the site you asked for.
- 3No inference in the browser. It does not run or call a language model. It captures pages as markdown; you take that markdown wherever you like.
- 4Every outbound connection is visible. Through the Network Inspector, in real time, grouped by origin.
- 5Data lives with you. SQLite and plain markdown on disk. No mandatory sync, no cloud account.
- 6No dark patterns. No forced onboarding, no retention tricks, no notification spam.
Radical transparency, as a surface
The Network Inspector is a first-class panel rather than something buried in devtools. It streams every outbound request the browser makes, grouped by origin, and a shield next to any origin cancels its future requests at the webview layer. Subresources to a blocked origin still log, marked blocked, so you can see what was refused rather than only what got through. The buffer is capped and never persisted; the blocklist is the only part that survives a restart.




Capture, not inference
Notes is where the browser earns its keep. A note opens beside the page rather than covering it, autosaves as you type, and carries the page's URL as its source line. Saving a page runs Mozilla Readability and Turndown to get the article as markdown; saving a selection converts whatever is highlighted. Both run inside the tab's own WebView with vendored copies, so no network call of any kind is involved.
Every note is written twice: to SQLite, which is the index the list reads, and to ~/Documents/Null/ as a real markdown file with YAML front matter. The file is the copy that matters. It opens in Obsidian, it greps, and it is still readable long after you stop running Null. The sync goes both ways, and it refuses the dangerous cases: a file older than Null's own last write is treated as a stale mirror rather than an edit, and an empty file never erases a note, because editors save by truncate-then-write and reading mid-save must not eat the copy.
The engineering worth pointing at
- Chrome height is a constant. Opening a second tab or saving a first bookmark used to change it, which physically reflowed the page you were reading. Tab creation and tab resizing now consume one function,
contentRect(), so they cannot disagree about where the page goes. - Shortcuts are native menu accelerators rather than listeners in the shell. Not cosmetic: the shell and each tab are separate native webviews, so a shell listener stops receiving keys the moment you click into a page, which is exactly when Reload and Back matter.
- The privileged webview is pinned to its own origin. A strict CSP on the shell,
http/https-only tab navigation refused at the IPC boundary, and a navigation guard so a remote link surfaced inside the shell can only ever open in a tab. Written up in SECURITY.md. - Favicons are captured, never fetched. Taken from pages as you visit them and validated in Rust, because fetching them would breach invariant 2. A hostname-derived letter mark stands in until the first visit.
- Extraction rides an image beacon. Readability and Turndown run in the tab and return through a custom
null-event://scheme as chunkedImage.srcrequests rather thanfetch, becauseimg-srcis broad whereconnect-srcis locked down on exactly the sites worth clipping. - Split view and pin folders. Drag a tab or a pin out of the sidebar onto the page for a live drop target; drop one pin dead-centre on another to fold them, iOS-style. Deleting a folder re-roots its pins, so arrangement is never a place data can be lost.
- Session restore that costs nothing. Restored tabs come back dormant, as rows with no webview, and load when selected. Twenty restored tabs cost one page load.
Where it is
M0 through M2, M6, M7 and M8 are done: browsing, bookmarks and history; the Network Inspector with subresource capture and per-origin blocking; shell hardening; the sidebar navigation rebuild; and the Zen-informed redesign that brought glass, split view, the Notes editor, pin folders, popups, downloads, per-tab zoom and find.
Next is subresource blocking through WebKit's own WKContentRuleList, a command bar that searches notes, bookmarks and history together, and FTS5 search over what you have actually seen. A SearXNG provider already exists in the Rust backend with no interface in front of it, because the search view was part of the AI drawer that was removed. It gets a UI or it gets cut.
Tauri 2 · Rust · React 19 · TypeScript · Tailwind v4 · SQLite · WebKit · MPL 2.0
What Null is not
Not a Chromium fork, because one maintainer cannot keep up with Chromium. Not an AI browser: it captures, it does not think. Not a product, not funded, not monetised, not for sale. Not a competitor to Chrome or Safari or Firefox, and it does not need to displace them to matter. Not for everyone. It is for people who would rather have control than convenience.