Skip to main content

Module http

Module http 

Source
Expand description

A synchronous HTTP/1.1 server, just large enough to serve the portal.

Nothing here is a general-purpose web server and nothing here should become one. It answers requests from one browser on one machine over the loopback interface, which is why it is a few hundred lines of std::net rather than a dependency: every HTTP crate that does chunked responses and a thread pool brings a tree semlith would then be shipping to every user of a local search tool, and the parts of HTTP this needs are the parts that fit on a page.

Three rules are enforced here, before any handler sees a request, because they are the product’s privacy claim and a claim enforced in one place is one that can be read in one place:

  • Loopback only. Server::bind binds 127.0.0.1 and there is no flag to change it.

  • A per-run token, carried in a request header. Generated at start, printed once in the URL, handed to the page from that URL, and sent back as Semlith-Token on every API request. A request without it gets 401 and an empty body — not a login page, not an error object, nothing that tells a prober what is here.

    It was a cookie until 0.14.0, and a cookie is the wrong container for it: localhost is one site however many ports are listening on it, so SameSite=Strict never separated this server from a page served by anything else on 127.0.0.1. A header the browser will not attach cross-origin does separate them. The cost is that a browser attaches no header to a stylesheet, a font, a favicon or an <img src>, so the page and its own static assets are served without a credential — they are the same bytes in every copy of the binary — and everything that answers about this machine is not.

  • A write has to come from this origin. Every request that is not a GET or a HEAD must carry Sec-Fetch-Site: same-origin, or — for a client that sends no fetch metadata at all, which is every client that is not a browser — an Origin matching this server or no Origin. It must also carry a JSON content type, because a body a browser can send with no preflight is a body this server will not read. Both are checked before the token, so a cross-origin page learns nothing from the difference between a right and a wrong guess.

  • A persisted agent key, and only for /mcp. From 0.13.0 the daemon also answers MCP over HTTP, and that endpoint needs a credential a client can keep in a configuration file — which the session token can never be, because it changes every run. The two have deliberately separate lifetimes and separate reach: the agent key opens /mcp and nothing else, so a key in a config file can never reach the rotate, adopt or upgrade routes, and rotating the session token does not disconnect an agent.

  • A Host header that is localhost. A page on another origin cannot read a cross-origin response, but it can send the request, and a DNS name that resolves to 127.0.0.1 would otherwise reach this server with the browser’s cookies attached. Only localhost and 127.0.0.1, with an optional port, are answered.

Every response also carries a Content-Security-Policy that allows only 'self', and no CORS header is ever emitted — there is no origin this server wants to be readable from.

Structs§

Chunks
What a handler writes a streaming body through.
Request
One parsed request.
Response
Server

Enums§

Refusal
Why a request was refused, for the one-line-per-class count the daemon logs.

Constants§

DEFAULT_PORT
The port semlith start listens on unless told otherwise.
MCP_PATH
The one path the agent key opens.
PORT_ENV
Overrides DEFAULT_PORT.
TOKEN_HEADER
The header the session token travels in.

Type Aliases§

Handler
A handler: everything the daemon serves, behind one function.