A secure-by-default web browser in pure C. Zero telemetry. Zero backdoors. Zero implicit trust.
Click the URL bar (or press Ctrl+L), type, and press
Enter or click Go.
example.com opens https://example.com (the scheme is added; http:// is upgraded to https).best linux distro runs a DuckDuckGo HTML search (the no-JS version). A dangerous scheme like javascript: is searched, never executed./path/file.html or a relative path opens it from disk.| Shortcut | Action |
|---|---|
Enter / Go | Open the address, or search the web if it is not a URL |
Ctrl+L | Focus the URL bar |
Ctrl+I | Toggle remote images (off by default — Privacy by Default) |
Ctrl+D | Distraction-free (reader) mode: drop nav/header/footer/aside + author styles, center the text |
Ctrl+P | Save the current page as a vector PDF (selectable text). Headless equivalent for automated/visual review: freedom --download-pdf=PATH <url-or-file> |
Ctrl+Shift+P | Save the current page as a single PNG image (full-height bitmap). Headless equivalent — the cheapest artifact for visual review, no PDF rasterise step: freedom --download-png=PATH <url-or-file> |
Ctrl+S | Save the current page to ~/Downloads/freedom/ |
Ctrl+R / F5 | Reload the current page (re-applies the full TLS/PQ policy). A reload button is also in the toolbar. |
Ctrl+Shift+B | Block this site: add the current host to block.conf (applies on the next fetch) |
Ctrl+Shift+A | Allow this site: add the current host to allow.conf (sovereignty override & omnibox favorite) |
Ctrl+Shift+J | Allow JavaScript here: add the current host to js.conf |
Ctrl+B | Bookmark the current page (toggle; the title comes from the page). Bookmarks and history are stored encrypted at rest in your profile; browse them at about:bookmarks (menu: “Bookmarks & history”). |
| Omnibar | Type to autocomplete from your bookmarks and history first, then your allow.conf favorites (Tab completes, ↑/↓ pick, Enter/click open). Edit with selection (Shift+arrows, Ctrl+A), cut/copy/paste (Ctrl+X/Ctrl+C/Ctrl+V). |
F12 | Freebug developer console (also in the hamburger menu): opens a second window with a log pane showing the page's console.* output + uncaught JS errors, over a resizable JS editor — type/paste JavaScript and run it with Ctrl+Enter against the live page (see the value, the error, and any console output). Each error is tagged with its source location file:line:col (e.g. inline #2:3:1 — the 2nd inline <script>, line 3, column 1), shown in a muted devtools-style link. Ctrl+L clears the editor; F12/Esc closes; drag the divider to resize. Headless equivalent: freedom --dump-console <url-or-file> prints the same console (with locations) without a window. |
--dump-dom | Render-tree dump (headless, layout debugging): freedom --dump-dom <url-or-file> prints the paint-ready tree — every block (kind, tag, container/flex/grid group, the container item it belongs to as item=N, box, author style) and the box-definition tree — in a deterministic, agent-readable form. The instrument for diagnosing why a node lays out wrong; add --author-css to populate the box tree. |
--dump-layout | Resolved-layout dump (headless, the layout-side counterpart to --dump-dom): freedom --dump-layout <url-or-file> prints the geometry the box engine actually produced — every line row (top, h, x_off, w) exposing the flex/grid column each line landed in, every in-flow box rect (x, top, w, h) and every out-of-flow positioned box (with z-index and stacking order). Where --dump-dom shows the structure (input), --dump-layout shows the rectangles (output): positioning/z-index/flex-distribution/fragmentation bugs are visible as plain numbers, no display or image needed. Add --author-css to populate the box tree. |
--dump-video-url | Video URL extractor (headless): freedom --js=on --dump-video-url <url> prints the full video source URL (the .m3u8 from the video pipeline) without truncation. Pipe it to --dump-video or ffplay. |
--dump-video=PATH | Video stream dumper (headless): downloads an HLS (.m3u8) or progressive (MP4/WebM) video URL and saves it to PATH (use - for stdout). Concatenates all HLS segments. Example: freedom --insecure --dump-video=- $(VID) 2>/dev/null | ffplay -i pipe:0 2>/dev/null. |
Ctrl++ / Ctrl+- | Zoom in / out (snaps to a 50–300% ladder) |
Ctrl+0 | Reset zoom to 100% |
Ctrl+C | Copy the focused field, or the page URL |
Ctrl+V | Paste into the focused field |
Ctrl+T | New tab |
Ctrl+W | Close tab |
Ctrl+Tab | Next tab |
Ctrl+Shift+E | Per-host exception for a weak-but-valid certificate (this session) |
j / k | Scroll down / up one line |
Space / b | Scroll down / up one page |
gg / G | Jump to top / bottom |
Home / End | Jump to top / bottom |
User-Agent and Accept-Language (a common Firefox-on-Linux identity) so every user looks the same. It never advertises "Freedom" on the wire. Override the User-Agent per session in the menu (top-right).Ctrl+I for images). Supported formats are PNG, JPEG, WebP and static GIF (the GIF decoder is Freedom's own bounded pure-C LZW — no giflib, less attack surface), decoded inside the sandboxed worker (never in the UI process); anything else shows a placeholder. Start with images on via FREEDOM_IMAGES=1. A page that uses images shows a tracking warning and a placeholder per image. Press Ctrl+I to see the logo above. Local pages get a file:// origin, so relative images load from disk — confined to the document's own directory (no ../ escape, no phone-home). Inline data: images (icons/logos/spinners embedded as <img src="data:image/png;base64,...">, extremely common on modern sites) decode with zero network request — the base64 payload is only ever sliced (not decoded) on the trusted side, then base64- and format-decoded inside the same confined worker as any fetched image (2026-07-16). An <img> with no plain src also falls back to its first srcset candidate, the shape most responsive-image and lazy-loading markup actually uses. Video playback (H.264/H.265) via FFmpeg in an isolated decoder process: <video> paint is wired to the GUI canvas and playback is paced by PTS against the wall clock (v2, 2026-07-19: a pure pacing brain regulates the whole pipeline through pipe backpressure — real-time speed, non-blocking audio, no UI freezes; microsecond PTS with per-segment time bases, first-segment frames kept, codecs drained only at end-of-stream; v2.1: the repaint is decoupled from the pipeline — heavy pages degrade to a slideshow while audio and the timeline stay real-time). Multiple <source> children are picked by type (HLS/MP4 preferred), media fallback content is suppressed, and page JS sees an identity-safe HTMLMediaElement facade (play()/canPlayType()/new Audio()) that never touches the network.off / allowlist / on, set in the menu, --js, or FREEDOM_JS) plus a per-host js.conf allowlist. When enabled, inline scripts run in the per-tab sandbox against a memory-safe writable DOM (document.title, textContent, createElement/appendChild, innerHTML — setter and bounded getter —, onload). Timers are real and async: setTimeout/setInterval fire with their actual delays on a virtual clock the trusted parent drives (the sandboxed worker has no real clock and cannot wake itself — anti-fingerprinting and anti-abuse by construction); chained timers render progressively, capped per page load. Click events are wired end-to-end: addEventListener('click', fn) and element.onclick = fn fire when you click the element, the worker mutates the live DOM, and the GUI repaints the result (links still navigate after the handler runs). Storage is in-memory and ephemeral, cookies are empty — JS runs without leaking your identity. Scripts read a real read-only location, and a JS-requested navigation (location.href = …) is re-checked against the full network policy by the trusted parent before it loads — a hostile or compromised script can never drive the browser off-policy. External (src) scripts run only for a host you trusted twice (in allow.conf and js.conf): the sandboxed worker never touches a socket — script bytes come from the trusted parent over the same gated channel as XMLHttpRequest/fetch, with the full network policy re-applied per script (host filter, realm routing, TLS-PQ) and a JavaScript Content-Type required (a 404 HTML page is never evaluated). Errors report their url:line:col in Freebug. For every other host, external scripts still never run. Real querySelector and a modern DOM surface: document.querySelector/querySelectorAll and element.matches/closest run through the same author-CSS selector engine as the stylesheets (identical matching from JS and CSS); elements expose parentNode/children/classList/style and DocumentFragment, with node identity cached so === works. The globals a modern site touches at startup are present but inert and identity-safe: matchMedia really evaluates width/height/orientation queries against the normalized 1920×1080 desktop identity (identity signals pinned: light scheme, fine pointer, hover — zero real-window leakage), IntersectionObserver delivers one synthetic isIntersecting:true entry per observe() in the deferred phase (scroll-reveal content that used to stay stuck at opacity:0 now renders; all values are synthetic constants), the other observers never fire, getComputedStyle returns "", performance.timing is a fixed epoch, and the viewport reads a fixed 1920 width — while window.open/postMessage stay undefined (no popups, no cross-frame messaging). Real homepages like google.com render; web-component apps (YouTube's Polymer shell) do not yet, since custom-element upgrade is unimplemented. A host you trust twice also gets in-memory session cookies: document.cookie is a real jar for that host so its consent/session JS works, but the cookies live only in RAM and are gone when you close Freedom (never written to disk — Zero Knowledge holds). Every other site sees an empty document.cookie. Google may still show its server-side "unusual traffic" wall (a botguard/IP decision on their end that session cookies do not defeat) — the address bar searches via the no-JS DuckDuckGo HTML, which renders cleanly. DuckDuckGo's JS SPA (duckduckgo.com/?q=…) builds its results only through async client-side scripts Freedom's synchronous model can't finish, so navigating there directly is transparently routed to the same no-JS HTML endpoint (same engine, real server-rendered results); Reload and Back re-apply the route. With JS off, <noscript> fallback content is shown..onion uses Tor only; .i2p uses I2P only — fail-closed, never leaked over clearnet.<style> blocks and inline style= — using a deliberately simpler subset: color, background, text-align, font-size, line-height, font-weight, font-style, text-decoration (underline/line-through/overline/none), display (incl. display:none), with type/class/id/group selectors plus the descendant (div p) and child (nav > a) combinators, attribute selectors ([type=text], [href^="https"], ~=/|=/$=/*= with the i case flag), !important, the sibling combinators (h2 + p, h2 ~ p) and a pseudo-class subset — :link (Zero Knowledge: no history, every link is unvisited), :root, :first-child/:last-child/:only-child/:nth-child(An+B)/:nth-last-child(), :checked/:disabled/:enabled; :visited and :hover/:focus/:active parse but never match (no CSS history sniffing, no interactive state), and unknown pseudos / all pseudo-elements fail closed. It never phones home: any value with url( and the network/font @-rules (@import/@font-face) are dropped, so author CSS can never trigger a fetch or a tracking beacon. CSS is hostile content: the parser is bounded (anti-DoS), fails closed, never executes native code (expression() and JS URLs are ignored) — var()/custom properties and calc() are supported as bounded, depth-capped text substitution and arithmetic, never a fetch — and it is fuzzed.display:flex / display:grid container takes its layout params (gap, justify-content, grid-template-columns/-rows, flex-wrap, row-gap, align-items/align-self) from the same cascade, so a <style> rule lays out columns — not just an inline style=. Items that overflow a wrapping flex row pack onto a new line, with row-gap (distinct from the column gap) spacing the lines and align-items/align-self positioning each item within its line (start/center/end). grid-template-columns resolves sized tracks (2026-07-11): fr weights (2fr 1fr gives a double-width first column) and fixed px tracks (150px 1fr is a fixed sidebar + fluid main) lay out for real, with repeat(N, ...) and minmax() (its max component) handled, and grid-column: span N places an item across N columns (wrapping to the next row when it does not fit, like CSS auto-placement). Because layout is structure, not styling, all of this renders even with author colors off (geometry opens no socket and leaks nothing — only author colors stay gated by the toggle). See examples/css-sheet-layout.html.float:left / float:right + clear): the classic two-column era layout — a floated sidebar, a floated main column, a clear:both footer beneath — now lays out side by side instead of stacking. The float band nests inside the open box stack, so a wrapping position:relative background panel stays in-flow and paints its background behind the columns (no more grey stripes showing through). Like flex/grid, layout is structure: the columns appear even with author colors off. v1: no text wrap around a lone float (the band is a self-contained row; the next non-floated block clears below it). See examples/float.html.flex-basis is sized to its measured content, not to an equal share of the row, so justify-content (space-between, center, …) finally has leftover space to distribute and a three-link nav bar shrink-wraps instead of spanning the page; item margin counts as main-axis space. The container now paints its own box (background, border, radius, padding, margin) with its items laid out inside it, and a flex/grid row nested in a max-width; margin:0 auto wrapper stays inside the wrapper. text-align inside a grid/flex cell centers within that cell. display:inline-block flows horizontally — a parent whose children are all inline-block lays out as an anonymous flex row, so nav bars, badge strips and button groups no longer stack one per line. Out-of-flow boxes shrink-wrap: position:absolute with width:auto sizes to its content (a right-anchored badge lands at the right edge instead of off-screen), and a position:relative wrapper holding only positioned children now generates its own box, so it paints, reserves its height and acts as their containing block (the hero-with-overlay pattern). Table cells generate boxes, so td { border: 1px solid } paints for real (with <th> centered by UA default), inline backgrounds (<code>, <mark>, highlighted spans) paint behind their own glyphs rather than smearing across the line, and <li> no longer inherits a paragraph’s 1em margin, so lists are tight like a browser’s. Still v1: text does not wrap beside a lone float, align-items centers against the tallest item rather than an explicitly taller container, and width:auto is not stretched between left+right.<link rel=stylesheet>): with Author styles on, Freedom fetches the page's external sheets the same Zero-Trust way it fetches trusted scripts: the sandboxed worker never touches a socket — each sheet is requested through the trusted parent, which re-applies the full network policy (tracker blocklist, realm routing, TLS-PQ) before any byte moves, serves style-only loads as GET exclusively, and never parses a wrong Content-Type as CSS. With the toggle off, nothing is fetched at all (Privacy by Default). This is what makes real sites (Hacker News' news.css class colors, for example) render as the webmaster styled them.allow.conf and with JS enabled via js.conf) automatically gets the full modern experience — author CSS and images on, plus the full JS it already had — no per-session toggles (where gated fetch/XMLHttpRequest can already move bytes, keeping images/CSS off adds no privacy; reader mode still wins, and a global js on alone is not trust). And the page loads like a modern browser does: a trusted-side preload scanner (pure, fuzzed) spots <link rel=stylesheet> / <script src> in the raw HTML and downloads them — and the page’s policy-approved images — in parallel (up to 4 threads) through the same policy-gated fetcher, while the sandboxed worker parses. The worker and its serial pipe protocol are untouched: the total wait tends to the max of the latencies instead of the sum, and a prefetch changes when bytes are fetched, never what.margin / padding / width / max-width / min-width / height / min-height / max-height): resolved through the same cascade (lengths in px, a bare 0, or em/rem, or viewport units vw/vh/vmin/vmax against the normalized 1920×1080 desktop — min-height:100vh heroes and calc(100vh - 80px) work with zero real-window leakage; % fails closed). The headline is the modern centered reading column — max-width + margin: 0 auto + horizontal padding — and an author margin-top/margin-bottom overrides the default block spacing. min-width widens a box past its content; height/min-height/max-height floor/cap/fix the painted box height. An author box can shrink content to unreadability, so the box model is gated behind the Author-styles toggle like the colors (Secure by Default). The CSS parser also resolves the values of border/border-radius, box-sizing, box-shadow (both outset and inset are now painted), outline, aspect-ratio (sizes the box height from its width when no explicit height is given) and grid item placement (grid-column/grid-row: span N) so a page using them no longer confuses the cascade — and since 2026-07-11 border-radius actually rounds (background, uniform border ring, shadow and outline), background-image paints for real — linear-gradient(), radial-gradient() and conic-gradient() (pure grammars that never fetch; conic paints as a 128-sector fan with from <deg>; stop positions in %/deg are honoured and a two-position stop makes a hard edge — pie charts and stripes work, 2026-07-19) and, since 2026-07-16, url(...) (fetched and decoded under the exact same policy as an <img> — caps.images, tracker/scheme checks, worker-confined decode — with background-size: cover/contain/auto and background-repeat honoured; background-position always starts top-left, which is also the CSS default) — and box-sizing: border-box is honoured (the declared width includes padding + border). Still not painted: multi-layer box-shadow, per-corner/elliptical border-radius, and grid-row: span N (row spans); position:relative/absolute/fixed with z-index stacking, per-item flexbox (flex-grow/shrink/basis/order/flex-wrap/align-items/align-self), and transform: translate()/scale()/rotate()/skew()/matrix() pivoted at transform-origin (a real Cairo affine matrix), are painted. Gradient text works: background-clip: text (+ -webkit-background-clip) with -webkit-text-fill-color: transparent fills the glyphs themselves with the element’s gradient (the Tailwind landing-page headline pattern; a transparent fill without a clip source stays visible — fail-visible), and filter: drop-shadow(dx dy blur color) casts a shadow of the composited group’s real alpha silhouette (rounded cards, PNG transparency) rather than its rect (2026-07-19). Glassmorphism works: backdrop-filter: blur(N) frosts what is already painted behind a panel, and rgba()/hsla() background alpha paints genuinely translucent fills (2026-07-19). Lengths throughout (including here) accept calc() and the math functions min()/max()/clamp() (whole-value or nested, same bounded units); the logical properties (margin-inline, padding-block, inset-inline, inline-size/block-size and their min-/max- forms) map to their physical equivalents; and the place-items/place-content/place-self shorthands, the two-value gap: <row> <col> and the font shorthand resolve through the same cascade (2026-07-10). pointer-events: none now removes an element from hit-testing (its links stop hovering/clicking), content-visibility: hidden skips painting while keeping the layout space, image-rendering: pixelated selects nearest-neighbour image scaling, and caret-color tints the caret of a focused form field. See examples/box-model.html.font-family (mapped to a generic family — serif / sans-serif / monospace / cursive / fantasy; exact font files are never matched and @font-face/url() are dropped, so it never fetches), text-transform (uppercase/lowercase/capitalize), font-variant: small-caps (approximated as uppercase), letter-spacing, word-spacing, text-shadow (single layer), opacity, vertical-align (sub/super), text-indent, white-space (nowrap/pre suppress wrapping), tab-size (carried, expands tabs in pre blocks), direction (carried as a record — v1 paint does not reorder words RTL), list-style-type (decimal/lower-alpha/upper-roman/disc/circle/square… changes the <li> marker), and list-style-position (inside/outside, carried). All but list-style-type are gated behind the Author-styles toggle. See examples/text-presentation.html and the full supported-vs-missing inventory in spec/css.md.min-width / height / min-height / max-height / aspect-ratio + box-shadow: inset: the box model is also extended in the v1 direction — a fixed height forces the painted box height, min-height/max-height floor/cap it, min-width widens a box past its content, and aspect-ratio: 16/9 sizes the box's height from its width when no explicit height is given (handy for video-shaped boxes). A box with ONLY one of these properties still triggers a box-def entry, so the painter sees it. box-shadow: inset is now painted too — the offset translates toward the opposite edge and the shadow stays inside the border box. See examples/css-pipeline-audit-2026-07-10.html for an end-to-end demo.visibility:hidden hides an element while still reserving its layout space (unlike display:none, which removes it); cursor:pointer shows the hand cursor on any element, not just links; text-overflow:ellipsis truncates a white-space:nowrap line that doesn't fit with an ellipsis; word-break/overflow-wrap split an unbreakable long word (a URL, say) instead of letting it overflow its box. overflow/-x/-y are recognised (so the cascade doesn't misparse them) but clipping isn't painted yet. See examples/visibility-and-text-wrap.html.@media is supported as a safe subset — with the dark theme on (and Author styles enabled), a page's @media (prefers-color-scheme: dark) rules apply automatically. screen/print and min/max-width queries evaluate against a fixed, normalized desktop width (no real viewport size leaks — anti-fingerprinting); not and unknown features fail closed. @media only chooses which local rules apply — it still never fetches.Ctrl+D): a clean reading view — nav/header/footer/aside boilerplate is dropped, author styling and images are turned off, and the text is centered in a comfortable reading column. Toggle again to restore. Your persistent image/CSS toggles are untouched.@font-face/url() are dropped), and never persists. If no font resolves it falls back cleanly to the previous renderer.0600 perms — nothing readable ever lands on disk. An explicit env override (FREEDOM_JS, FREEDOM_TOR_PROXY, FREEDOM_READER) still wins for that session.Ctrl+B, about:bookmarks): bookmark any page, and visited URLs feed the omnibox autocomplete. Both are stored only in the encrypted profile. Sovereignty: the “Remember history” menu toggle turns persistence off and forgets what was retained; a tampered or foreign profile fails closed (defaults apply, the file is never overwritten).~/Downloads/freedom/ instead of being parsed as a page; Ctrl+S saves the current page there too. The filename is derived fail-closed from the hostile Content-Disposition / URL (only [A-Za-z0-9._-], no path separators, no traversal, no hidden dot), the body is size-capped, and the file is written atomically with 0600 perms.Ctrl++ / Ctrl+- step through a 50–300% ladder; Ctrl+0 resets. The page reflows at the new size with no network round-trip.X25519MLKEM768) preferred.Ctrl+Shift+E for a session exception.mmap/mprotect requesting PROT_EXEC are killed, so it cannot stage native shellcode), and the worker is made undumpable (no core file, no foreign ptrace) so its secrets cannot be exfiltrated. The allowlist is deny-by-default, so io_uring is blocked — it is a seccomp-bypass primitive (its operations skip the filtered syscall entry), so it never enters the worker, not even for async I/O; async I/O lives only on the trusted side. The parent survives a compromised worker.[ Isolated Session: Local Storage Ephemeral / Isolated PID ]