LeanTEA
Lean 4 + TEA (The Elm Architecture). A tiny full-stack framework โ and a handful of apps built on top of it (a functional spreadsheet with an MCP endpoint, a board-game SPA, a Chrome-CDP MCP server, and several other AI-driving MCP servers).
๐ Read the docs โ the LeanTEA book ยท HTTP benchmark trend
Questions, design discussion, and weekly progress threads live in the Discord channel above.
- Pure Lean stack: HTTP server, WebSocket client, and SQLite live in Lean.
- No Node.js, no Python at runtime (Python is used only by a few
build helpers in
tools/). - SQLite is vendored โ
c/sqlite3.cis the amalgamation, linked into the binary, so deployment doesn't need-lsqlite3. - The browser sees plain HTML + inlined CSS, plus one small runtime
JS file served from the same origin (
/runtime.js). Beyond the standardfetch/ History / DOM APIs any SPA needs, the Web Speech API is the only domain-specific browser API used. - Matches (slightly beats) tuned nginx on hello-world / 5-field
JSON: the
LeanTea.Net.ReactorServerbackend is ac/leantea_reactor.cnon-blocking event loop (kqueue on macOS/BSD, epoll on Linux) driving a LeanByteArray โ IO ByteArraycallback per request. 72 k RPS at c=128 vs nginx 69 k on the same box. Full numbers indocs/BENCHMARKS.md.
Inspirations
The name LeanTEA is Lean 4 + TEA โ The Elm Architecture. The framework borrows ideas across the Elm + Haskell-flavoured ecosystem and ports them into Lean 4:
- The Elm Architecture (TEA) โ
Model / Msg / update / viewon both the TUI and the in-browser runtimes. Lean's structures + dependent types letMsgbe an inductive that the compiler exhaustiveness-checks for you. - Yesod โ the "full-stack typed web framework" framing: routing, sessions, OAuth, and templates as first-class Lean values rather than stringly-typed configuration.
- Persistent โ the
Entity / Repotypeclasses underLeanTea.Persist.*are Persistent-style: define a record, derive a backend-agnostic store, swap SQLite / MySQL / in-memory at the call site. - Servant โ the typed-RPC layer
(
LeanTea.Rpc) treats the API surface as a Lean type the server and client share. No hand-written JSON wrangling, no schema drift.
Not affiliated with or endorsed by the Elm, Yesod, Persistent, or Servant projects.
Sibling projects in the Lean 4 ecosystem
- Verso โ a Lean-native
authoring tool for documentation and books (Scribble / Sphinx
lineage). Complementary to LeanTEA, not overlapping: Verso
generates static documents; LeanTEA serves dynamic web apps.
We're considering migrating
docs/to Verso once the framework stabilises so the code snippets in the book are actually type-checked.
Secure by Construction
LeanTEA's headline property is that whole classes of vulnerabilities can't be expressed in user code that compiles. Nine primitives ship today; one more is planned. The shipped set covers most of the IPA ใๅฎๅ จใชใฆใงใใตใคใใฎไฝใๆนใ 11 categories and OWASP Top 10 2021. See SECURITY.md for the design + threat model, docs/11 for the walk-through, and ROADMAP.md for sequencing.
| Vulnerability class | LeanTEA primitive | IPA / OWASP | Status |
|---|---|---|---|
| Authorization bypass / IDOR | LeanTea.Auth.Proof (Proof c + Capability lattice + dependent Proof (.owner id)) | IPA ยง3.7 / A01 | โ shipped โ walk ยท demo |
| SQL injection | LeanTea.Persist.SafeQuery (typed Where / Select / Update / Delete + .trusted decl_name% audit) | IPA ยง3.1 / A03 | โ shipped โ walk ยท demo |
| XSS (URL scheme + event-handler names) | LeanTea.Html.SafeAttr (private mk + URL allow-list + on* rejection) | IPA ยง3.5 / A03 | โ shipped โ walk ยท demo |
| Path traversal | LeanTea.Net.SafePath (workspace-relative + .. / NUL / sibling-prefix reject) | IPA ยง3.4 / A01 | โ shipped โ walk ยท demo |
| OS command injection | LeanTea.Os.SafeCmd (args : List String + shell-name allow-list reject + grep-able SafeCmd.shell audit) | IPA ยง3.3 / A03 | โ shipped โ walk ยท demo |
| HTTP header injection | Response.setHeader (CR / LF / NUL reject) | IPA ยง3.6 / A03 | โ shipped โ walk ยท demo |
| Clickjacking + MIME sniffing | Response.defaultSecurityHeaders (XFO / nosniff / Referrer-Policy / Permissions-Policy) | IPA ยง3.10 / A05 | โ shipped โ walk ยท demo |
| Open redirect | LeanTea.Net.SafeRedirect (allow-listed origin + relative-path-only mode + scheme reject + sibling-prefix reject) | IPA ยง3.9 / A01 | โ shipped โ walk ยท demo |
| CSP typos / misconfiguration | LeanTea.Net.Csp (typed CspSrc directives โ a mistyped source or directive doesn't compile) | IPA ยง3.5 / A05 | โ shipped โ demo |
| Invalid state transitions | OrderState / Transition s s' style proofs | โ | ๐ง planned |
Snippet โ SafeQuery rejects string-shaped SQL at compile time
-- โ
Compiles โ typed builders, positionally bound:
let rows โ SafeQuery.run users
{ where_ := .and (UserCols.email.eq "alice@x.com")
(.not (UserCols.deleted.eq true)) }
-- โ Compile error โ `Where.eq` is `private` to SafeQuery.lean.
-- The framework gives no path from a raw String to a `Where` clause.
let bad := Where.eq "email" rawUserInput
-- error: Unknown constant `LeanTea.Persist.SafeQuery.Where.eq`
Snippet โ Auth.Proof enforces the auth check in the type signature
-- The admin handler demands an unforgeable `Proof .admin`:
def handleAdminDelete (proof : Proof .admin) (req : Request) : IO Response := โฆ
-- Removing the `proof` parameter breaks the route registration:
def handleAdminDelete (req : Request) : IO Response := โฆ
-- error: Type mismatch in route registration
-- expected: Proof .admin โ Request โ IO Response
-- got: Request โ IO Response
The proof's mk is private to the auth module โ only Proof.issue
(which checks the session) can mint one. Forgetting the auth check
is now a build failure, not a CVE.
To close the "added a route but forgot to guard it at all" gap,
collect routes as a SecureRoute list: every entry must be either
.needs c (proof enforced) or .anyone (explicitly public). There's
no unstated third case, so an unguarded endpoint is a visible,
greppable .anyone rather than an accidental omission.
For the full walk-through (capability lattice, dependent
Proof (.owner id), the .trusted decl_name% audit-grep escape, the
~480-LOC trusted core across the shipped primitives), see
docs/11-secure-by-construction.md.
Layout
LeanTea/
โโโ Cmd.lean Sub.lean Runtime.lean -- TUI Elm runtime
โโโ Web.lean Html.lean Css.lean Js.lean -- WebApp (Model/Msg/update/view) + DSLs
โโโ Template.lean -- {{var}} / {{#each}} / {{#if}} / {{#include}}
โโโ Rpc.lean JsonRpc.lean -- Servant-style typed RPC + JSON-RPC envelope
โโโ Mcp.lean -- MCP Handler (stdio + HTTP transports)
โโโ Markdown.lean Markdown/ -- CommonMark-ish parser
โโโ Json/ -- terse Json accessors (.getStrD etc.)
โโโ Net/
โ โโโ Http.lean Server.lean -- HTTP/1.1 server + Request/Response/Handler
โ โโโ HttpClient.lean -- pure-Lean HTTP/1.1 client
โ โโโ WebSocket.lean -- pure-Lean RFC 6455 client (handshake, masking)
โ โโโ Desktop.lean Memcached.lean -- OS desktop FFI, memcached client
โโโ Persist/
โ โโโ Sqlite.lean Mysql.lean -- backend FFI
โ โโโ Store.lean Query.lean Backend.lean Migrate.lean -- Entity / Repo / migration
โ โโโ SafeQuery.lean -- typed Where / Select / Update / Delete (no `String โ SQL`)
โโโ Auth.lean -- session store
โ โโโ OAuth2.lean Saml.lean Passkey.lean Security.lean
โ โโโ Proof.lean -- Capability + Proof.issue (Authorization)
โโโ Crypto/ -- Base64 / SHA-1 / SHA-256 / HMAC / PBKDF2 / JWT
โโโ Browser.lean Comfy.lean Diffuse.lean -- 3rd-party tool bridges
โโโ Llm/Openai.lean -- streaming OpenAI-compatible client (LM Studio)
โโโ Agent/ -- run history, replayable scripts
โโโ LSpec.lean -- tiny test runner (group / it / lspecIO)
โโโ assets/runtime.js styles.css -- embedded client runtime
LeanJs/ -- Lean-subset โ JavaScript compiler
โโโ Ast.lean Parser.lean JsParser.lean
โโโ Check.lean -- arity + record-field guard
โโโ Codegen.lean Eval.lean LeanEmit.lean Includes.lean
c/
โโโ sqlite3.c sqlite3.h -- SQLite amalgamation (vendored, ~9 MB)
โโโ leantea_sqlite.c -- SQLite FFI wrapper
โโโ leantea_mysql.c -- MySQL FFI (opt-in via LEANTEA_MYSQL=1)
โโโ leantea_crypto.c -- OpenSSL bindings (opt-in via LEANTEA_CRYPTO=1)
โโโ leantea_desktop.c -- macOS Quartz bindings (opt-in via LEANTEA_DESKTOP=1)
examples/
โโโ Counter/ Quiz/ CounterWeb/ -- TUI + browser TEA demos (~50 lines each)
โโโ Sheet/ -- functional spreadsheet + /mcp (typed Rpc + Persist + Mcp)
โโโ Reversi/ -- board game (.leanjs client, Lean server)
โโโ Gpu/ -- WebGPU demo
โโโ ChromeCdpMcp/ -- real-Chrome driver via CDP (10 tools)
โโโ BrowserMcp/ BrowserAgent/ -- Playwright-driven browser + LLM agent
โโโ ComfyuiMcp/ -- ComfyUI HTTP/WebSocket driver
โโโ DesktopMcp/ -- OS-level mouse + screenshot (macOS Quartz)
โโโ ImageMcp/ -- HTML/CSS โ PNG compositor
โโโ UiScript/ UiReport/ -- AI-driven E2E test runner + HTML report
โโโ Smoke/ -- subsystem smoke tests (one per area)
โโโ Tests/ -- LeanJs spec runner
โโโ Tools/ -- gen_site + leanjs_{compile,interp,run} CLIs
โโโ Docs/ -- runnable doc examples
tools/
โโโ dev.py -- file watcher + auto reload for the dev loop
โโโ browser-bridge/ -- node + Playwright (used by BrowserMcp)
โโโ run-tests.sh run-docs.sh -- CI entry points
Build and run
# Build everything (~3 min cold, seconds incrementally)
lake build
# In-browser counter (TEA in 50 lines)
./.lake/build/bin/counter_web --port 8001
open http://127.0.0.1:8001/
# Multi-user SVG editor + MCP endpoint at /mcp
./.lake/build/bin/sheet_serve --port 8002 --db ../.leantea-state/sheet.sqlite
open http://127.0.0.1:8002/
# Board game (Reversi) โ client logic is a .leanjs file compiled at startup
./.lake/build/bin/reversi_serve --port 8005
# Chrome-CDP MCP server (drives your already-open Chrome)
# 1. Launch Chrome with: --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-cdp
# 2. Then:
./.lake/build/bin/chrome_cdp_mcp_serve --stdio
Tests are organised into two consolidated LSpec runners plus a handful of subsystem smokes:
persist_specโ Store roundtrips + Query DSL + Migration runner + Auth.Proof dispatch + SafeQuery typed SQL (32 assertions, one binary, one CI step).security_specโ SafeHtml + SafePath + SafeCmd + SafeHeader- SafeRedirect construction-time guarantees (60 assertions, one binary, one CI step).
template_smoke/crypto_smoke/http_*_smokeโ narrower per-module subsystem smokes that don't yet have an LSpec runner.
Read the runner source under examples/Tests/ as the shortest
"this is what works" demonstration of any given area.
Architecture
[Browser]
index.html โโโโโ GET /, /styles.css, /runtime.js
runtime.js โโโ
โ fetch('/api/step?msg=...', X-Model: <encoded>)
โผ
[Lean: sheet_serve]
Net.Server (Std.Internal.Async.TCP)
โ
SheetServe.handler
โโ "/" โ render the toolbar + SVG host from Template
โโ "/cells" โ SVG fragment built from Persist.Store.shapes
โโ "/api/*" โ Rpc.dispatch (typed Endpoint records)
โโ "/mcp" (POST) โ LeanTea.Mcp.handleMcp (text / image content)
โโ everything else โ 404
[Lean: Persist.Store (SQLite via FFI)]
shapes (id, kind, x, y, w, h, text, color, page_id)
pages (id, name)
audit (id, action, ts)
The client encodes the current Model in the X-Model header on
every action; the server runs WebApp.step (pure) and ships the new
model back the same way. SQLite is for things that need to outlive a
restart (shape DB, sessions, audit). No middleware stack, no
implicit context โ every clause in handler is one function from a
Request to a Response.
Persistent-style typed DB API
structure CellRow where
kind : String -- "rect" / "ellipse" / "text" / "sticky" / "pen"
x y : Int
w h : Int
text : String
color : String
instance : Entity CellRow where
table := "shapes"
ddl := "CREATE TABLE IF NOT EXISTS shapes(...)"
columns := ["kind", "x", "y", "w", "h", "text", "color"]
toRow s := #[s.kind, toString s.x, ..., s.color]
fromRow r := ...
-- Usage โ SafeQuery makes SQL injection unrepresentable:
namespace CellCols
open LeanTea.Persist.SafeQuery
def kind : Col CellRow String := โจ"kind"โฉ
def x : Col CellRow Int := โจ"x"โฉ
end CellCols
let shapes : Repo CellRow := Repo.new db
shapes.migrate
let _ โ shapes.insert { kind := "rect", x := 0, y := 0, w := 80, h := 40,
text := "hello", color := "#38bdf8" }
let rects โ SafeQuery.run shapes
{ where_ := .and (CellCols.kind.eq "rect")
(CellCols.x.gt 100) }
Where's value-leaf constructors are private to the SafeQuery
module โ there's no path from a raw String into a Where, so an
LLM-generated Where.eq "email" userInput is a compile error.
Sheet app + MCP server
examples/Sheet/ is a small functional spreadsheet:
lake build sheet_serve
./.lake/build/bin/sheet_serve --port 8002 --db ../.leantea-state/sheet.sqlite
open http://127.0.0.1:8002/
- SVG rendering for rect / ellipse / text / sticky / freehand pen
- Click to select, drag to move, drag corner handles to resize, double-click for in-place text editing (foreignObject editor), separate โ๏ธ Pen tool, color picker and W / H inputs
- State lives in SQLite (
cellstable)
MCP (Model Context Protocol) support
POST /mcp is a minimal JSON-RPC 2.0 endpoint so Claude or other
clients can edit cells directly:
# handshake
curl -X POST http://127.0.0.1:8002/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'
# list tools
curl -X POST http://127.0.0.1:8002/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
# add a shape
curl -X POST http://127.0.0.1:8002/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"set_cell",
"arguments":{"kind":"rect","x":100,"y":200,"text":"Hello"}}}'
Tools exposed:
add_shape(kind, x, y, w?, h?, text?, color?)โ new idmove_cell(id, x, y),resize_shape(id, w, h)set_text(id, text),set_color(id, color)delete_shape(id),list_shapes(),clear_all()
When wiring this into Claude Desktop / Claude Code, point its MCP
client at transport: "http", url: "http://localhost:8002/mcp".
Typed RPC (Servant-style, checked on both sides)
LeanTea.Rpc.Typed makes the request and response types part of the
endpoint. One declaration is the single source of truth, and both the
server and the browser client are checked against it โ a wrong field
is a compile error, not a runtime surprise.
structure SetCellReq where ref : String; formula : String
deriving Lean.ToJson, Lean.FromJson
structure SetCellResp where ok : Bool; value : String
deriving Lean.ToJson, Lean.FromJson
def setCell : Endpoint SetCellReq SetCellResp :=
{ name := "apiSetCell", path := "/api/set",
reqType := "SetCellReq", respType := "SetCellResp" }
Server. serve setCell (fun (r : SetCellReq) => โฆ) โ the handler
is SetCellReq โ IO SetCellResp. Dispatch decodes the body to
SetCellReq via the derived codec and encodes the result; a malformed
or wrong-shape body becomes a 400 at runtime (the wire carries
untrusted bytes, so that check is unavoidable), while the handler code
itself only ever sees a valid, typed request.
Client. The same endpoint generates the browser JS
(Endpoint.clientFn โ fetch + JSON.stringify(req) + r.json())
and a type-check stub. A .leanjs client that builds the request or
reads the response is checked against the very same SetCellReq /
SetCellResp: accessing a field the type doesn't have fails to
compile. LeanJs has no type system of its own, so rather than
reinvent one, LeanJs.TypeCheck emits the client to Lean and lets
Lean's own elaborator do the checking (asyncโdo, awaitโโ); the
shared types are imported, not re-declared, so there is exactly one
definition.
correct client โ type-checks โ
resp.value โ resp.valueX โ REJECTED โ (SetCellResp.valueX doesn't exist)
See examples/Tests/TypedRpcSpec.lean for the end-to-end proof (server
dispatch + client accept/reject + generated JS). The legacy
stringly-typed LeanTea.Rpc (Handler := List String โ IO String)
remains for back-compat.
CSS / JS DSLs
Both CSS and JS are also small ASTs with a render step.
open LeanTea.Css in
def sheetStyles : Sheet := [
rule ".btn" [("background", "#0284c7"), ("color", "#fff")],
rule ".btn:hover" [("background", "#0369a1")],
keyframes "ripple" [
("0%,100%", [("box-shadow", "0 0 0 8px rgba(239,68,68,0.25)")]),
("50%", [("box-shadow", "0 0 0 12px rgba(239,68,68,0.2)")])
]
]
open LeanTea.Js LeanTea.Js.E LeanTea.Js.S LeanTea.Js.Dom in
def helloFn : Stmt :=
afn "hello" [] [
constV "btn" (getById "btn-hello"),
doE (addEventListener (i "btn") "click"
(aarrow [] [doE (await_ (call (i "alert") [s "hi"]))]))
]
Block.render produces compact (one-line) JavaScript โ readable but
not pretty. Used by LeanTea.Rpc.clientLib to emit the typed RPC
client functions.
Google OAuth login
LeanTea.Auth is plugged into the example servers (sheet_serve,
reversi_serve, โฆ) and activates only when the environment
variables below are set so local development is unaffected.
| Variable | Required | Example |
|---|---|---|
GOOGLE_CLIENT_ID | yes | 123-abc.apps.googleusercontent.com |
GOOGLE_CLIENT_SECRET | yes | GOCSPX-... |
BASE_URL | yes | https://your-app.fly.dev (for redirect_uri) |
COOKIE_SECURE | no | 1 to flag cookies Secure on HTTPS |
ALLOWED_EMAILS | no | comma-separated email allowlist |
When both *_ID and *_SECRET are present:
GET /auth/google/loginโ mints a CSRF state and 302-redirects to GoogleGET /auth/google/callback?code=&state=โ posts to Google's/tokenand/userinfoviacurl(1), mints a session, sets an HttpOnly cookieGET /auth/logoutโ drops the cookie and the DB row/mcpand a few static asset paths are on a public allowlist- API paths under
/api/*return 401 when unauthenticated; UI paths 302 to the login page - Sessions live in the
sessionsSQLite table, CSRF state inoauth_states
Google Cloud Console setup
-
Create an OAuth 2.0 Client ID (Web application) at https://console.cloud.google.com/apis/credentials.
-
Add
${BASE_URL}/auth/google/callbackto "Authorized redirect URIs". -
Approve
email,profile,openidon the consent screen. -
Push the secrets to Fly:
flyctl secrets set \ GOOGLE_CLIENT_ID=โฆ \ GOOGLE_CLIENT_SECRET=โฆ \ BASE_URL=https://your-app.fly.dev \ COOKIE_SECURE=1 \ ALLOWED_EMAILS=you@example.com
Implementation notes
- HTTPS calls to Google go through
curl(1)(the Lean stdlib has no TLS). The runtime image already bundles curl. - Session tokens are 32 random bytes from
/dev/urandom, hex-encoded (Auth.randomToken). Important: useIO.FS.Handle.mk+read 32โIO.FS.readBinFilereads to EOF, and/dev/urandomnever EOFs, so it spirals into an OOM. Caught the hard way. Auth.gate cfg store publicPaths inneris aHandler โ Handlerwrapper. The inner handler is typed asSession โ Handlerso the logged-in user is available without leaking through globals.
Cloud deployment
Docker (works on any container host)
Dockerfile and fly.toml live at the repo root. The multi-stage
build installs the Lean toolchain on Debian Bookworm, builds the
binary, then copies just the binary into a slim runtime image
(~170 MB).
docker build -t leantea-sheet .
docker run -d --name leantea \
-p 8080:8080 \
-v leantea_data:/data \
leantea-sheet
open http://127.0.0.1:8080/
/data holds the SQLite file, so a container restart keeps history.
Fly.io (free tier)
The hobby tier is free for shared-cpu-1x ร 3 machines (256 MB each)
with up to 3 GB of persistent volume. auto_stop_machines is on, so
machines park themselves when idle and there's a 10โ20 s cold start
on the next request โ perfect for a personal app.
curl -L https://fly.io/install.sh | sh
flyctl auth login
flyctl launch --no-deploy # picks app name + region
flyctl volumes create sheet_data --region nrt --size 1
flyctl deploy
flyctl open
Other options
- Oracle Cloud Free Tier โ Always-free ARM Ampere instances (4 vCPU, 24 GB RAM). SSH in and run the binary, or deploy the same Docker image.
- Render โ Free web service tier (sleeps after 15 min idle). Dockerfile works out of the box; persistent disk requires a paid plan.
- Google Cloud Run โ Pay-per-use, 2 M requests/month free. The Lean binary runs fine; persistence has to move to Firestore or Cloud SQL since the FS is ephemeral.
Development & testing
LeanTEA's testing story has three layers, by intent:
| Layer | Tool | Purpose | Status |
|---|---|---|---|
| Type-level proofs (the negative space) | The compiler itself + Proof of Authorization + SafeQuery | "Could this auth bypass / SQL injection / XSS ever happen?" โ answered statically. What the compiler proves, you don't have to test. | โ shipped |
| Unit & smoke | LeanTea.LSpec โ a tiny LSpec-shaped runner with group / it + tree output | Pure-function business logic, update : Msg โ Model โ Model, codec round-trips, render output. Used by every examples/Smoke/* binary. | โ shipped |
| E2E (LLM-driven, exploratory) | examples/UiScript + browser_mcp_serve | Declarative JSON scripts (click โ screenshot โ LLM classify) that survive DOM refactors because the LLM reasons about intent. Pairs with examples/BrowserAgent for record-once / replay-many. | โ shipped |
| E2E (typed, deterministic) | LeanTea.WebSpec (planned v0.2) โ do-notation over ChromeCdpMcp | CI/CD regression tests with group "login flow" [ it "rejects bad password" do โฆ ]. Same mental model as LSpec, but with navigate / fill / click / expectText primitives. | ๐ง planned |
What this means in practice:
- Security tests don't exist in a LeanTEA codebase โ the compiler already enforced the property. Your test suite is just the happy-path business logic, which is drastically smaller than the equivalent Rails / Django suite (most of which exists to assert "the framework didn't let this bad input through").
- For UI regressions today, write a
UiScriptJSON and let the LLM drive it. For deterministic golden tests, wait for v0.2 or hand-roll thechrome_cdp_*calls in a smoke binary โ the surface area is already there.
Dev loop
tools/dev.py is a tiny stdlib-only file watcher: it lake builds on
save, restarts the dev server with DEV_MODE=1, and the page polls
GET /_dev/ping once a second so the browser auto-reloads after a
successful build.
python3 tools/dev.py --app sheet --port 8801
Community
- Discord: https://discord.gg/94Xueve8WD โ design discussion, weekly progress threads, beginner Q&A.
- GitHub issues: bug reports + feature requests on this repo.
- Roadmap & security model: ROADMAP.md + SECURITY.md.
License
c/sqlite3.c / c/sqlite3.h are public domain
(https://www.sqlite.org/copyright.html). Everything else is MIT.