An open-source AI assistant for legal work — chat over your documents, draft and redline contracts, and look up Swiss case law from primary sources.
Michi is a self-hostable legal document platform. Upload contracts and filings, ask questions grounded in the actual text, have the AI propose tracked-change edits to Word documents, and pull authoritative Swiss decisions and statutes instead of trusting a model's memory. Users bring their own model API keys, which are encrypted at rest.
⚠️ Michi is a personal/portfolio project, not a certified legal product. Its output is not legal advice. Do not store privileged client data without your own security review.
Add screenshots to docs/screenshots/ to show the Assistant, Swiss Law Search, and Tabular Review views.
- Document-grounded chat — Ask questions about uploaded documents. Answers cite the specific file and quote the passage, so claims are traceable.
- AI redlining — The assistant proposes edits to
.docxfiles as native Word tracked changes you can accept or reject. - Swiss law lookup — Built-in tools query OpenCaseLaw for federal and cantonal decisions (BGE/BGer), statutes (OR, ZGB, BV, StGB), and scholarly commentary. The model cites real decisions with canonical URLs instead of inventing case numbers.
- Swiss Law Search — A dedicated search page to query case law, statutes, and doctrine directly, with results linking to the official source.
- Tabular Review — Extract structured fields across many documents into a spreadsheet-style table (e.g. governing law, term, liability cap across 50 contracts).
- Workflows — Reusable prompt templates for recurring drafting and review tasks.
- Projects & sharing — Organize documents into projects and share them with colleagues by email.
- Bring Your Own Key (BYOK) — Each user supplies their own Anthropic or Google key. Keys are encrypted before storage and never sent back to the browser. Supports Claude, Gemini, and OpenRouter models.
| Layer | Stack |
|---|---|
| Frontend | Next.js (App Router), React, TypeScript, Tailwind CSS |
| Backend | Express, TypeScript |
| Auth & DB | Supabase (Postgres + Auth) |
| Storage | S3-compatible object storage (e.g. Cloudflare R2) |
| Documents | LibreOffice (DOC/DOCX → PDF), mammoth, docx tracked-change generation |
| Models | Anthropic Claude, Google Gemini, OpenRouter (user-supplied keys) |
frontend/ Next.js app (UI, calls the backend over HTTP with a Supabase JWT)
backend/ Express API
├─ routes/ auth-gated endpoints (chat, documents, projects, caselaw, …)
├─ lib/ business logic
│ ├─ access.ts ownership/sharing checks (the authorization layer)
│ ├─ cryptoKeys.ts AES-256-GCM encryption for user API keys
│ ├─ chatTools.ts LLM tool definitions (read docs, edit docx, Swiss law)
│ └─ swissCaselaw.ts OpenCaseLaw REST client
└─ migrations/ one-shot Supabase schema + RLS
Trust model: the backend talks to Supabase with the service role, so it bypasses row-level security by design. Every data route therefore re-checks ownership against the authenticated user via lib/access.ts before returning anything. New routes must do the same.
Security was treated as a first-class concern, not an afterthought:
- Passwords never touch the app — authentication is handled entirely by Supabase; the backend only verifies signed JWTs.
- Encrypted API keys — user model keys are sealed with AES-256-GCM (random IV + auth tag per key) using a server-side
KEY_ENCRYPTION_KEY. Key columns are revoked from theauthenticated/anonPostgres roles, so only the backend can read them, and the plaintext is never returned to the client. - Server-side authorization — consistent ownership and project-sharing checks on every document, project, and download route.
- Hardened transport layer — per-IP rate limiting (stricter on chat and account routes) and
helmetsecurity headers. - Output safety — AI responses are rendered as escaped Markdown (no raw HTML), so model output can't inject scripts.
A security self-audit lives in .gstack/security-reports/ (kept local). For production use handling real client data, get an independent professional review.
Install dependencies:
npm install --prefix backend
npm install --prefix frontendCreate local env files from the examples:
cp backend/.env.example backend/.env
cp frontend/.env.local.example frontend/.env.localFill in backend/.env. At minimum you need Supabase, S3/R2, and a KEY_ENCRYPTION_KEY:
# Required: 32 random bytes, base64-encoded. Encrypts user API keys.
openssl rand -base64 32Run the schema in the Supabase SQL editor for a fresh database:
backend/migrations/000_one_shot_schema.sql
(001_encrypt_api_keys.sql is only needed when upgrading a database created before key encryption.)
Start the backend and frontend:
npm run dev --prefix backend # http://localhost:3001
npm run dev --prefix frontend # http://localhost:3000Open http://localhost:3000.
- Supabase (Auth + Postgres)
- S3-compatible object storage, e.g. Cloudflare R2
- At least one model provider key (or let users supply their own via BYOK)
- LibreOffice, for DOC/DOCX → PDF conversion
npm run build --prefix backend
npm run build --prefix frontend
npm run lint --prefix frontendAGPL-3.0-only. If you run a modified version as a network service, you must make your source available under the same license.
