Skip to content

v1.7.0

Latest

Choose a tag to compare

@airiclenz airiclenz released this 14 Aug 15:49

Added

  • A server's API key can come from a command instead of the config file. A servers: entry may now set api_key_cmd — a line whose standard output is the key, typically a lookup in the machine's secret store (security find-generic-password -s llama-launcher -a llamacpp -w). The line goes to the platform shell (sh -c, cmd /C on Windows) so a pipeline works without a wrapper script, and is resolved once at config load for enabled entries only. The command gets no stdin and neither of the launcher's standard streams — it may run under the menu, which owns the terminal — so a store that must ask the human to unlock has to prompt through its own GUI agent. It is bounded by a 60-second timeout and a 64 KiB output cap, and a command that fails, times out, overruns the cap or prints nothing fails the load naming the entry and quoting its stderr: a key source that answers with nothing is a broken source, not a keyless server. Setting both api_key and api_key_cmd, or an api_key_cmd holding only whitespace, also fails the load. The companion marker plaintext_key_ok: true records that a literal key is meant to stay in the file.

  • The interactive menu offers, once per launch, to move a plaintext key into the machine's secret store. Finding enabled entries that still keep api_key as a literal without plaintext_key_ok: true, the launcher raises a single offer before the menu is drawn, naming those entries and their file: move it into the store (the entry reads the key with api_key_cmd afterwards), not now, or never for these entries. Finding none it does nothing at all — not even a store probe. Moving is one ordered move per entry: write the secret, read it back by running the exact api_key_cmd about to be persisted, and rewrite the entry only if what comes back matches, so a failed read-back leaves the config byte-identical and the run keeps working off the literal it has. The rewrite splices text rather than re-marshalling, so comments, blank lines and key order survive; anything it cannot do safely is refused with an edit-by-hand error, and the spliced bytes are re-parsed before an atomic 0600 write. Subcommands never prompt — they emit a one-line warning: on stderr naming the entries, the config file this run read, and the three ways out by hand.

Changed

  • Starting a managed server now checks the target port first and names whoever holds it. The start path refused to spawn where a server of the same backend was still coming up, but had nothing to say about a foreign occupant: it forked, llama-server died inside the grace window with couldn't bind HTTP server socket, and that surfaced as a generic "server exited immediately after start". A pre-flight check now runs between the Starting probe and the binary lookup: if anything is listening on the port, the start is refused (exit code 3) with the port, the address the backend cannot bind, every listening PID with its executable name, and guidance to stop the occupant or configure a different port. The check is one-directional — when lsof cannot answer it reports no occupant and the start proceeds — and deliberately port-wide, since a listener on any interface fails the bind. That asymmetric case also shadows loopback for every other launcher command, so a status that normally answers in under 0.1 s takes 4 s; VS Code's Remote-SSH and Dev Containers port auto-forwarding is a common source, and the README's new "When the port is already taken" section documents the symptom pair.

Security

  • The api_key for llamacpp reaches llama-server through the environment instead of the command line. It used to be appended as --api-key <key>, putting the credential in ps output for every user on the machine for as long as the server ran. It is now exported as LLAMA_API_KEY, which llama-server reads only when no --api-key flag is present, so a user's own extra_args override still wins — and that literal override is still visible in ps, as the README says.

  • The launcher no longer follows redirects from configured LLM servers. authedGet and authedPostJSON each built a default http.Client, which follows up to ten hops — so whatever answers on a configured port could steer an outbound request carrying the launcher's Authorization: Bearer <key> header at a destination of its choosing. Both now share a client whose CheckRedirect returns http.ErrUseLastResponse: the first 3xx comes back unfollowed and no second request is issued.

  • The MCP adapter enables cross-origin protection on its streamable HTTP handler. The IP allowlist admits a whole machine, so a browser page on an allowlisted machine could drive the control plane from a foreign origin. crossOriginHandler now sits between the allowlist and the MCP handler, always on: real MCP clients send no Origin header, or a same-origin one, and pass straight through.

Full details in CHANGELOG.md.