diff --git a/.github/ISSUE_TEMPLATE/01-bug-report.yml b/.github/ISSUE_TEMPLATE/01-bug-report.yml new file mode 100644 index 00000000..132de05c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01-bug-report.yml @@ -0,0 +1,123 @@ +name: Bug report +description: Report reproducible behavior that is not working as expected +title: '[Bug]: ' +labels: + - bug +body: + - type: markdown + attributes: + value: | + Thanks for helping improve Freedom. Do not include seed phrases, private keys, tokens, credentials, private URLs, or unredacted sensitive logs. + + - type: checkboxes + id: preflight + attributes: + label: Preflight + options: + - label: I searched the existing issues for this problem. + required: true + - label: I read the [contribution guidelines](https://github.com/solardev-xyz/freedom-browser/blob/main/CONTRIBUTING.md). + required: true + + - type: input + id: freedom-version + attributes: + label: Freedom version + description: Find this under Menu → About. For a source build, include the commit SHA. + placeholder: 0.8.0 or 0123abcd + validations: + required: true + + - type: dropdown + id: install-source + attributes: + label: Installation source + options: + - GitHub release + - Built from source + - Other or unofficial package + validations: + required: true + + - type: dropdown + id: operating-system + attributes: + label: Operating system + multiple: true + options: + - macOS + - Windows + - Ubuntu + - Other Linux + - Other + validations: + required: true + + - type: input + id: system-details + attributes: + label: System version and architecture + description: Include the OS version, CPU architecture, and Wayland or X11 on Linux. + placeholder: macOS 26.1 arm64; Windows 11 x64; Ubuntu 26.04 x64 Wayland + validations: + required: true + + - type: textarea + id: target + attributes: + label: What were you trying to open or do? + description: Describe the feature, protocol, name, or sanitized content reference involved. + placeholder: I opened an ipfs:// URL from the address bar and… + validations: + required: true + + - type: dropdown + id: node-mode + attributes: + label: Relevant node configuration + options: + - Default managed node + - External or system node + - Not applicable + - Not sure + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Give the smallest sequence that consistently demonstrates the problem. + placeholder: | + 1. Open… + 2. Enter… + 3. Observe… + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual behavior + description: Include the exact visible error or failure mode. + validations: + required: true + + - type: input + id: last-working-version + attributes: + label: Last known working version + description: Leave blank if this never worked or you do not know. + + - type: textarea + id: diagnostics + attributes: + label: Sanitized logs, screenshots, or other context + description: Remove credentials, keys, personal paths, private URLs, and other sensitive data before attaching anything. diff --git a/.github/ISSUE_TEMPLATE/02-feature-request.yml b/.github/ISSUE_TEMPLATE/02-feature-request.yml new file mode 100644 index 00000000..2a430533 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02-feature-request.yml @@ -0,0 +1,69 @@ +name: Feature request +description: Propose an improvement grounded in a concrete user or developer need +title: '[Feature]: ' +labels: + - enhancement +body: + - type: checkboxes + id: preflight + attributes: + label: Preflight + options: + - label: I searched the existing issues for this request. + required: true + - label: I read the [contribution guidelines](https://github.com/solardev-xyz/freedom-browser/blob/main/CONTRIBUTING.md). + required: true + + - type: textarea + id: problem + attributes: + label: Problem or use case + description: Who needs this and what can they not do today? + validations: + required: true + + - type: textarea + id: outcome + attributes: + label: Desired outcome + description: Describe the behavior or result, not only a proposed implementation. + validations: + required: true + + - type: dropdown + id: area + attributes: + label: Area + options: + - General browser UI + - Swarm + - IPFS or IPNS + - Radicle + - Naming and resolution + - Wallet or dApp integration + - Privacy, permissions, or security + - Downloads or storage + - Build, packaging, or developer tooling + - Documentation + - Other + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Include workarounds or related designs you evaluated. + + - type: textarea + id: context + attributes: + label: Additional context + description: Add examples, screenshots, standards, or links that help explain the request. + + - type: checkboxes + id: contribution + attributes: + label: Contribution + options: + - label: I may be able to help implement or test this. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..0086358d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..0fc97a9b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,29 @@ +## Summary + + + +## Related issue + + + +## Verification + + + +- [ ] `npm run lint` +- [ ] Relevant unit tests +- [ ] Relevant Playwright or live smoke tests +- [ ] `npm run format:check` +- [ ] `git diff --check` + +## Visual changes + + + +## Security and privacy + + + +## AI assistance + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29246f60..6c46c3c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: npm - name: Install dependencies @@ -66,7 +66,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: npm - name: Install dependencies @@ -102,7 +102,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: npm - name: Install dependencies @@ -360,7 +360,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: npm - name: Install Linux native USB build dependency @@ -430,7 +430,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: npm - name: Install Linux native USB build dependency @@ -484,7 +484,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: npm - name: Install Linux native USB build dependency @@ -536,7 +536,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 cache: npm - name: Install Linux native USB build dependency diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..8dfc5cb1 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24.18.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..8df569b3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,60 @@ +# Contributing to Freedom Browser + +Thanks for helping build a browser for the decentralized web. Contributions to code, tests, documentation, issue triage, and reproducible bug reports are welcome. + +## Before you start + +- Search the [issue tracker](https://github.com/solardev-xyz/freedom-browser/issues) before opening a report or proposing work. +- For a bug fix, comment on the relevant issue or open a structured bug report. +- For a feature, major refactor, dependency change, or architectural change, open an issue first so maintainers and contributors can agree on scope. +- Keep changes focused. Unrelated cleanup makes review and regression analysis harder. + +## Set up the project + +Freedom uses Node.js 24 LTS. Follow the [development guide](docs/development.md) to install the pinned version, dependencies, and native node binaries. + +Create a branch from an up-to-date `main` branch. Match the naming and organization of nearby code, and read the repository's [architecture boundaries](docs/agent-playbooks/architecture-boundaries.md) before changing process responsibilities or IPC. + +Do not add or upgrade dependencies without maintainer approval. Dependencies affect the browser's attack surface, native builds, package size, and license obligations. + +## Make and verify changes + +- Add or update tests for behavior changes and regressions. +- Run `npm run lint` after code changes and fix introduced errors or warnings. +- Run `npm test` when modifying files with corresponding `.test.js` files. +- Run the relevant Playwright or live smoke suite when the change affects UI flows, protocols, or native nodes. +- Run `npm run format:check` and `git diff --check` before submitting. +- Never commit secrets, API keys, tokens, private keys, seed phrases, unredacted personal data, or signing credentials. + +The cross-platform CI matrix runs additional native and integration checks. If you cannot run one of those locally, state that clearly in the pull request rather than marking it as completed. + +## Commits and pull requests + +Use Conventional Commit subjects: + +```text +(): +``` + +Common types are `feat`, `fix`, `docs`, `test`, `refactor`, `chore`, `build`, `ci`, and `perf`. Explain why a non-trivial change is needed in the commit body. + +Pull requests should: + +- Link the relevant issue. +- Explain the problem and the chosen approach. +- Describe user-visible impact and risk. +- List the checks actually run. +- Include screenshots or recordings for visible UI changes. +- Remain available for review questions and follow-up changes. + +Draft pull requests are welcome for early feedback. Maintainers decide whether and when a contribution fits the product roadmap. + +## AI-assisted contributions + +AI assistance is allowed, but a human must remain accountable for every submission. You must review and understand everything you submit, verify generated claims and tests, and be able to explain and maintain the result. Disclose material AI assistance in the pull request. Unreviewed automated submissions or bot-only conversations may be closed. + +## Licensing + +Freedom Browser is licensed under the [Mozilla Public License 2.0](LICENSE). Unless explicitly agreed otherwise, submitted contributions are made available under MPL-2.0. Only submit work you have the right to contribute, preserve applicable notices, and do not copy material with incompatible license terms. + +If a report may expose a security vulnerability, do not include exploit details, keys, credentials, or sensitive user data in a public issue. Contact `browser@freedom.baby` before public disclosure. diff --git a/README.md b/README.md index e4ac242a..770b7ea9 100644 --- a/README.md +++ b/README.md @@ -1,861 +1,87 @@ # Freedom Browser -[![License: MPL-2.0](https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg)](https://opensource.org/licenses/MPL-2.0) +[![CI](https://github.com/solardev-xyz/freedom-browser/actions/workflows/ci.yml/badge.svg)](https://github.com/solardev-xyz/freedom-browser/actions/workflows/ci.yml) +[![License: MPL-2.0](https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg)](LICENSE) [![Platform](https://img.shields.io/badge/platform-macOS%20|%20Linux%20|%20Windows-lightgrey)](https://github.com/solardev-xyz/freedom-browser/releases) -Freedom is a browser for the decentralized web, with Swarm, IPFS, Radicle, ENS, and Tezos Domains as first-class protocols. -It ships with integrated Swarm, IPFS, Radicle, and experimental Myotis nodes, enabling direct peer-to-peer network access without relying on centralized HTTP gateways. Radicle is available on macOS and Linux; the Windows build ships without Radicle until official Windows binaries are published upstream. +Freedom is a browser for the decentralized web, with Swarm, IPFS, Radicle, ENS, and Tezos Domains as first-class protocols. Integrated Ant, freedom-ipfs, Radicle, experimental Myotis, and Tor components provide direct access to decentralized and onion networks without relying on centralized HTTP gateways. ---- +## Download -## Quick Start +Download the latest build for macOS, Linux, or Windows from [GitHub Releases](https://github.com/solardev-xyz/freedom-browser/releases). -1. **Install Node.js 18+** +Radicle is available on macOS and Linux. The Windows build ships without Radicle until upstream publishes official Windows binaries. -2. **Install dependencies:** +## What Freedom supports - ```bash - npm install - ``` +- Native `bzz://`, `ipfs://`, `ipns://`, and `rad://` navigation, plus optional `.onion` routing through Tor. +- Integrated Ant (Swarm), freedom-ipfs, Radicle, experimental Myotis, and Tor components with per-profile configuration. +- ENS, WNS, GNS, and Tezos Domains resolution, including `.eth`, `.box`, `.wei`, `.gwei`, and `.tez` names. +- Tabs, sidebar, bookmarks, history, downloads, find-in-page, shortcuts, themes, permissions, and automatic updates. +- Ad blocking with signed list updates and per-site allowlisting. +- Wallet and dApp flows, x402 payments, hardware-wallet support, and Swarm/Radicle provider APIs. +- Custom protocol origins so decentralized applications can use relative assets, storage, service workers, and range requests naturally. -3. **Download the node binaries (first time only):** +See the [feature guide](docs/features.md) for the detailed capability list. - ```bash - npm run ant:download - npm run ipfs:download - npm run radicle:download - npm run myotis:download - ``` +## Run from source -4. **Launch the app:** - - ```bash - npm start - ``` - -5. Swarm and IPFS nodes start automatically by default. Myotis is an opt-in embedded Ethereum light client under **Settings → Automatic Startup**. To use `rad://`, first enable **Settings → Experimental → Enable Radicle integration (Beta)**. Enter a Swarm hash, IPFS CID, Radicle ID, `bzz://` URL, `ipfs://` URL, `rad://` URL, or `.eth`/`.box`/`.wei`/`.gwei`/`.tez` domain in the address bar. - ---- - -## Architecture - -Freedom Browser is an Electron application. Protocol logic lives in the main process; the renderer is a modular UI layer that talks to it over IPC (channels defined in `src/shared/ipc-channels.js`). The main process manages node lifecycles (`ant-manager.js`, `ipfs-manager.js`, `myotis/myotis-manager.js`, `radicle-manager.js`, `tor-manager.js`), URL rewriting (`request-rewriter.js`), and persistent data (settings, bookmarks, history). A central `service-registry.js` tracks node endpoints, modes, and status, and broadcasts state to all windows — both node managers and the request rewriter read from it. - -When a user enters a `bzz://`, `ipfs://`, `ipns://`, `rad://`, `.onion`, or ENS URL, the main process either dispatches to a custom protocol handler (`bzz`, `ipfs`, `ipns`) that proxies to the local node, rewrites the URL to the active gateway URL via the registry (`rad`), or routes `.onion` hosts through the active profile's Tor SOCKS5 endpoint. `rad://` handling is gated by the Radicle integration setting, and `.onion` routing is gated by the Tor integration setting. `bzz://` navigation is additionally gated by a cold-start probe (see next section). `ipfs://` / `ipns://` navigation goes straight to the native IPFS protocol handler, so no renderer warm-up probe is needed. - ---- - -## Swarm Content Retrieval - -A fresh Swarm node pulls chunks on-demand through the DHT, and any individual chunk lookup can transiently fail with `HTTP 404` even when the content is healthy and peers are connected. Across a page with 10–30 sub-resources (JS, CSS, fonts, images, video), a modest per-request failure rate compounds into visibly broken CSS, missing images, and videos that don't load. Retries almost always succeed — the problem is strictly first contact with cold content. - -Freedom mitigates this in two layers: - -1. **Navigation probe** (`src/main/swarm/swarm-probe.js`) — before loading a `bzz://` URL, the main process HEAD-polls `/bzz/` on the local node's gateway with exponential backoff (30 s per attempt, 5 min overall). The tab spinner runs during the probe, so the user never sees the node's raw 404 JSON; on timeout or node-unreachable we route to `error.html` with the original URL preserved and a **Try Again** button. Probes are cancellable. - -2. **Custom `bzz:` scheme** (`src/main/swarm/bzz-protocol.js`) — `bzz` is registered as a privileged standard scheme, so `bzz:///` becomes the page origin in Chromium. Every `bzz://` request (top-level, sub-resources, `fetch`, media `Range`, CSS descendants, service workers) routes through a main-process handler that proxies to the local node's gateway, always sets `Swarm-Chunk-Retrieval-Timeout` + `Swarm-Redundancy-Strategy: 3` + `Swarm-Redundancy-Fallback-Mode: true`, retries transient `5xx` on idempotent methods with bounded exponential backoff (~50 s total) with a 30 s per-attempt deadline, and streams the response back. `404` responses are surfaced immediately so SPAs that feature-detect missing endpoints don't stall — the cold-start case that 404 retries used to absorb is now handled upstream by the navigation probe. Because `bzz:///` is the origin, same-origin relative paths (`/foo.js`, `url(/bg.png)`) resolve naturally with no URL rewriting. - -The handler also accepts Ethereum-named hosts: `bzz://swarm.eth/...`, `bzz://site.wei/...`, and `bzz://apoorv.gwei/...` resolve the contenthash via the in-process name resolver (cache hit after address-bar resolution) and proxy the same way. The page's URL/origin stays `bzz:///` rather than the resolved hash, so DevTools, `window.location`, storage, and subresource fetches like `fetch('bzz://swarm.eth/data')` see the name. Cross-transport mismatches (e.g. `bzz://name.eth`, `bzz://name.wei`, or `bzz://name.gwei` whose contenthash is IPFS) return `404` with an explanatory body — the typed scheme is treated as an assertion. The renderer's address-bar pipeline applies the same assertion before navigating, so both layers agree. - -> **Origin model.** `bzz://swarm.eth` and `bzz://` are different origins from Chromium's perspective — cookies, localStorage, IndexedDB, and service workers are not shared between them. This mirrors HTTPS, where `https://example.com` and `https://192.0.2.1` are also different origins even when they resolve to the same server. Pinning storage to the ENS name keeps state stable across contenthash updates. - -### Migrating Swarm sites to the `bzz://` scheme - -Versions of Freedom before this change loaded `bzz:///path` by rewriting it to the gateway URL `http://127.0.0.1:1633/bzz//path` and navigating there. Pages saw `window.location.protocol === 'http:'` and `window.location.pathname === '/bzz//path'`. - -With the custom scheme, pages now see: - -- `window.location.protocol === 'bzz:'` -- `window.location.host === ''` -- `window.location.pathname === '/path'` (the `/bzz//` prefix is gone — it's encoded in the host) - -Most sites work without changes — relative URLs (`./assets/...`, `/foo.js`, `url(bar.png)`) resolve naturally because `bzz:///` is the origin. Sites break only when they sniff `window.location` to construct absolute gateway URLs. - -**Anti-pattern 1: protocol/pathname sniffing** (e.g. tile servers, Leaflet maps): - -```js -// ✗ Old pattern — assumes the page is served from a gateway URL -const urlServer = window.location.protocol === 'http:' ? '' : 'http://localhost:1633'; -const bzzMatch = window.location.pathname.match(/^\/bzz\/([\dA-Fa-f]{64})\//); -const bzzRoot = bzzMatch ? `/bzz/${bzzMatch[1]}/` : ''; -const tileUrl = `${urlServer}${bzzRoot}{z}/{x}/{y}.png`; - -// ✓ New pattern — relative URLs work from any origin (gateway or bzz://) -const tileUrl = './{z}/{x}/{y}.png'; -``` - -**Anti-pattern 2: appending `/bzz//` to `window.location.origin`** (e.g. SPAs fetching feeds, manifests, or other Swarm refs from JS): - -```js -// ✗ Old pattern — works under http://127.0.0.1:1633, builds garbage under bzz:// -// becomes: bzz:///bzz// → 404 -const feedUrl = `${window.location.origin}/bzz/${feedRef}/`; - -// ✓ New pattern — point at the content ref directly with the bzz:// scheme -const feedUrl = `bzz://${feedRef}/`; - -// ✓ Or, to support both gateway and native scheme modes: -const feedUrl = - window.location.protocol === 'bzz:' - ? `bzz://${feedRef}/` - : `${window.location.origin}/bzz/${feedRef}/`; -``` - -**When you really need an absolute URL to your own bzz root:** - -```js -const bzzRoot = - window.location.protocol === 'bzz:' - ? `bzz://${window.location.host}/` - : `/bzz/${window.location.pathname.match(/^\/bzz\/([\dA-Fa-f]{64})\//)?.[1]}/`; -``` - -Don't hardcode `http://localhost:1633` — Freedom users may have their Swarm node on a different port, and external visitors via a public Swarm gateway certainly do. - ---- - -## IPFS / IPNS Content Retrieval - -`ipfs` and `ipns` are registered as privileged standard schemes (`src/main/ipfs/ipfs-protocol.js`), mirroring how `bzz` is wired up. Every `ipfs:///...` and `ipns:///...` request — top-level navigation, sub-resources, `fetch`, media `Range`, CSS `url(...)`, service workers — flows through a main-process handler that calls the embedded `freedom-ipfs` native request API and streams the response back. Because `ipfs:///` (or `ipfs:///`) is the page origin, `window.location.protocol === 'ipfs:'`, same-origin relative paths Just Work, and storage (cookies, localStorage, IndexedDB, service workers) is keyed to the content reference. - -The native request API accepts the same `/ipfs/...` and `/ipns/...` gateway-path shape, but Freedom no longer starts a loopback Kubo gateway or HTTP API for desktop IPFS. Status and progress shown in the UI come from native `freedom-ipfs` diagnostics. - -ENS-named hosts work the same way as for `bzz`: `ipfs://vitalik.eth/...` resolves the contenthash via the in-process ENS resolver (cache hit after the address-bar resolution) and proxies the same way. The page's URL/origin stays `ipfs:///` rather than the resolved CID. Cross-transport mismatches (e.g. `ipfs://name.eth` whose contenthash is Swarm or IPNS) return `404` with an explanatory body — the typed scheme is treated as an assertion, matching the `bzz` handler's behaviour. Unlike `bzz`, the IPFS handler doesn't wrap its requests in a retry loop, and `4xx` / `5xx` responses pass through to the page so SPAs that feature-detect missing endpoints can render their own fallback. - -> **Origin model.** Same as `bzz`: `ipfs://vitalik.eth` and `ipfs://` are different origins from Chromium's perspective. Pinning storage to the ENS name keeps state stable across contenthash updates. - -### CID & IPNS-key canonicalisation - -Because `ipfs:` and `ipns:` are standard schemes, Chromium's URL parser treats the host segment as a hostname and lowercases it. The base58btc encodings used by CIDv0 (`Qm...`), CIDv1 base58btc (`z...`), and IPNS peer-ID multihashes (`12D3Koo...`, `16Uiu2H...`, `Qm...`) are case-sensitive, so a naïve `ipfs://Qm.../path` would arrive at the protocol handler as `ipfs://qm.../path` — different bytes, and the IPFS backend rejects it. - -The address-bar / load pipeline in `src/renderer/lib/url-utils.js` (`formatIpfsUrl` → `parseIpfsInput`) canonicalises on the way in, before `new URL` sees the input: - -- CIDv0 `Qm...` → CIDv1 base32 `bafy...` -- CIDv1 base58btc `z...` → CIDv1 base32 `b...` -- base58btc IPNS peer ID → libp2p-key base36 (`k51...` for Ed25519, `k2k4...` for sha2-256) - -All target encodings are lowercase, so subsequent host normalisation by Chromium is a no-op. DNSLink names (`docs.ipfs.tech`) and ENS names (`vitalik.eth`) fall through unchanged — they're not base58btc and don't suffer the case issue. The encoders live in `src/renderer/lib/cid-utils.js` (kept inline because the renderer has no bundler). - -If you click a `` link inside a page (rather than typing into the address bar), the webview preload intercepts the click in capture phase and reads the raw DOM attribute (`getAttribute('href')`) before Chromium resolves and lowercases the URL. It then sends that original mixed-case href to the host renderer, which routes it through `formatIpfsUrl`, so embedded link clicks canonicalise the same way address-bar input does. The interceptor covers same-tab clicks (`click` event) **and** modified-click / middle-click / `target="_blank"` / named-target dispositions (`auxclick` for real middle-click, since modern Chromium dispatches `click` only for the primary button) — without that, the new-window code path (Chromium → `setWindowOpenHandler` → `tab:new-with-url`) would receive the URL after Chromium had already lowercased the host, and case-sensitive bytes would be lost. The renderer's `link:navigate` IPC handler dispatches by disposition: same-tab calls go through `loadTarget`, new-tab calls go through `openInNewTabWithTarget` (the same helper the IPC `tab:new-with-url` path uses), so a `target="docs"` named target reuses an existing `docs` tab the way it does for plain HTTPS links. - -Direct sub-resource fetches (``, `