A connection that cannot point at the machine it is running on
"Nothing runs on the LLeMbas host" is the sentence the absent sandbox and the absent local MCP rest on, and an SSH profile aimed at 127.0.0.1 walked straight past it -- through a real login, with every gate in policy.py still applying, onto the machine holding the database and the Fernet key. From the SSH layer down it is indistinguishable from a container on the network, so nothing here could have noticed. One switch, three positions: never, one named port, anywhere. The middle one is the one with a real use -- a container that published its SSH port on the loopback interface is genuinely somewhere else -- and port 22 is refused even there, because that one is this host's own sshd. Enforced in five places, because a row can predate a setting: saving a profile, `session.resolve` (the control every agent tool, the terminal and the canvas go through), the composer's picker, browsing, and the draft the panels open against before a chat exists. Check refuses before it opens its socket rather than after. And the recognition never resolves a name on the request path. `refusal` runs several times per page render; the first version of this looked names up inline and the suite went from two minutes to not finishing. Literal forms are decided from the string, a name is settled where a network call is already expected, and the answer lives on the row. The gap that leaves is written down rather than discovered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ lembas info # paths + counts, useful when confused
|
||||
lembas secret-key # generate LEMBAS_SECRET_KEY
|
||||
lembas create-admin # create or promote an admin
|
||||
|
||||
pytest # 1828 tests, ~2min
|
||||
pytest # 1854 tests, ~2min
|
||||
# PLAN.md tracks what is and is not built
|
||||
ruff check . # lint (line length 100)
|
||||
python scripts/build_artwork.py # regenerate artwork (SVG + PWA icons;
|
||||
@@ -791,6 +791,33 @@ meet it. Exactly what `--header-height` already does at the top of the shell. A
|
||||
composer that grows past it as somebody types is expected; nothing is pretending
|
||||
the sidebar should follow.
|
||||
|
||||
**An SSH connection may not point at this machine unless an administrator says
|
||||
so.** "Nothing runs on the LLeMbas host" is the sentence the absent sandbox, the
|
||||
absent local MCP and the whole security story rest on — and a profile pointed at
|
||||
`127.0.0.1` walked straight past it, looking from the SSH layer down exactly
|
||||
like a container on the network. `services/agent/hosts.py` is one switch in
|
||||
three positions: **off** (the default, and the default on upgrade), **port**
|
||||
(one named port, for a container that published SSH on the loopback interface —
|
||||
22 is refused there regardless, being this host's own sshd), and **on**.
|
||||
|
||||
Enforced in five places because a row can predate a setting: saving a profile,
|
||||
`session.resolve` (the control — every agent tool, the terminal and the canvas
|
||||
go through it), the composer's picker, `browse`, and the draft endpoint that the
|
||||
panels open against before a chat exists. Check refuses *before* opening its
|
||||
socket.
|
||||
|
||||
**And the recognition never resolves a name on the request path.**
|
||||
`is_loopback` reads the string only. A *name* pointing at loopback needs
|
||||
`getaddrinfo`, which blocks, and `refusal` is called several times per page
|
||||
render — the first version resolved inline and the suite went from two minutes
|
||||
to not finishing. So resolution happens where a network call is already expected
|
||||
(saving, and Check) and is written to `SshProfile.resolves_here`, which the
|
||||
request path reads for free. The gap this leaves is stated rather than
|
||||
discovered: a name whose DNS moves *after* it was saved is not noticed until it
|
||||
is saved or checked again. `tests/conftest.py` runs the suite with the switch
|
||||
**open**, because the tests that stand up a real asyncssh server can only listen
|
||||
on loopback; `tests/test_agent_hosts.py` closes it explicitly.
|
||||
|
||||
**An empty htmx verb is a request, not a no-op.** htmx looks for the
|
||||
*attribute* — `if(s(t,"hx-"+r))` is `hasAttribute` — so `hx-get=""` is a real
|
||||
request for the empty path, which the browser resolves against the current
|
||||
|
||||
Reference in New Issue
Block a user