Skip to content

About

End-to-end encrypted bookmark and tab sync across Chrome, Firefox and Safari. Self-host on your tailnet or use the cloud.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

BrowserLace

Keep the browsers you like. Choose what follows you across them.

BrowserLace syncs bookmark folders and open tabs across Chrome (and other Chromium browsers), Firefox and Safari. Everything is end-to-end encrypted: the server stores ciphertext and never sees a URL, title or collection name. Use the hosted server, or run your own on your tailnet.

Install it from the Chrome Web Store. The store version defaults to the hosted server, https://browserlace.skylive.world, and you can point it at your own.

Bookmarks and tabs only, by design. See docs/architecture.md for how it works and what's deliberately left out, PRIVACY.md for what's stored, and SECURITY.md to report a vulnerability.

How it works for users

  • Collections are sets of bookmarks you choose to sync, like "Work" or "Research".
  • Each browser decides which collections it syncs and into which folder: two-way, receive only or send only (this browser's folder is the source of truth). A work laptop can receive "Personal" and edit "Work"; a home PC might never see "Work".
  • Profiles save a browser's setup ("Work laptop") so a new browser applies it in one step.
  • Pairing a new browser takes a one-time code, link or QR code. The popup searches every collection, which is how Safari reaches them.
  • Other devices in the toolbar popup shows each browser's open tabs, so you can pick up where you left off without merging every session together. Send a tab to any device from the popup or the right-click menu; it opens there right away.
  • Changes reach your other browsers within a few seconds while they're open.
  • Safety: large deletions pause until you confirm, and every collection keeps a history you can restore from.
  • Your keys, your devices: removing a device rotates the encryption key so it can't read anything new, and a printable recovery key gets you back in if you lose them all.
  • Safari can't edit its bookmarks from an extension (Apple doesn't expose the API), so there collections open from the toolbar popup instead. Tabs work fully.

Repository

Path What
packages/core Sync engine: op model, reconciliation, encryption, pairing. Pure TS.
apps/server Sync server: Hono + SQLite (built into Node 24). One Docker image.
apps/extension WXT extension for Chrome, Firefox and Safari. React UI.
deploy/tailscale Compose file to self-host the server privately on a tailnet.

Development

Requires Node 24 and pnpm.

pnpm install
pnpm test        # core + server tests
pnpm typecheck

pnpm dev:server      # http://localhost:8787, database in apps/server/browserlace.db
pnpm dev:extension   # launches Chrome with the extension loaded (WXT dev mode)

End-to-end runs drive isolated browser profiles (never yours) against a throwaway server:

pnpm build && pnpm --filter @browserlace/e2e chromium
# Chromium ↔ Firefox (see e2e/firefox.ts for getting a throwaway Firefox ESR):
FIREFOX_APP=<path to Firefox.app> pnpm --filter @browserlace/e2e firefox

pnpm --filter @browserlace/extension dev:firefox does the same for Firefox. Set WXT_DEFAULT_SERVER_URL (see apps/extension/.env.example) to change the server prefilled in the setup screen.

Building

pnpm build   # server bundle + chrome-mv3, firefox-mv3 and safari-mv3 extension builds
pnpm --filter @browserlace/extension zip            # store-ready zips for Chrome and Firefox
pnpm --filter @browserlace/extension safari:xcode   # Xcode wrapper app for Safari (macOS, needs Xcode)

Safari extensions ship inside a native app: open apps/extension/safari in Xcode, set your team for signing, and run the macOS (or iOS) scheme. For local testing, enable Safari → Settings → Developer → Allow unsigned extensions.

Store identifiers are set at build time (in the environment or apps/extension/.env):

Variable Default Meaning
WXT_DEFAULT_SERVER_URL http://localhost:8787 Server prefilled in the setup screen
WXT_FIREFOX_ADDON_ID browserlace@browserlace.app Firefox add-on id. Never change it after the first AMO upload
SAFARI_BUNDLE_ID app.browserlace.BrowserLace Bundle id of the Safari wrapper app (safari:xcode)

Releases

CI runs typecheck, tests, builds, Mozilla's linter and a Docker smoke test on every PR. Pushing a tag that matches apps/extension/package.json's version (e.g. v0.2.0) publishes a GitHub release with the Chrome and Firefox zips and pushes the server image to ghcr.io/skylivellc/browserlace-server. Set the repository variables DEFAULT_SERVER_URL and FIREFOX_ADDON_ID for store builds. Listing text, privacy answers and screenshots for the Chrome Web Store are in docs/store-listing.md.

Running the server

docker run -p 8787:8787 -v browserlace:/data ghcr.io/skylivellc/browserlace-server
# or build it yourself from the repo root:
docker build -f apps/server/Dockerfile -t browserlace-server .
Variable Default Meaning
PORT 8787 HTTP port
DATABASE_PATH /data/browserlace.db SQLite file (browserlace.db outside Docker)
SIGNUP_TOKEN unset If set, creating an account requires it. Use it for invite-only servers.
HISTORY_DAYS 365 Changes older than this are dropped once a snapshot covers them; history and restore go back this far
TRUST_PROXY unset Header with the client IP behind a reverse proxy (fly-client-ip, x-forwarded-for), for rate limits
DEVICE_REQUESTS_PER_MINUTE 600 Per-device rate limit. Sign-ups and claims are limited to 10 a minute per IP
ACCESS_LOG on JSON access logs (method, path, status, ms) on stdout; off to disable
LITESTREAM_REPLICA_URL unset Continuous SQLite backups with Litestream, e.g. s3://bucket/browserlace; restored automatically on an empty volume. S3 credentials in LITESTREAM_ACCESS_KEY_ID / LITESTREAM_SECRET_ACCESS_KEY

Billing (hosted service only)

Billing and plan limits turn on only when all four Stripe settings are set. Without them (self-hosting) there are no limits and no billing UI.

Variable Meaning
STRIPE_SECRET_KEY Stripe API key
STRIPE_WEBHOOK_SECRET Signing secret of the webhook endpoint https://<server>/billing/webhook
STRIPE_PRICE_ID Recurring price for the Plus plan
PUBLIC_URL The server's public URL, where Stripe sends people back after checkout
FREE_DEVICES, FREE_COLLECTIONS, FREE_STORAGE_MB Free plan limits (default 3, 5, 25)
PLUS_DEVICES, PLUS_COLLECTIONS, PLUS_STORAGE_MB Plus plan limits (default 20, 200, 1024)

The webhook needs the events checkout.session.completed and customer.subscription.created, .updated and .deleted. Enable the customer portal in Stripe so people can manage or cancel.

Put it behind HTTPS (any reverse proxy). To self-host privately on a tailnet, see deploy/tailscale. To run a public server on Fly.io, see deploy/fly. The hosted service at https://browserlace.skylive.world runs on Coolify, with Litestream backups to Cloudflare R2.

License

AGPL-3.0. You can use, modify and self-host BrowserLace freely; if you offer a modified version as a hosted service, you must publish your changes under the same license.

About

End-to-end encrypted bookmark and tab sync across Chrome, Firefox and Safari. Self-host on your tailnet or use the cloud.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages