Legal security before you agree.
Exetazo is a browser extension (Manifest V3) plus an analysis API that reads terms of service, privacy policies, and subscription agreements, flags risky clauses, and scores the document with fixed rules — the way a smart-contract audit reports vulnerabilities.
- Deterministic score — fixed integer penalties computed by the rules engine; a model never sets the score
- Evidence on every finding — the exact clause text, located in the cleaned document and highlightable on the page
- Not legal advice — a risk-detection tool for human review
Landing page: www.exetazo.xyz (static, deployed on Vercel).
- Extract — the content script pulls block-level text from the rendered page and removes navigation, header/footer, cookie/consent, promotional, and layout noise. Page text is treated as untrusted data: it is never executed and never rendered as markup.
- Clean — the API normalizes the text into clause-sized paragraphs with section headings and character offsets. Pages with fewer than 60 words of legal text get an explicit
not-legal-textanswer — no findings are fabricated. - Detect (rules) — the rules engine matches 15 category patterns plus cross-checks for self-contradicting documents, emitting findings whose severity, title, explanation, and recommendation come from the rule catalog. Every finding carries the verbatim evidence it matched.
- Detect (semantic, optional) — if configured, a model proposes paragraph-level candidates for categories the rules missed. Candidates are validated against the cleaned document (evidence is always real text, never model output) and merged as advisory findings.
- Score —
100 − 20·critical − 10·high − 5·medium − 2·low, clamped to 0–100, duplicate finding IDs counted once. Risk bands: 80–100 low, 60–79 moderate, 30–59 high, 0–29 critical, lifted by severity floors (a critical finding never reads below HIGH RISK). - Report — the side panel renders the score, counts, and findings; clicking a finding scrolls to and highlights the exact clause (section-aware anchoring, with a raw text-node fallback).
Determinism boundary: only rules findings affect the score. Semantic findings are labelled advisory in the report — a model can add context but cannot change a document's score, so identical pages always produce identical scores.
- Unilateral Modification — the company can change the terms anytime
- Mandatory Arbitration — you waive the right to sue in court
- Class-Action Waiver — you can't join a class lawsuit
- Automatic Renewal — subscriptions renew without a clear reminder
- Difficult Cancellation — cancelling needs calls, letters, or mazes
- Broad Liability Limitation — damages capped or disclaimed broadly
- Broad Indemnification — you cover the company's legal costs
- Broad Data Sharing — data shared with vaguely defined third parties
- Data Sale Permissions — selling your data is explicitly permitted
- Data Retention Ambiguity — "kept as long as necessary", undefined
- Account Termination Rights — vague grounds, no appeal
- Forced Venue / Jurisdiction — disputes fought where they choose
- Hidden Fees / Pricing Changes — surprise charges, mid-term changes
- Broad IP / Content Ownership — expansive rights over your content
- Contradictory Clauses — the document takes back its own promises
Each category has an explicit definition, a rule catalog entry with negation
guards, a severity, jurisdiction notes, and tests — including real-world
false-positive controls (see tests/rules.test.ts).
Does: propose paragraph-level candidates for categories the rules missed,
when OPENAI_API_KEY is configured. Without it the pipeline is fully
rules-based.
Does not: set severity (rule catalog), set the score (fixed arithmetic), write evidence (always the document's own text), or answer questions (this is a scanner, not a chatbot).
- The backend is stateless. No database, no cache, no analytics. The only lines it ever logs are its own startup message (port, semantic mode).
- Document text exists only in memory for the duration of a request. On Vercel, the platform records invocation metadata (time, status, duration) — not request bodies.
- The extension stores exactly one thing: the most recent report —
structured findings, not page text — in
chrome.storage.session, which clears when the browser closes. - No accounts, no sign-up, no tracking, no telemetry.
- Secrets stay server-side. Model credentials are server environment
variables only; the extension ships no credentials (see
.env.example).
api/analyze.ts Vercel serverless endpoint (same contract as the local API)
extension/
manifest.json MV3 manifest (dev origins; production builds narrow host_permissions)
src/background.ts service worker: inject content script, call API, session storage
src/content.ts page-text extraction + clause highlighting
src/sidepanel.ts report UI | src/popup.ts launcher
src/messages.ts extension contracts + build-time API base
webpack.config.cjs bundles src/*.ts → dist/
assets/ toolbar/store icons, logo (logoo.png is the master source)
server/
api/main.ts, server.ts local HTTP API (framework-free node:http, CORS, error contract)
analyzer/clean.ts noise removal, sections, offsets, word count
analyzer/analyze.ts pipeline: clean → rules → contradictions → optional AI → score
rules/catalog.ts the 15 categories: patterns, negations, severity, guidance
rules/engine.ts matching + finding construction
rules/contradictions.ts cross-checks for self-contradicting documents
scoring/score.ts fixed-penalty scoring + risk bands
llm/ optional OpenAI-compatible semantic layer
shared/types.ts finding / report / error contracts
scripts/ build + asset tooling (see "Asset regeneration")
web/ landing page (static) — Vercel Root Directory = web
tests/ jest suites (see "Testing")
Prerequisites: Node 18+. (Python 3 with Pillow is needed only for the OG-card script.)
npm install # or: npm ci — clean, lockfile-exact install
npm test # full test suite
npm run build:server # tsc → dist/server
npm run start:server # API on :8787 (rules-only unless OPENAI_API_KEY is set)
npm run build:extension # webpack + asset copy → extension/distServer environment variables (all optional — see .env.example): OPENAI_API_KEY,
OPENAI_BASE_URL, OPENAI_MODEL enable the semantic layer; PORT changes the
port (default 8787).
npm run build:extensionchrome://extensions→ enable Developer mode → Load unpacked → selectextension/dist- Start the API:
npm run start:server(the dev build callshttp://127.0.0.1:8787) - Open any terms page, click the Exetazo icon, press Scan
One build emits both browser targets from one source — extension/dist (Chrome)
and extension/dist-firefox. The Firefox variant swaps the service worker for an
event page, uses Firefox's native sidebar_action instead of the Chrome side
panel (the same sidepanel.html renders — no duplicate UI), and carries the
gecko settings needed for addons.mozilla.org (exetazo@exetazo.xyz, data-collection
disclosure: website content sent to the API for transient analysis).
Public installation (AMO):
- The Firefox add-on is published and available at:
Developer testing (temporary load):
about:debugging#/runtime/this-firefox→ Load Temporary Add-on → selectextension/dist-firefox/manifest.json- Start the API (
npm run start:server) and open a terms page - Click the Exetazo icon → Scan — the sidebar opens with the same report; clicking a finding highlights the clause on the page
npx web-ext lint --source-dir extension/dist-firefox passes with zero errors,
warnings, and notices. For a production build, set EXETAZO_API_ORIGIN as below —
both targets get the narrowed host_permissions.
Two Vercel projects deploy from this one repo:
| Project | Root Directory | Framework | Notes |
|---|---|---|---|
| Landing page | web |
Other | No build step, no env vars. Live at exetazo.xyz (www canonical). |
| Analysis API | repo root | Other | Serves POST /api/analyze + GET /api/health from api/analyze.ts. Rules-only, so it needs no environment variables and no secrets. |
Point the extension at the deployed API at build time — the value used for the
Firefox AMO submission (see REVIEWERS.md for the reviewer build steps):
EXETAZO_API_ORIGIN=https://api.exetazo.xyz npm run build:extensionThe value becomes the bundle's API base and the only entry in the built
manifest's host_permissions. Unset means local development (http://127.0.0.1:8787,
dev origins kept). Non-HTTPS origins other than loopback fail the build. This is
configuration, not a secret — the extension ships no credentials.
env -u OPENAI_API_KEY -u OPENAI_BASE_URL -u OPENAI_MODEL npm run start:serverThe score is rules-only either way — semantic findings are advisory and cannot
move it (enforced in server/scoring/score.ts, proven in tests/analyzer.test.ts).
Verify the whole path against five live policies, each scanned twice:
npm run validate:pagesDemo flow: open a real terms page → Scan → read the score and severity counts → click a finding → the exact clause highlights on the page (lime, settling to lavender) → check EVIDENCE, EXPLANATION, RECOMMENDATION.
npm test # or: npx jest --runInBand (low-memory devices)extraction.test.ts— cleaning against verbatim real-policy excerpts: noise removed, clause text preserved verbatim, offsets consistentrules.test.ts— all 15 categories with true positives, real-world false-positive controls, and negation casescontradictions.test.ts— contradiction pairs, reconciliation guards, end-to-end scoringscoring.test.ts— penalties, bands, floors, duplicate prevention, reproducibility, advisory exclusionanalyzer.test.ts— the pipeline on real documents, plus the determinism boundary (any AI candidate set, and a model returning fabricated evidence, cannot change the score)api.test.ts/vercel-api.test.ts— the HTTP contracts: validation, error codes, CORS, byte-identical repeat scansllm.test.ts— malformed/model-failure handling and graceful degradation to rules-onlyextension-dom.test.ts— extraction and highlighting in jsdom against a verbatim live ToS page (noise removal, repeated-clause section anchoring, table cells, hostile text)extension-render.test.ts— the side panel renders untrusted report content as text, labels advisory findings, and shows the clear no-analysis state
python3 scripts/gen-brand-cuts.py # logo cuts from the master (byte-identical output)
node scripts/gen-icons.cjs # toolbar/store icons
python3 scripts/gen-og-card.py # social preview card (needs Pillow)Exetazo flags potential risks for human review. It does not provide legal advice, does not create an attorney-client relationship, does not decide whether a clause is legal or illegal, and does not replace a lawyer. Jurisdiction, context, and specific wording all matter — consult a lawyer for important agreements.
- Accuracy — fewer, correct findings beat a flood of false positives
- Evidence — every finding points to actual text
- Determinism — score, severity, and findings are reproducible
- Speed — a scan completes well inside five seconds
- Clarity — users understand what was found and why it matters
- Chrome Extensions documentation · Manifest V3 · Side Panel API
- Chrome Web Store publishing
- Vercel documentation
MIT licensed.