Back to Projects
Solo Project2026

Tosly - Terms of Service Reader

Tosly is a Chrome extension that reads Terms of Service and Privacy Policies for you and flags the clauses that work against you: data selling, forced arbitration, auto-renewals, with the exact quote from the document. The extension extracts visible page text; a Go backend prompts an LLM with a structured-output schema, so flags are consistent and citable instead of a vague summary. Free, no account, source on GitHub.

The Story

  • April 2026: read Spotify's privacy policy after clicking 'Accept' on it for the hundredth time. They share listening data with advertising partners, and continuing to use the service is the opt-in. There's no way to refuse.
  • Realized I do this every signup, every checkout, every app update. Companies bet you won't read 30 pages of legalese. That bet always pays off.
  • Wanted something that read the document for me and quoted the bad parts back. Not a summary. Summaries strip out the evidence you need to act on.
  • Decided the right surface was a Chrome extension. The risk has to surface on the page where you're about to click. Anything that requires a separate app or workflow won't get used.
  • Built the Go backend first: single /analyze endpoint, in-memory LRU cache keyed by URL, Gemini for the analysis. The prompt is the product; the rest is plumbing.
  • Picked Plasmo for the extension because MV3 with Shadow DOM isolation and hot reload is the only practical way to iterate on UI that runs on every site.
  • Shipped end-to-end in a weekend. Then spent several resubmissions getting it through Chrome Web Store review, almost all of it about permission justifications and required support pages that aren't in the official MV3 docs.
Screenshot of the Tosly landing page

Video Demo

Key Features

  • Detects around 40 risky-clause patterns: data selling, forced arbitration, broad license grants, auto-renewals, retention beyond account deletion. Quotes the exact paragraph back instead of paraphrasing.
  • Per-URL server-side cache with a 7-day TTL. A popular Privacy Policy is analyzed exactly once during that window across every user, which keeps LLM cost effectively flat.
  • Two permissions only: storage (saves widget position and auto-scan preference) and host_permissions (read visible page text). No identity, no browsing history, no cookies, no form data leave the browser.
  • Click any flagged quote in the result panel and the page scrolls to the original paragraph with a flashing highlight. Works on SPA-rendered DOMs where window.scrollTo silently no-ops.

Challenges & Solutions

The Challenge

Three things were harder than expected. First, getting a legal-text analyzer to produce consistent output: 'summarize this ToS' gives slop. The fix was a category rubric of fixed risk patterns, a severity scale, and a structured-output schema that requires an exact quote per flag. v1 of the prompt was unusable; v2 produces citable JSON on hostile input. Second, the Chrome Web Store review process. The first build worked locally in a weekend, but it took several resubmissions to get past the reviewer, almost all of it about permission justifications and required support and privacy URLs. Third, click-to-scroll on pages I don't own: finding a text fragment in an SPA-rendered DOM, scrolling it into the page's actual scroll container, and rendering a highlight that doesn't fight the host site's CSS.

What I Learned

  • Permission justifications in manifest.json get quoted back at you by the Chrome reviewer verbatim. Write them as sentences explaining why the feature needs the permission, not one-word labels. I dropped the tabs permission once I realized nothing actually needed it; the feature still worked.
  • The Web Store wants more than the extension. A working privacy URL and a working support URL on a real site are required. Added /privacy, /support, /support/success to the landing site mid-review because none of that is in the official MV3 docs.
  • Prompts beat code for legal text. The thing that makes Tosly trustworthy is a prompt that forces the model to (a) match a category from a fixed rubric, (b) return a severity, and (c) cite the source. None of that is novel ML; it's just refusing to ship a 'summarize this' prompt.
  • Cache aggressively when the input is public and identical for every user. Same policy URL means one LLM call per week, served to everyone. Cost stopped being a variable.
  • Shadow DOM isolation matters when your UI ships on every site on the internet. Plasmo's CSUI shadow DOM is the single biggest reason Tosly doesn't get repainted by the host page's CSS.

Impact & Growth

  • First product on the Chrome Web Store under my name. Went through the full submission, review, and listing cycle end-to-end.
  • Built a reusable prompt scaffold for structured output over hostile input (category rubric, severity, required exact quote) that I reach for any time I need an LLM to act on unstructured text.
  • Got a piece of generally useful DOM engineering out of it: click-a-quote, scroll-and-highlight on a page I don't control. Lifts cleanly to any annotation tool, doc reader, or 'find this string in a foreign page' use case.
  • Settled on a ship order: smallest privacy-preserving version first, support pages second, marketing site third. The slow steps are LLM cost and store review, both solvable.

Technologies Used

Chrome MV3PlasmoReactTypeScriptGoGeminiAstroTailwind CSS

Want to see more?

View all projects