A bug every web dev has shipped, caught by a compiler that refuses to make human assumptions — and pinned to the real code that ships.
Press → to break something.
function withdraw(balance, amount) {if (amount <= balance)return balance - amount;return balance;}
Two unit tests pass. Ship it?
A human reads amount as "a positive number".
The compiler reads it as "every integer that exists".
-- a withdrawal never grows your balance:theorem never_grows (balance amount : Int) :withdraw balance amount ≤ balance
No test values. balance and amount are variables over all integers — infinitely many pairs, one claim.
#eval withdraw 0 (-50)50 ← a zero balance became £50. Alice stole money.
$ lean Mini.leanerror: omega could not prove the goal:a possible counterexample may satisfy:b ≤ -1 ← the amount is NEGATIVEwhere a := balance, b := amount
Nobody typed −£50 as a test case. The failed proof derived that negative amounts are exactly where it breaks.
A failing test says "this one input broke". A rejected proof says the shape of every breaking input — which is why the fix is mechanical.
def withdraw (balance amount : Int) : Int :=if 0 ≤ amount ∧ amount ≤ balancethen balance - amount else balance-- theorem unchanged:theorem never_grows ... := byunfold withdraw; split <;> omega
$ lean Mini.lean✓ no output — silence IS the proof#print axioms never_grows[propext, Quot.sound] ← no sorry: no fake proof
Proved for every pair of integers — and the axiom audit means a future "temporary sorry" fails the build instead of faking green.
if (abs.startsWith(UPLOAD_DIR)) // looks fine
A .. buried mid-path. You'd unit-test a.png and ../../etc/passwd — nobody tests a .. in the middle.
-- careless guard accepts?true-- where does it really resolve?["srv", ".ssh", "id_rsa"] ← OUT of the vault-- the shipped guard (resolve FIRST)?false ✓ rejected
Shout a filename. Whatever you say — the theorem already covers it.
All six escapes share one shape: a .. right after the managed prefix — the exact condition the proof isolates. We never picked them.
The sweep is exhaustive for the slice. The theorem is what covers the infinity beyond it — by induction, not enumeration.
✔ accepts a real file inside the managed dir✔ rejects the Lean counterexample (mid-path ..)✔ rejects sibling dir "moss-uploads-evil"✔ rejects missing files, relative, emptyℹ pass 4 · fail 0
| layer | guarantee |
|---|---|
| theorems ↔ model | certain — compiler-checked, zero sorry |
| model ↔ JS | pinned — same witness runs in Node; refactor away path.resolve and CI goes red |
| known gaps | symlinks (statSync follows links) · POSIX only · named in README, not hidden |
Lean turns "is this safe?" into "is my model faithful?" — a small, attackable question instead of a vibe.
$ curl https://elan.lean-lang.org/elan-init.sh -sSf | sh # toolchain$ lean Mini.lean # 9 lines, seconds- run: (cd proofs && lake build) # one line, next to node --test
Unit tests check what you imagined.
Proofs check what's possible.
Proofs: moss/proofs/Uploads.lean · pinned by uploads.test.js — Moss 🪨