mirror of
https://github.com/hanzoai/lsp.git
synced 2026-09-30 22:52:48 +00:00
d0442c59ef054e3f3a0ccb98dfe19237baae7fe9
The boot self-test failed on every host it ran on. The pod reported
JAIL SELF-TEST FAILED — refusing every request
err="jail: serve canary did not run: exit status 126"
and refused every request, correctly. 126 is not execve's — it is this jail's
own setupFailed. The child's stderr said what actually happened, and Probe threw
it away:
jail: chdir /tmp/jail: no such file or directory
ROOT CAUSE, and it is an ordering one. run() bound every allowed path at its own
absolute path and THEN mounted the writable tmpfs at <root>/tmp. A mount whose
path is an ancestor of an earlier one covers it, and nothing fails when it
happens: the bind is still there and simply cannot be seen. Every bind under
/tmp was placed and erased — and /tmp is where the staging directory lives, so
the self-test masked its own working tree and died on chdir with ENOENT.
The filesystem is now a value: plan() returns the mounts ordered shallowest path
first, one mount per path, so an ancestor-after-descendant order is not
representable and nobody has to remember which directories are safe to hand a
jail. The jail's own /tmp claims that path first, so a caller naming /tmp cannot
swap the host's directory in for the private tmpfs that dies with the process.
The working tree is the one path that is required: missing, it names itself
instead of leaving a chdir to report the symptom.
The canary is now EXEC'D — `lsp canary`, this same binary, reached through argv
because the child strips every control variable before exec. It used to report
from inside the process that built the jail, so the exec a language server
depends on was the one step the self-test never took.
Probe loses its second argument. It took a staging root AND a scratch working
directory, and shipped with the two in a relationship that cannot work; there is
now nothing to get wrong. It also distinguishes ErrHost — this host cannot
create namespaces — from every other failure, which is the jail's own.
Tests, all of which fail on the previous code:
TestThePlanHidesNothing no kernel, no privileges: nothing may cover an
earlier mount. Fails with the shipped order.
TestThePlanIsThePhase serve writes nothing, fetch writes the tree and
the cache, the tree is required.
TestTheJailKeepsItsOwnTmp an argument cannot replace the jail's /tmp.
TestProbeProves… now FAILS rather than skips when the jail builds
and misbehaves; skips only on ErrHost.
TestMain (both packages) a jail re-execs `self`, which under `go test` is
the test binary — without this the child re-ran
the suite and the canary compared "PASS" against
"socket=denied".
Measured on the gVisor node (code-exec-pool, runsc), work under /tmp:
serve reports `socket=denied operation not permitted`, fetch `socket=open`.
Probe's rlimits are generous on purpose — RLIMIT_NPROC is per-UID and shared
with the daemon's own threads, and at 32 a Go program cannot start a third.
hanzoai/code-exec's jail_linux.go, which this was copied from, orders its mounts
the same way and has the same bug wherever it eventually deploys.
Co-authored-by: Hanzo Dev <[email protected]>
lsp
The jailed language-server daemon. It holds immutable per-(org, repo, commit) working trees, runs a language server over each one inside a jail with no network, and answers position questions about them.
The answer it exists for is a definition that leaves the repository and lands in a dependency — which a static symbol index structurally cannot give you.
POST /root {org, repo, rev, files} → {ready, cold, langs}
POST /root/warm same body, only for a held repo
POST /ask {org, repo, rev, op, path, line, character, relation}
→ the answer, or 409 {"need":"tree"}
GET /healthz liveness
GET /readyz readiness — green only once the jail proved out on this host
Everything else — the wire contract in full, the two-phase security model, the deployment, and what phase 2 is — is in LLM.md.
Description
Jailed language-server daemon: immutable per-(org,repo,commit) roots, gopls behind a seccomp jail with no network. Backs /v1/code/lsp.
192 KiB
Languages
Go
93%
Dockerfile
7%