I'm Bharat Chandra, and this is the story of building Likhitex — a private, invite-only collaborative LaTeX editor for small research groups. The core idea: treat LaTeX compilation as code execution, sandbox everything, and never depend on a vendor's infrastructure.
The problem with existing LaTeX editors
Overleaf is great for open collaboration. But for small, trusted research groups — thesis committees, grant teams, confidential paper drafts — the commercial model doesn't fit:
- ▸Compile timeouts on free tiers
- ▸Arbitrary collaborator limits
- ▸Data on someone else's server
- ▸Vendor lock-in
Likhitex solves this by being invite-only, self-hosted, and designed to run on free-tier infrastructure with zero operational cost.
The security constraint that drove everything
LaTeX compilation is code execution. The \write18 command (shell-escape) lets you run arbitrary shell commands from inside a .tex file. If you're running untrusted LaTeX on shared infrastructure, that's a security disaster.
My approach: treat every compilation as hostile. No trust, no exceptions.
The compilation sandbox
Every LaTeX compile runs in an isolated Docker container with:
- ▸gVisor runtime — application kernel sandbox that intercepts syscalls
- ▸
--network none— zero external network access - ▸
shell_escape = fenforced intexmf.cnf— \write18 disabled at TeX engine level - ▸Read-only TeX tree — immutable TeX Live installation
- ▸Resource limits — strict CPU, memory, file size, and process count via
prlimit - ▸60-second hard timeout with process group termination
The container runs with env -i and has no access to secrets, credentials, or the host filesystem beyond the single .tex file being compiled.
Real-time collaboration with Yjs
The editor uses Yjs — a CRDT (Conflict-free Replicated Data Type) framework that lets multiple users edit the same document simultaneously without a central lock.
Every keystroke gets converted into a CRDT operation and broadcast over WebSockets. When two users type at the same time, Yjs automatically merges the edits without conflicts. No operational transforms, no manual conflict resolution.
The result: Google Docs-style live cursors, instant synchronization, and zero merge conflicts.
WebSocket authentication with time-limited tickets
WebSockets don't support headers (annoying), so I couldn't just pass a JWT on connect.
The solution: one-time tickets stored in Redis with a 30-second TTL.
- 1.Client requests a WebSocket ticket from the REST API (JWT-authenticated)
- 2.Backend generates a random ticket ID and stores it in Redis: `ticket:{id}` → `user_id`, TTL 30s
- 3.Client connects to WebSocket with `?ticket={id}`
- 4.Backend validates the ticket, deletes it from Redis, and authenticates the WebSocket
Once the connection is established, subsequent messages are validated against the active session. One ticket = one connection.
The tech stack
| Layer | Technology | Why | |-------|-----------|-----| | Frontend | Next.js 15 + TypeScript | Server-side rendering, clean routing, full SEO control | | Backend API | FastAPI + Python 3.12 | Async-first, type-safe, fast iteration | | Database | Neon Postgres (free tier) | Serverless Postgres, auto-scaling, zero ops | | File Storage | Cloudflare R2 (S3-compatible) | Free egress, S3 API, no lock-in | | Cache/Queue | Upstash Redis | Rate limiting, compile queue, WebSocket tickets | | Authentication | Clerk | JWT tokens, invite-only enforcement via backend allowlist | | Collaboration | Hocuspocus + Yjs | CRDT synchronization, WebSocket server | | Compiler | TeX Live + Docker + gVisor | Sandboxed, isolated, secure LaTeX compilation | | Editor | CodeMirror 6 | Extensible, LaTeX syntax support | | PDF Viewer | PDF.js | Client-side rendering, no server load |
The entire stack is designed to run on free-tier infrastructure: Vercel (frontend), Render (backend + compiler), Neon (database), Cloudflare R2 (storage), Upstash (Redis). Zero monthly cost for small teams.
Input validation: the boring but critical part
LaTeX lets you write some truly cursed input: path traversal, symlinks, zip bombs, shell-escape attempts. Here's how I handled each:
ZIP bomb detection: Size limits + decompression ratio checks. If a 1MB ZIP extracts to 500MB, it gets rejected.
Path traversal: Reject ../, absolute paths, and directory escapes. Every file path gets normalized and validated against an allowlist.
Symlink rejection: Block all symbolic links in uploaded archives. No exceptions.
Extension allowlist: Only .tex, .bib, .sty, .png, .jpg, .pdf, etc. Everything else gets rejected.
Package blocklist: Reject packages that require shell-escape (minted, pythontex). If your document needs those, it doesn't compile.
The sub-60s compile target
Original target: 60 seconds per compile. Actual result: most documents compile in under 15 seconds.
The key was keeping the TeX Live image warm and reusing containers across compiles. Cold-start time is still ~5s, but after the first compile, subsequent ones are near-instant.
Where the project stands
Phases 1-3 are live — Likhitex is a fully functional collaborative LaTeX editor with sandboxed compilation, user authentication, and real-time multi-user editing.
Phase 1: Core + Security ✅ - Sandboxed LaTeX compilation with Docker + gVisor - Security test suite (path traversal, shell-escape, zip bombs) - Basic editor with CodeMirror 6 + PDF preview - Deployed to Vercel (frontend) + Render (backend)
Phase 2: Accounts & Persistence ✅ - Clerk authentication with JWT verification - User and project database models (Neon Postgres) - File upload and ZIP import/export - Storage quotas and limits (Cloudflare R2) - Project dashboard with role-based access
Phase 3: Real-Time Collaboration ✅ - Yjs CRDT document synchronization - Live cursor tracking and presence awareness - WebSocket authentication with time-limited tickets - Share by email with Owner/Editor/Viewer roles - Multi-user editing with zero merge conflicts
What's working now: You can invite collaborators, edit LaTeX documents together in real-time with live cursors, compile in isolated containers, and see instant PDF previews. All on free-tier infrastructure.
Still to ship
Phase 4 (Production Readiness): Compile queue with position display, rate limiting on all endpoints, error log parsing, version history with diffs, automated backup verification.
Phase 5 (Quality of Life): SyncTeX jump-to-source, LaTeX autocomplete, template presets, monthly backup restore tests.
The system is production-ready for small teams. The roadmap is public on GitHub.
Why open-source if it's invite-only?
The code is MIT-licensed so anyone can fork it and run their own instance. The hosted version at the production URL is invite-only because it's designed for small, trusted groups — not open collaboration.
If you're a research group that needs this, you can self-host. If you want access to the hosted instance, reach out.
— Bharat Chandra, GITAM University Hyderabad GitHub: [github.com/BharatChandra-sys/Likhitex](https://github.com/BharatChandra-sys/Likhitex)
