title: v0.7.0 — security-audit remediation, supply-chain hardening, and committee governance
description: Release notes for v0.7.0, as recorded in docs/releases/v0.7.0.md.
url: https://legalquants.github.io/lq-ai/changelog/v0-7-0/
checked-against: b092e1f5 (2026-08-10)
status: draft
kind: explanation
sources: docs/releases/v0.7.0.md

> **Status: ready for the `v0.7.0` tag.** The blocker is cleared — **ADR 0025** (release versioning
> policy and pipeline ordering) was accepted by the committee at the 2026-08-09 weekly call and
> merged as **#487**, so the `version-consistency` gate and the backend-before-frontend pipeline
> this release is cut under are live on `main`. This PR marks ADR 0025 Accepted, bumps `api` and
> `gateway` `__version__` to `0.7.0`, and regenerates the committed OpenAPI export, which carries
> the api version. **This is the first release cut under ADR 0025.**

This is a **hardening release**, not a feature milestone. It is the first substantial remediation
of the 22-finding 360° security audit (**#288**, Claude Fable 5), and it lands supply-chain, CI, and
governance work alongside it. Its headline is that **all three High-severity audit findings are
closed**, together with three Mediums. Per ADR 0025 the number is computed from what has accumulated
on `main`: three of these changes (#396, #399, #400) require operator action, so the next release is
a **minor** — `v0.7.0`, not a patch. The through-line is the audit's own verdict — LQ.AI is "a
codebase worth hardening, not rewriting."

**It is also the first release whose headline is community work.** Every audit-remediation PR here
was written by an external contributor, and four people landed their first commit on `main` in this
range. See [*Who built this release*](#who-built-this-release).

---

## Headline — security-audit remediation (#288)

The audit surfaced **22 findings** (3 High, 10 Medium, 8 Low, 1 Info). None is exploitable by a raw
external attacker in the shipped default (every service binds to `127.0.0.1`); all sit on the
platform's core promise — cross-tenant data sovereignty for privileged legal material. This release
closes **all three Highs plus three Mediums**:

**High — all three closed**

- **GW-01 — the gateway inference/embeddings endpoints now require a key.** `POST /v1/chat/completions`
  and `/v1/embeddings` previously accepted requests with **no gateway key** while every sibling
  router enforced one, contradicting the gateway's own OpenAPI contract; on the flat compose network
  any co-located container could drive the crown-jewel egress path directly. Both now require the
  key. *(#396)*
- **API-01 — the cross-tenant IDOR at chat creation is closed.** An authenticated user could bind a
  new chat to **another user's project UUID**, pulling the victim's knowledge-base documents into
  the response *and* forwarding them to the external LLM with anonymization disabled. Chat creation
  now validates project ownership. This was the finding most directly on the data-sovereignty
  guarantee. *(#397)*
- **D-01 — stored XSS in the skill source viewer is closed.** `SkillSourceView.svelte` rendered skill
  markdown through `marked` with **no DOMPurify** — the one LQ-authored `{@html}` sink that skipped
  the sanitization applied elsewhere by convention (a malicious skill body → JS in a viewer's
  authenticated session → account takeover). Skill markdown is now sanitized before render. *(#398)*

**Medium — three closed**

- **GW-04 — LLM provider `base_url` egress is guarded.** The provider `base_url` had no
  SSRF/allowlist/HTTPS guard, asymmetric with the hardened tool-egress path; it is now validated at
  adapter-build time, and a violation is **fatal at gateway startup** rather than skipped (skipping
  would let the router fall through and send prompts somewhere the operator did not choose).
  `https` is always allowed; plaintext `http` only to a loopback/private host. *(#400)* Related:
  provider egress refusals are now a typed `ProviderEgressRefused` under the `LQAIError` hierarchy.
  *(#488)*
- **AG-01 — autonomous `retrieve_chunks` is scoped to the session owner.** Model-supplied
  `kb_id`/`file_id` in the autonomous layer previously carried no ownership scoping; retrieval is now
  bound to the session owner. *(#401)*
- **API-04 — the api refuses to boot on the published dev JWT secret.** A deployment left on the
  documented dev `JWT_SECRET` will now refuse to start in production (set the secret, or
  `LQ_AI_DEV_MODE`). *(#399)* — *This addresses audit finding API-04; the commit was not tagged with
  the finding ID, so it will not appear in a `Refs #288` grep.*

> **Operator note — three behavior changes (the reason this is a minor, per ADR 0025 decision 3).**
> All three carry the `breaking-change` label, so the `version-consistency` gate sees them.
> **#396** — a deployment calling the gateway directly without a key gets `401` on every inference
> call after upgrading (in the default topology the api already sends the key, so no action is
> expected). **#399** — an install still on the dev `JWT_SECRET` will refuse to start until the
> operator sets a real secret (or `LQ_AI_DEV_MODE`). **#400** — the **gateway refuses to start** if
> any configured provider `base_url` is plaintext `http` to a non-local host, or uses a scheme other
> than `http`/`https`. A local Ollama on `http://localhost` or a private IP is unaffected; a remote
> `http://` endpoint must move to `https`. Read before upgrading.

---

## Supply-chain & build hardening

- **uv-managed lockfiles for `gateway/` and `api/` (ADR 0023).** Both services build from
  `uv`-managed lockfiles — reproducible, fully-pinned resolution across the SBOM. *(#312, #441)*
- **Stack-boot CI smoke test.** CI builds and boots the whole compose stack on every run, catching a
  break in the 7-service bring-up before merge. *(#297)*
- **GitHub Actions pinned to commit SHAs.** Release-relevant actions no longer float on mutable tags
  (partial remediation of audit E-02; the signing/`packages:write` split remains open). *(#296)*
- **The release pipeline is staged and gated (ADR 0025).** `release.yml` splits its single unordered
  matrix into `build-backend` → `build-frontend`, so a backend failure structurally blocks the
  frontend from publishing under the same tag, and adds a `version-consistency` job that fails the
  tag push if `api` and `gateway` disagree with each other or with the tag, or if a patch tag is cut
  while a `breaking-change`-labelled PR merged since the previous release. ADR 0025 is **Accepted**
  (committee, 2026-08-09), and this is the first release cut under it. *(#487)*
- **Windows entrypoint line-endings** forced to LF, fixing a class of Windows-checkout boot
  failures. *(#259)*
- **Gateway test fixture pins `OLLAMA_BASE_URL`**, closing an ambient-environment leak that let a
  developer's shell change test behaviour. *(#443)*
- **Dependency bumps**, including **starlette `0.48.0 → 1.3.1`** (a major bump) and
  **cryptography `44.0.3 → 49.0.0`** in the gateway, FastAPI, uvicorn, aioboto3, sqlalchemy, ruff,
  and several GitHub Actions to current majors — several swept into single lock/CI writes. *(#468,
  #454, #449, #469, #472, #452, #476, #477, #467, #463, #180, #250, #124, #123, #248, #249)*

---

## Correctness & governance

- **Knowledge bases listed by the `project_knowledge_bases` junction, with a backfill.** Replaces the
  legacy single-column association; ships migration **0066** to backfill existing rows. No operator
  step (the backfill runs on migrate). *(#442)*
- **Committee governance framework (ADR 0022).** New `GOVERNANCE.md` plus ADR 0022 formalize the LQAI
  committee's decision process and meeting-record convention. *(#311)*; ADR 0022 is now marked
  **Accepted** (committee, 2026-07-26). *(#507)*
- **Expansion-contribution routing (ADR 0024 + direction paper).** A direction paper and ADR 0024
  give jurisdiction- and practice-area-expansion contributions a documented route, so proposals stop
  stalling for want of a place to go. Both Accepted (2026-08-09). *(#313)*
- **Native frontend dev flow (Vite HMR)** documented for `web/`. *(#281)*
- **caddy-tailscale deployment recipe** cleaned up. *(#261)*
- **DE-385 filed** — desktop `package-lock.json` version stale at 0.5.2, tracked for follow-up.
  *(#276)*
- **DE-386 filed** — tool providers have no client-credentials auth path; the five missing pieces are
  enumerated in PRD §9 rather than left implicit. *(#508)*

---

## Fixes

- **compose:** forward `LQ_AI_GATEWAY_MASTER_KEY` into the gateway service (Donna #3 follow-up) —
  fixes encrypted-key startup in the default stack. *(#278)*
- **desktop:** pull images before `up` so the launcher picks up published releases (shipped as the
  `desktop-v0.6.2` tag). *(#277)*

---

## Who built this release

**The entire security-audit remediation in this release was written by a contributor from outside
the maintainer team.** All three High findings (#396, #397, #398), all three Mediums (#399, #400,
#401), the two follow-ups that tidied up after them (#443, #488), and the SHA-pinning of GitHub
Actions (#296) are [**@SaifAlYounan**](https://github.com/SaifAlYounan)'s work — nine PRs, the
first of which landed on 2026-07-08. The headline of this release is not maintainer work.

**Four people landed their first commit on `main` in this release:**

| Contributor | What they landed |
|---|---|
| [@SaifAlYounan](https://github.com/SaifAlYounan) | the whole #288 remediation (9 PRs), plus SHA-pinned Actions |
| [@mkorpela](https://github.com/mkorpela) | LF line-endings on the Windows entrypoint, fixing a class of Windows-checkout boot failures *(#259)* |
| [@sgbooth](https://github.com/sgbooth) | the native Vite-HMR frontend dev flow, documented *(#281)* |
| [@sergiomaldo](https://github.com/sergiomaldo) | knowledge bases listed by the junction table rather than the legacy column *(#442)* |

**And one came back:** [@ThurgyThurg](https://github.com/ThurgyThurg), whose caddy-tailscale recipe
first landed in #134, returned to clean it up in #261.

The project's contributor base is genuinely widening. Seven distinct human identities had landed
commits on `main` by `v0.6.1`; twelve had by the time this release was cut. Concentration is still
real — one person authored roughly 95% of all human-authored commits in the project's history, and
ADR 0025's *Cadence* section is honest about what that means for release cadence and for the
desktop signing identity. But the security-critical work in this release came from the community,
and that is a different shape of project than the one the last release shipped from.

Worth recording alongside the credit: none of these landed on the first pass. The security PRs went
through review rounds that sent work back — a missing loop-path test on #401, a corrected diagnosis
of which guard was actually failing on #443 — and they are better for it. Review capacity is the
other half of a widening contributor base, and it is the half that does not scale by itself.

---

## Schema

Migration head **0066** (`0066_backfill_project_knowledge_bases`, #442) — reversible, applied on
`make migrate`, no operator action. This is the only schema change in the release.

---

## Version

Per **ADR 0025 decision 1**, `api`, `gateway`, `web`, and `proxy` share one release version — the
image tag. This PR bumps the two project-owned strings, **`api` and `gateway` `__version__` →
`0.7.0`**. The drift ADR 0025 records (`api` `0.6.1`, `gateway` lagging at `0.5.1`) was already
closed by #487, which moved `gateway` to `0.6.1`; both now move together, as the policy requires.
`web/package.json` stays `0.9.2` (OpenWebUI fork, per ADR 0001) and `proxy` carries no version
string; neither is retitled.

The api version is also stamped into the committed OpenAPI export
(`docs/api/backend-openapi.generated.yaml` → `info.version`), so the bump requires a `make openapi`
regeneration; the `test_generated_openapi_export_is_current` drift-guard fails otherwise. That step
is not yet in the release checklist — see *Follow-ups* below.

Per **ADR 0025 decision 3**, this is a **minor**: #396, #399, and #400 each require operator action,
so a patch tag would fail the `version-consistency` breaking-change gate. The number is not chosen
from a milestone — it is what has accumulated on `main`.

---

## Honest scope — what did *not* ship

Six of the 22 audit findings are closed here (all 3 High; GW-04, AG-01, API-04). The remainder of
#288 stays open and should each land with its finding ID in the commit subject (`GW-02, Refs #288`)
so the next narrative assembles the same way:

- **Medium:** GW-02 (client-settable anonymization/tier flags), GW-03 (embeddings egress enforces
  neither anonymization nor tier floor), API-02 (`viewer` role defined but unenforced), API-03 (no
  login/MFA rate-limiting), AG-02 (MCP trusts server-attested safety metadata), D-02 (Electron
  navigation allowlist / `sandbox:false`), E-05 (EOL Electron 31).
- **Low:** GW-05 (Presidio recognizers off by default), AG-03 (autonomous `max_allowed_tier=None`),
  AG-04 (`emit_artifact` re-embeds model content), D-03 (Teams `id_token` unverified), E-03 (root
  containers), E-02 remainder (signing + `packages:write` still in the release job), E-04
  (`*_BIND_ADDR` exposure), E-06 (floating community submodule ref).
- **Info:** GW-06 (master key + gateway secret via plaintext env).

---

## Release readiness

**Unblocked.** ADR 0025 merged as #487, and this branch is rebased onto it — the
`version-consistency` gate and the staged pipeline are live and will run on the tag push.

**Gates (maintainer, before tagging):** api `ruff`/`mypy` + pytest; gateway `ruff`/`mypy --strict` +
pytest; web `check:lq-ai` + vitest; the stack-boot smoke; and the `version-consistency` job
(api == gateway == tag; not a patch while a `breaking-change`-labelled PR merged since `v0.6.1`).

**Pre-flight, already satisfied:**
- `api` and `gateway` `__version__` are both `0.7.0`; the OpenAPI export is regenerated to match.
- #396, #399, and #400 carry the `breaking-change` label, so the gate sees them and `v0.7.0`
  (a minor over `v0.6.1`) satisfies it.

**Steps (maintainer):**
1. Merge this PR to `legalquants/main`.
2. Tag `v0.7.0` (annotated) on the merge commit and push it to `legalquants` — the Release workflow
   builds + signs the four images and attaches the SBOM. Do not use `[allow-version-drift]` or
   `[allow-breaking-in-patch]`; neither is needed.
3. Verify the published image set before going further (ADR 0025 decision 4 gates backend before
   frontend, but the intra-stage guarantee is partial — confirm all four images carry the tag).
4. Cut `desktop-v0.7.0` after the image set is live and verified, recording the image tag it ships
   against (ADR 0025 decision 2).

## Follow-ups this release surfaced

- **The release checklist does not mention regenerating the OpenAPI export.** `docs/BUILD-AND-RELEASE.md`
  §"Versioning policy" tells the maintainer to bump `api` and `gateway` `__version__`, but the api
  version is also stamped into `docs/api/backend-openapi.generated.yaml`, so a bump alone leaves CI
  red on `test_generated_openapi_export_is_current`. It cost a red CI run on this release. Worth a
  one-line addition to the checklist.
- **The 2026-08-09 committee minutes are not published yet.** ADR 0025's Accepted status (and ADR
  0024's, from the same call) rests on the maintainer's record of the call until they are. The
  minutes backlog is tracked separately — only two of the recent calls have public notes.
- **ADR 0025's contributor-concentration figures do not reconcile.** Its *Cadence* section states
  655 non-merge commits, ~642 (~98%) by one person across two identities, and "five distinct human
  identities"; recounting at `b060ae2f` — the SHA the ADR says it verified against — gives 704
  commits, 665 (~94%) and **eleven** identities. The argument the section makes is unaffected: 94%
  is still overwhelming concentration, and the desktop signing bottleneck is unchanged. But the
  numbers should be corrected so this document and that one do not disagree on a countable fact.

---

*Drafted from the `v0.6.1..legalquants/main` inventory on 2026-08-06 (35 commits) and refreshed
2026-08-10 against `460997b5` (40 commits), which added #313, #443, #487, #507, and #508. Security
context from #288 (Claude Fable 5). Remediation coverage verified against finding-ID commit subjects
on `legalquants/main`: GW-01 (#396), API-01 (#397), D-01 (#398), GW-04 (#400), AG-01 (#401) carry
`Refs #288`; API-04 is #399 (untagged). Version numbering per ADR 0025.*

## Next

- [Upgrade](/lq-ai/operate/upgrade/) — the runbook for taking this release.
- [Release versioning](/lq-ai/reference/versioning/) — what a patch and a minor each commit to.
- [All release notes](/lq-ai/changelog/) — every tag, and which have notes.
