96f269dadb
The security pass. Six findings, none reachable by visiting the site and every one a boundary this codebase says it keeps. A subagent is pinned to a list of read-only commands, in every mode, unattended, with no card anybody could approve -- and `find *` was on it. find writes files with -fprintf, runs programs with -exec and removes them with -delete, and none of that needs a character the metacharacter guard refuses. A page the model had just read could ask for a helper and get a key into authorized_keys, from Plan mode, which promises to change nothing. Refused in `subject()` rather than trimmed from the list: a pattern cannot say "and no dangerous flags", and "this one looks read-only" is exactly what put find there. The loopback guard missed `0.0.0.0`, which is not is_loopback but does connect to localhost -- so it answered a *decided* False and skipped the DNS half too. The one spelling of "this machine" that walked past a guard whose whole job is that sentence. Twice in the update helper, which is the one place this deliberately crosses a privilege boundary: root ran a script the service account owns, and root sourced a file that account can replace. Either turns a compromise of the web application into root. The first needed no compromise at all -- a pull happens as the service user and root runs whatever it fetched, so control of the branch was control of root. The old test asserted that exact ExecStart line and had pinned it in place. Push endpoints skipped check_url, the only outbound request that did. And a chat could be filed in another account's folder, which hands over its system prompt -- `_new_chat` resolved the folder, discarded it when it was not the caller's, and stored the raw id anyway. An existing helper install keeps the old wiring until install.sh is re-run; update.sh now says so when it finds itself inside the checkout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
305 lines
16 KiB
Markdown
305 lines
16 KiB
Markdown
# Changelog
|
|
|
|
What changed, per version, for somebody using or running LLeMbas — not a
|
|
restatement of the commit log. If a change fixed something that *looked* like it
|
|
worked, that is worth a line: those are the ones nobody would otherwise know to
|
|
stop working around.
|
|
|
|
Newest first. Versions are `__version__` in `src/lembas/__init__.py`, which is
|
|
the only place a version is written.
|
|
|
|
The first tagged release is **1.0.0**. Everything below it shipped as a running
|
|
deployment rather than as a release, and is recorded here so the release notes
|
|
for 1.0.0 have something to be assembled from.
|
|
|
|
---
|
|
|
|
## Unreleased
|
|
|
|
## 0.9.12
|
|
|
|
**The security pass.** Six findings, all fixed. None is reachable by simply
|
|
visiting the site; every one of them is a boundary that was supposed to hold
|
|
and did not.
|
|
|
|
- Fixed: **a helper could write files and run programs on the remote machine,
|
|
unattended, in a mode that promises to change nothing.** A subagent is pinned
|
|
to a fixed list of read-only commands — and `find` was on it. `find -fprintf`
|
|
writes a file, `find -exec` runs a program, `find -delete` removes one, and
|
|
none of them needs a character the shell-metacharacter guard refuses. A page
|
|
the model had just read could have asked for a helper and got an SSH key
|
|
written into `authorized_keys`. Those flags are refused outright now, whatever
|
|
list a command is on.
|
|
- Fixed: **an SSH connection could be pointed at `0.0.0.0` and reach the machine
|
|
LLeMbas runs on**, with the "may a connection point here" setting still
|
|
reading *off*. Every other spelling was caught; that one is neither a real
|
|
destination nor a refused one, and connecting to it goes to localhost.
|
|
- Fixed, twice, in the update helper — the one place this deliberately crosses a
|
|
privilege boundary: **root ran a script the unprivileged service account
|
|
owns**, and **root sourced a file that account can replace**. Either turns a
|
|
compromise of the web application into root on the host, which is exactly what
|
|
the unprivileged split exists to prevent. The first also meant control of the
|
|
branch was control of root, with no compromise needed at all.
|
|
**If you installed the update helper before this, re-run the installer** —
|
|
the old wiring stays until you do, and the update script now says so loudly
|
|
when it notices.
|
|
- Fixed: **browser notification endpoints skipped the guard that stops the
|
|
server being aimed at your own network.** It was the only outbound request in
|
|
the codebase not going through it.
|
|
- Fixed: **a chat could be put in another account's folder**, and a folder hands
|
|
its system prompt to the chats inside it — so that read a setting across an
|
|
ownership boundary through a field that looks like a tag.
|
|
- Fixed: a `"` typed into the share panel's search box silently stopped every
|
|
checkbox in the panel from doing anything.
|
|
|
|
## 0.9.11
|
|
|
|
- The Updates page no longer runs the **Check the remote** button flush against
|
|
the version and commit above it, where the two read as one block.
|
|
|
|
## 0.9.10
|
|
|
|
**The second audit pass: screens that were harder to use than they needed to
|
|
be.** Checked by rendering them in a real browser and measuring, not by reading
|
|
the CSS.
|
|
|
|
- Fixed: **the Prompts admin page put its reference material first.** The
|
|
Variables legend and the Preview run to a screen each and sat above the tabs,
|
|
so the editor — the thing the page is for — started two screens down and every
|
|
tab switch had to move the whole page to be any use. On a short tab it could
|
|
not move far enough and left the panel stranded above a screenful of nothing.
|
|
The editor comes first now, the reference after, and the tab bar stays put:
|
|
measured, it moved 385→642px between tabs before and does not move at all now.
|
|
The tab bar also sticks to the top, so a long panel does not scroll it away.
|
|
- Fixed: **custom themes were three fixed slots.** A fresh instance opened on
|
|
fifty-seven empty colour boxes under three identical headings, and a fourth
|
|
theme could not be made at all. Now: one block per theme you have, plus one
|
|
blank to add the next, with the colours behind a disclosure — so a theme is a
|
|
name and a starting point until you ask for more. Up to twelve. The page is
|
|
half the height it was.
|
|
- Fixed: **deleting a chat left every file it held on disk.** The rows went —
|
|
the message, the attachments, the generated images — and the files they named
|
|
stayed, with nothing that would ever look at them again. Four of the five ways
|
|
a chat can end had this: the delete button, a schedule's task chat, a helper's
|
|
hidden chat, and deleting an account. There is one function that deletes a
|
|
chat now, and it removes the files first.
|
|
- Fixed, and it is what made the above invisible: **a file attached before the
|
|
chat existed never learned which chat it belonged to.** Anything picked on the
|
|
new-chat screen kept an empty `chat_id` for the rest of its life. Six things
|
|
filter on that, so for those files the model was not told they were attached,
|
|
the canvas would not open them, and the cleanup could not find them.
|
|
- **Folders can be nested, which the README has always claimed.** The route has
|
|
handled it since folders existed — cycle guard, depth limit — and the sidebar
|
|
has always drawn a tree; there was simply no control that could ask for it.
|
|
Moving a folder also respects the depth limit now, which only creating one did.
|
|
- The Proxmox container installs the **update helper by default**. A container
|
|
made thirty seconds ago to run one thing is not the shared host the plain
|
|
installer has to be careful about, and an appliance you cannot update without
|
|
a shell is one nobody updates. `INSTALL_UPDATE_HELPER=0` opts out. Docker
|
|
deliberately has no equivalent: updating a container is pulling an image, and
|
|
a helper inside one would need the Docker socket, which is root on the host.
|
|
- The starting points on the new-chat screen are four new ones, aimed at
|
|
somebody who has just stood an instance up and wants to know what is behind
|
|
it. Only a fresh install gets them; an instance that has already seeded keeps
|
|
whatever its administrator has made of the list.
|
|
- `README.md` describes what this actually is again — schedules, reports,
|
|
helpers, image generation, semantic search, quotas, sharing, branding and the
|
|
updates page were all missing, and two things listed as *planned* had shipped.
|
|
It gained sections on Docker, the Proxmox container and updating.
|
|
|
|
## 0.9.9
|
|
|
|
**The first of five audit passes before 1.0.0** — everything that landed between
|
|
0.8.1 and 0.9.8 read as a whole rather than one feature at a time. This one is
|
|
the main logic, the harness, and every instruction a model is given.
|
|
|
|
- Fixed: **every model was told the time in a zone with no name.** On any
|
|
account that had not chosen a timezone — which is the default state of every
|
|
account — the date line shipped as "Times the person gives you are in
|
|
unless they say otherwise", on every request. The code claimed in two places
|
|
that the line disappeared instead. It never had.
|
|
- Fixed: **the prompt preview could not show most of what it previews.** Eleven
|
|
fragments are gated on things that only exist once there is a real chat, and
|
|
the preview has none — so the whole agent surface, both scheduling fragments
|
|
and the helper warning were missing from it whatever you ticked. Editing
|
|
`tool.agent` and pressing preview showed a system message without `tool.agent`
|
|
in it, and nothing said so. Two new controls come with the fix: what kind of
|
|
chat to preview as, and which agent mode.
|
|
- Fixed: **a model in Plan mode was told to use a tool it did not have.**
|
|
`plan_update` is withdrawn in that mode in favour of `plan_submit`, but its
|
|
guidance appeared whenever a plan existed — directly under the line saying
|
|
anything not in your tool list does not exist.
|
|
- Fixed: **reading one knowledge document could fill the whole context window.**
|
|
Every other reader caps what it returns and says so; this one returned the
|
|
document whole, and its description said "in full", so it did exactly what it
|
|
claimed. A long PDF is now cut at 40,000 characters with the model told.
|
|
- Fixed: **the guidance about helpers on a machine was wrong in both
|
|
directions.** It denied that a helper can write files, which is a documented
|
|
option of the tool beside it, and it named seven of the twenty-three commands
|
|
a helper may run — so a model avoided commands it was allowed to use. Both are
|
|
now checked against the real list and the real schema by tests, because prose
|
|
and a constant drift the moment one is edited alone.
|
|
- The tool description for delegating no longer claims a helper gets "the same
|
|
tools". It gets deliberately fewer, and sizing a task against the wrong set is
|
|
how a whole phase gets planned around something that will refuse it.
|
|
|
|
- The Updates page notices when the update helper on a host was installed for a
|
|
**different channel** than the page follows. It is declared in two places —
|
|
`lembas.env` and the systemd unit — and only the installer writes both, so
|
|
editing one by hand would have left the button deploying something other than
|
|
what the page named, with nothing anywhere saying so.
|
|
- Fixed: release notes from a **signed** tag rendered the signature block.
|
|
`_notes_for` stripped the PGP header only, and which header appears depends on
|
|
`gpg.format` — this repository signs with SSH.
|
|
- A `CHANGELOG.md`, kept from now on rather than assembled at release time.
|
|
|
|
## 0.9.8
|
|
|
|
**Updates follow a channel, not a commit.** `stable` tracks the newest `vX.Y.Z`
|
|
tag; `edge` tracks the branch tip. A branch tip is not a release — following one
|
|
means deploying whatever was pushed five minutes ago — so stable is the default
|
|
for anybody who is not the person writing it.
|
|
|
|
- The Updates page shows a **version** rather than a commit sha: `1.0.0` at a
|
|
tag, `1.0.0-7-gd4f56d` seven commits past one, and a bare sha only before the
|
|
first release exists.
|
|
- Release notes come out of the **annotated tag itself**, so no forge API is
|
|
involved anywhere. That matters: the Gitea API this was checked against
|
|
returns a 500 from a server-side panic on exactly the releases endpoint.
|
|
- A tag with a suffix (`v1.1.0-rc1`) is deliberately not a release — git's
|
|
version sort ranks it *above* `v1.1.0`, so accepting one would step a stable
|
|
host onto a candidate.
|
|
- Fixed: `deploy/update.sh` stopped silently after `== fetching ==` on any host
|
|
with no release tags — which was every host. Fetched, not reset, not
|
|
restarted, and no error printed.
|
|
- Fixed: `install.sh` now refuses an `ssh://` repository URL up front instead of
|
|
letting the clone fail as a service user with no key.
|
|
|
|
## 0.9.7
|
|
|
|
**Packaging, and updating without a shell.**
|
|
|
|
- `/admin/updates`: what is running, what is available, and what changed between.
|
|
A button applies it — answered by an **opt-in** systemd helper, because the
|
|
service runs unprivileged and a web application that can restart its own
|
|
service is one whose worst day is much worse. Without the helper the page says
|
|
so and prints the command.
|
|
- `Dockerfile` and `docker-compose.yml`. No secret key, no data and no `.git`
|
|
baked in; loopback only; a TLS proxy expected in front, because a service
|
|
worker and a microphone both require HTTPS or localhost.
|
|
- `deploy/lxc-install.sh` creates an unprivileged Proxmox container and runs the
|
|
existing installer inside it.
|
|
- `/healthz`, which opens the database rather than only proving the socket is
|
|
listening.
|
|
|
|
## 0.9.6
|
|
|
|
**Permissions, quotas and sharing.**
|
|
|
|
- **"What can this account actually do?"** answered on screen, naming *where*
|
|
each permission came from — admin, the baseline, or a group.
|
|
- Users and groups are list-plus-detail, and membership is edited from **one**
|
|
side. It was on both, and a save from either overwrote what the other showed.
|
|
- Reading and writing split for notes, memory and skills.
|
|
- **Quotas on a group** — monthly tokens, concurrent replies, agent wall clock,
|
|
images a day, helpers a reply. Resolved by maximum across a person's groups,
|
|
with zero meaning *no limit* and winning outright.
|
|
- Fixed: **deleting a group or an account left every share naming it behind.**
|
|
`forget_principal` had existed since shares did and was called by nobody.
|
|
- Fixed: `library.share` defaulted to off, so sharing shipped documented as done
|
|
and unreachable — the panel only renders for somebody who holds it.
|
|
- The share panel is its own action with a search box. It used to be checkboxes
|
|
inside the resource's save form, listing every account on the instance, and a
|
|
tick only took effect if you also saved the resource.
|
|
- Reports are shareable, and every listing has a **Shared with me** filter.
|
|
|
|
## 0.9.5
|
|
|
|
**Extraction settings, embeddings, and hybrid search.**
|
|
|
|
- `/admin/extraction`: upload size, image edge, JPEG quality, PDF pages,
|
|
extracted characters, orphan age, extra text extensions.
|
|
- An **embedding model** can be chosen from models flagged for it. Library search
|
|
then fuses keyword and semantic ranking, so *"how do I get paid"* finds a
|
|
document that says *"invoicing"*.
|
|
- **Choosing none is not a degraded mode**: no rows written, no requests made,
|
|
and byte-for-byte the keyword search that was always there.
|
|
- Vectors carry their model and width, and a mismatch is skipped rather than
|
|
scored — comparing two embedding spaces produces a confident wrong answer.
|
|
- Indexing happens in the background as records are written, with a rebuild
|
|
button for everything that already existed.
|
|
|
|
## 0.9.4
|
|
|
|
**An instance can be somebody else's.**
|
|
|
|
- Name, tagline, logo, favicon and launcher icons derived from the logo.
|
|
- The Middle-earth wording is editable data. Leaving a box alone does not freeze
|
|
it, so a later release can still improve the default.
|
|
- **Custom themes** as a set of colours rather than a stylesheet, inheriting
|
|
whichever built-in they start from.
|
|
- Global CSS overrides, served as `/branding.css`.
|
|
|
|
## 0.9.3
|
|
|
|
**Subagents.** A reply can hand a self-contained piece of work to a helper that
|
|
runs on its own and reports back — several at once, so research fans out instead
|
|
of queueing.
|
|
|
|
- A helper cannot ask questions, cannot send helpers of its own, writes nothing
|
|
unless the call asked and the chat's mode allowed it, and on a machine runs
|
|
only a fixed list of read-only commands — in **every** mode, including Auto.
|
|
- Fixed, and it was live in scheduled runs too: an unattended chat that hit an
|
|
approval built a card nobody could see and sat on it for fifteen minutes.
|
|
|
|
## 0.9.2
|
|
|
|
**Image generation defaults an administrator can actually set** — steps, cfg,
|
|
size, sampler, scheduler, denoise, negative prompt, checkpoint, batch. There were
|
|
none: one hard-coded set from the SD1.5 era, and prose in a box as the only way
|
|
to change it.
|
|
|
|
- The samplers and schedulers ComfyUI had been reporting all along are now the
|
|
pickers; nothing had ever read them.
|
|
- The tool's own schema restates the instance's defaults, instead of telling the
|
|
model "Default 512" beside an instance that draws at 1024.
|
|
|
|
## 0.9.1
|
|
|
|
**Everything that arrives is announced, not only chat replies.** A scheduled run
|
|
that filed a report used to light a dot in a corner and say nothing.
|
|
|
|
- A count in the tab title while you are looking elsewhere.
|
|
- **Web push**, so a schedule firing at seven in the morning reaches a browser
|
|
that is shut. Opt-in per device. It is the one thing here that contacts an
|
|
outside service, and `services/push.py` says so plainly.
|
|
|
|
## 0.9.0
|
|
|
|
**A model can schedule things.** There was no tool for it — asked to "remind me
|
|
every Monday", a model wrote a note and reported that it had scheduled
|
|
something, and every screen agreed with it.
|
|
|
|
- `schedule_create`, `schedule_list`, `schedule_update`, `schedule_cancel`, over
|
|
the same rule normaliser the manual form uses.
|
|
- The reply says the resulting timing back in words, which is the only moment
|
|
anybody can check that Monday was understood as Monday.
|
|
|
|
## 0.8.3
|
|
|
|
**An SSH connection may not point at this machine unless an administrator says
|
|
so.** A profile aimed at `127.0.0.1` walked straight past "nothing runs on the
|
|
LLeMbas host" — through a real login, onto the machine holding the database and
|
|
the encryption key. Three positions: off, one named port, or anywhere.
|
|
|
|
## 0.8.2
|
|
|
|
- Fixed: **opening the canvas before a chat existed swapped the whole site into
|
|
the panel.** `hx-get=""` is not "fetch nothing" — htmx looks for the attribute,
|
|
not the value, so the empty one was a real request for the current document.
|
|
- Fixed: the Canvas and Terminal buttons appeared where they could not work.
|
|
- The bottom edge of the shell is no longer drawn, so the sidebar footer and the
|
|
composer stop meeting a line at two different heights.
|
|
- Admin pages scroll in one container; `/admin/prompts` no longer drops you at
|
|
the bottom of a shorter panel.
|