The thesis: Slash is private and safe by default. Not a feature list bolted on later, the posture the browser ships with. This file is the build plan. Each item lists what it does, where it hooks into the codebase, the effort, and the gotchas. Order is by impact-over-effort, not by tier number.
Cross-cutting rule: every new UI surface (site-info, permission prompt, blocker
counter, clear-data, private window) follows DESIGN.md. Dark chrome, one
accent, borders-only, mono for machine facts.
Landed (2026-06-04): item 1 (DuckDuckGo default + searchEngine setting),
item 2 (API keys encrypted via safeStorage), item 3 (per-site permission
prompts + site-info omnibox button/popover), item 4 (HTTPS-only with fallback
interstitial), item 5 (shell hardened: sandbox/contextIsolation/nav-block/CSP
on all views), item 6 (EasyList/EasyPrivacy ad/tracker blocker via
@ghostery/adblocker-electron, with a shield button + per-page blocked count +
on/off toggle), item 8 (DoH), plus a full in-app settings surface exposing
search engine, the privacy toggles, and accent. Next up: item 7 (third-party
cookies + referrer), item 9 (clear-data + private window), item 14 (AI
send-page consent UI). The settings page now has homes for clear-data and a
private-window launcher when those land.
Slash ships with Chromium's background networking off. The one deliberate exception is Widevine DRM, on by default so paid streaming (Netflix, Spotify, Disney+, Prime) plays. For that, Chromium's component updater downloads the Widevine CDM from Google on first run and refreshes it periodically. That is the only component that contacts Google. Everything else stays off: no telemetry, no variations/Finch, no Safe Browsing pings, no sync, no account. The CDM only handles DRM playback; it does not see your browsing.
Flip the default engine from Google to DuckDuckGo. Make it a real setting, not a constant.
- Hooks:
ENGINES+normalizeInputinmain.js(currently falls back to Google), the hero engine row default, and a newsearchEnginefield inlib/settings.js. - Effort: small. Touches ~3 spots. Do it as "add a setting" so the choice is the user's.
Stop storing BYOK keys as plaintext JSON. Use safeStorage (Windows DPAPI /
macOS Keychain / libsecret).
- Hooks:
lib/settings.js. On write,safeStorage.encryptStringthen base64; on read, base64 ->safeStorage.decryptString. Guard withsafeStorage.isEncryptionAvailable(). Migrate existing plaintext keys on first read (decrypt-or-passthrough). - Effort: small. Highest embarrassment-to-fix ratio. Do first.
Camera / mic / location / notifications / clipboard are currently auto-handled by Electron defaults. Gate them behind a prompt and a per-site decision.
- Hooks:
session.defaultSession.setPermissionRequestHandlerandsetPermissionCheckHandlerinmain.js. New per-site permission store (extendlib/store.js). New prompt UI (reuse the popover layer or a small dedicated view) plus a site-info button in the omnibox. - Effort: medium (needs UI + storage). Security-sensitive, do it right: deny by default until the user answers.
Upgrade http:// to https://, warn (do not silently load) when a site has no
HTTPS.
- Hooks:
webRequest.onBeforeRequestto rewrite the scheme, with a fallback interstitial when the upgrade fails. Per-site "continue to HTTP" escape hatch. - Effort: medium. Gotcha: some sites genuinely lack HTTPS; the escape hatch and the warning page matter.
Close the "Electron done wrong" surface.
sandbox: trueon everyWebContentsView(verify the trusted preloads stay sandbox-safe: they only usecontextBridge+ipcRenderer, which is fine).- Explicit
contextIsolation: true,nodeIntegration: falseon all views (defaults today, make them explicit and audited). - Strict CSP on the chrome HTML pages (
index/hero/ai/overlay/find/context). will-navigatedeny on the trusted views so a compromised chrome page cannot navigate itself; keep web navigation only inside the untrusted page views.- Confirm
webviewTagis disabled (Electron default off, assert it). - Effort: medium, low risk. High structural value. Pairs with a one-pass audit.
Brave's signature, and the thing that defends you on the real web.
- Hooks:
session.defaultSessionnetwork layer. Cleanest path is@ghostery/adblocker-electron(fromLists+blockingViewsFromSession), which is a maintained, well-tested matcher. Adds one dependency. Ship a per-site toggle and a blocked-count badge in the toolbar. - Effort: large-ish but mostly integration. The badge follows DESIGN.md.
- Hooks:
webRequest.onBeforeSendHeaders/onHeadersReceivedto drop cross-siteCookie/Set-Cookie, and set a strictReferrer-Policy(strict-origin-when-cross-originor tighter). Electron has no single third-party-cookie toggle, so this is filter-based. - Effort: medium. Gotcha: do not break first-party logins; scope to cross-site only.
Your DNS lookups stop being readable by the network/ISP.
- Hooks:
session.defaultSession.configureHostResolver({ secureDnsMode: 'secure', secureDnsServers: [...] }). One call, plus a settings entry for the resolver. - Effort: small. Easy, high win. Good Tier-1-adjacent.
- Clear-data: time-ranged clear of history / cookies / cache. History and
bookmarks are our JSON store (
lib/store.js); cookies/cache viasession.clearStorageData/clearData. New settings surface. - Private window: a second
BaseWindowon an in-memory partition (session.fromPartition('slash-private', { cache: false })) that writes nothing tostore.jsand is dropped on close. - Effort: medium. The private window is the marquee piece.
Apply the safeStorage pattern from item 2 to the history/bookmarks blob in
lib/store.js. Small once item 2 exists.
Reduce canvas / WebGL / font entropy. Hard and partial. Tension with the design: page views deliberately have no preload, so spoofing navigator/canvas props needs either a per-page injection or a session-level shim. Partial wins only. Defer until Tier 1 and 2 land.
A browser that lags on Chromium is the real long-term risk. This is release
process, not runtime: electron-updater / update-electron-app for the
packaged build, plus a discipline of bumping the electron dep on security
releases. Set up when Slash starts shipping packaged builds.
- Proxy:
session.setProxy({ proxyRules }), easy. - Tor: needs a bundled or external Tor SOCKS endpoint to point the proxy at. Hard. Proxy first, Tor maybe never.
- Never send page content to the AI without an explicit user action. Today the panel only sends the chat transcript, which is correct; keep it that way.
- Any future "send this page / selection to AI" action must show exactly what is included before it sends, and require a click.
- CLI variants already run in default permission mode (no bypass, no autonomous tools). Do not loosen that.
- Session restore: the open tabs are saved (debounced + on quit) to
userData/slash-session.jsonand reopened on launch. Restored background tabs come back suspended/lazy (reusing tab discarding), so startup is fast and low-memory; only the active tab loads. - Clear browsing data (item 9, partial): Settings -> Privacy clears history
(
store.clearHistory), cache (session.clearCache), and cookies + site data (session.clearStorageData), each opt-in. Cookies default off so a clear does not sign you out unless asked. The private window (item 9's other half) needs the multi-window refactor and is tracked separately. - Encrypt the local store at rest (item 10): bookmarks / history /
permissions in
lib/store.jsare now encrypted withsafeStorage(the same pattern as API keys and the vault). Legacy plaintext is read once and re-encrypted on the next write.
Moving in from another browser, without sending anything off the machine.
- Reads installed Chromium-family browsers (Chrome, Edge, Brave, Vivaldi, Opera), one entry per profile, from the local profile directory. Nothing is uploaded.
- Bookmarks / history: plaintext JSON and the
HistorySQLite DB, read withsql.js(pure WASM, no native build, so every clone of the repo works). History merges by URL keeping each entry's own visit time (store.importHistory). - Cookies (stay signed in): the
CookiesSQLite DB. Values are AES-256-GCM encrypted under a key sealed with Windows DPAPI. Same OS user, so we unseal the key (CryptUnprotectDatavia PowerShell, no native dep) and decrypt, then inject withsession.cookies.set. Honest limits, surfaced in the UI:- Chrome's newer app-bound (
v20) cookies cannot be read by design; they are skipped and counted ("N protected"). Verified live: current Chrome returns 100% app-bound, Brave decrypts cleanly. - A running browser can lock the DB; we fall back to a temp copy, and tell the user to close it if that still fails.
- Chrome's newer app-bound (
- Gotcha: nothing here ever leaves the device, and it only runs on an explicit per-source "Import" click with the data types the user ticked.
- Encrypted at rest with the same
safeStorage(DPAPI/Keychain/libsecret) as the API keys (item 2). Plaintext only in memory while running. - Filled by CSV import (the ecosystem-standard path every browser and password manager supports, and the only robust one now that app-bound encryption blocks direct password reads), plus capture-on-login.
- Autofill (
tab-preload.js): a minimal sandboxed, context-isolated preload on page views. It fills saved logins only into visible fields on the matching origin, on a real focus gesture, and never exposes passwords to page scripts (nothing onwindow; the vault data stays in the isolated world). Offers to save/update on submit via the non-blocking infobar. - Related: item 10 (encrypt the local data store) now has a working pattern to copy.
Cheapest high-impact set to land first, mostly main-process and settings, little new UI: 2 (encrypt keys), 5 (harden shell), 8 (DoH), 1 (DDG default). Then 3 (permissions) and 4 (HTTPS-only) once the site-info UI exists, then 6 (the blocker) as the signature feature.