DEV Community

Gilles W
Gilles W

Posted on

I built a Chrome tab manager that cannot make a network request

In 2021 the Great Suspender — a beloved tab-suspending extension with millions of users — was quietly sold to a new owner, who shipped an update that turned it into adware/malware. Google eventually pulled it and force-disabled it for everyone. The unsettling part wasn't that one extension went bad. It was the realization that "it's just a tab manager" was never actually safe: the thing had the permissions to read every page you visited, and one ownership change was all it took to use them.

So when I wanted a tab manager, I didn't want a promise that it wouldn't phone home. I wanted a tool that structurally cannot. The privacy should be a property of the code, not a line in a privacy policy.

I built Stowtab, and the whole pitch is that you don't have to trust me — you can read it in an afternoon. It's MIT, exactly 3 permissions, zero host permissions, and zero network calls. Here are the two engineering decisions that make those claims verifiable rather than aspirational.

Status note up front, because honesty is the entire point of this project: Stowtab is not on the Chrome Web Store yet — that one-click listing is in review. Today you install it by loading it unpacked from the public repo (30-second steps in the README). The one-click version is coming soon; there's a waitlist if you'd rather wait for the button.

1. Delete the attack surface: no host permissions

The entire trust pitch is the manifest. Here's the relevant part, verbatim:

{
  "manifest_version": 3,
  "permissions": ["tabs", "tabGroups", "storage"],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'"
  }
}
Enter fullscreen mode Exit fullscreen mode

No host_permissions. No <all_urls>. No scripting. No content scripts. That's not minimalism for its own sake — each of those omissions removes a capability that the extension then provably doesn't have:

  • It can enumerate tabs (title, URL, group) through the tabs API, but it cannot read page content or touch the DOM. Reading pages requires scripting + host permissions, which Stowtab never requests. If a future "update" wanted to start scraping your pages, it couldn't do it silently — adding those permissions triggers a new, scary install-time consent prompt.
  • With no host permissions, there is no origin the extension is allowed to fetch. That's the load-bearing detail. "No network calls" isn't a policy I'm asking you to believe — it's the absence of a capability. There's no host it's permitted to reach, and the CSP (script-src 'self') means no remote code can be injected to work around that.

Storage is local-only:

// everything (sessions, settings, license) lives here — never storage.sync
chrome.storage.local
Enter fullscreen mode Exit fullscreen mode

I deliberately avoided chrome.storage.sync so nothing silently rides up to Google's servers. And suspend uses the native chrome.tabs.discard() rather than the old Great-Suspender trick of redirecting each tab to an extension-hosted page — so there's no interception layer sitting between you and your tabs, and discarded tabs restore natively.

The result: the most private design also turned out to be the simplest one. You don't have to be trusted not to abuse a capability you never asked for.

2. Monetize without a server: offline Ed25519 licenses

Here's the fun constraint. A tool whose entire identity is "never phones home" obviously can't have a license-activation server to call. So how do you sell a $19 Pro tier without a backend?

You sign the licenses offline and verify them locally with WebCrypto.

  • Mint (server-side, one private key that never ships): a payload like { tier: "pro", emailHash, issuedAt } gets signed with an Ed25519 private key.
  • Verify (in the extension): the package ships only the public key. The extension checks the signature with crypto.subtle.verify and the native Ed25519 algorithm.

The verification path, lightly trimmed from src/lib/license.js:

import { LICENSE_PUBLIC_KEY_RAW_B64 } from './constants.js';

let _pub = null;
async function pubKey() {
  if (!_pub) {
    _pub = await crypto.subtle.importKey(
      'raw', b64ToBytes(LICENSE_PUBLIC_KEY_RAW_B64),
      { name: 'Ed25519' }, false, ['verify']
    );
  }
  return _pub;
}

export async function verifyKey(key) {
  if (typeof key !== 'string' || !key.startsWith('STOW1.')) return null;
  try {
    const token = JSON.parse(new TextDecoder().decode(b64urlToBytes(key.slice(6))));
    const payloadBytes = b64ToBytes(token.p);
    const ok = await crypto.subtle.verify(
      { name: 'Ed25519' }, await pubKey(), b64ToBytes(token.s), payloadBytes
    );
    if (!ok) return null;
    const payload = JSON.parse(new TextDecoder().decode(payloadBytes));
    return payload.tier === 'pro' ? payload : null;
  } catch {
    return null; // fail closed
  }
}
Enter fullscreen mode Exit fullscreen mode

Two details I like:

  • It re-verifies on every check (isPro() re-runs verifyKey against the stored key), so hand-editing chrome.storage.local to flip yourself to Pro just fails the signature check and reverts to Free. It fails closed.
  • The public key is safe to ship — that's the point of asymmetric signing. You can read it in the repo and confirm there's no secret hiding in the bundle.

And the honest trade-off, which is right there in a code comment so I can't pretend otherwise:

// stops casual fakes & storage tampering; does not stop a determined user from sharing a key.
// No network calls ever — consistent with the "never phones home" promise.
Enter fullscreen mode Exit fullscreen mode

Offline keys can't be revoked and can't stop key-sharing. For a $19 trust-first tool, that's the correct call — aggressive DRM would require exactly the kind of phone-home machinery the whole product exists to avoid. I'd rather lose a few bucks to sharing than contradict the premise.

What it actually does (so it's not just a privacy stunt)

A privacy model nobody uses is worthless, so the free tier is genuinely useful:

  • Group tabs by domain in one click
  • Find and close duplicate tabs
  • Suspend tabs via native chrome.tabs.discard() to reclaim memory
  • Fuzzy search across every open tab
  • Save / restore sessions (Free: 3; Pro: unlimited)
  • Encrypted local backup (Pro)

Pro is a $19 one-time unlock — no subscription, no account.

Verify, don't trust

That's the whole ethos. Don't take my word for any of the above:

  1. Read the manifest and confirm there are no host permissions.
  2. Read src/lib/license.js and confirm the verification is offline.
  3. Open DevTools → Network while you use it and watch nothing happen.

If you find a hole in the permission model, I genuinely want to hear it — that's the most useful comment I could get. The brand is honesty, so poke hard.


Disclosure: I built Stowtab. It's a commercial product with a free open-source core. Links above are my own.

Top comments (0)