Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,55 @@ Based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

---

## [2.5.2] — 2026-06-07

### Fixed

- **Admin console — space creation no longer fails with "Empty response".**
- **Root cause (infrastructure, not application code).** The admin console
proxies every tool call through `POST /api/tool`, which is routed
**through** the Coraza WAF (OWASP CRS). When creating a space with
Markdown rules, that content travels in the request body and trips CRS
XSS/SQLi heuristics — notably the `<` characters in the default rules
(`activeContext.md < 8 KB`, `< 15 KB`) and the word `delete`
(`delete sections superseded…`). The cumulative anomaly score crossed
the threshold (5), Coraza returned a **403 with an empty body**, and the
web UI surfaced it as `space_create` failing with *"Empty response"*.
This is why `space_list` worked but `space_create` (with rules) did not.
- **Fix.** `waf/Caddyfile` now excludes the **request body** of
`/api/tool` from CRS inspection
(`ctl:requestBodyAccess=Off` scoped to `REQUEST_URI @beginsWith
/api/tool`). The endpoint is already gated by an HttpOnly cookie **and**
the `write` permission and only proxies structured `{tool, arguments}`
calls, so this mirrors the trust rationale already applied to `/mcp*`.
Rate-limiting, body-size limits, and URI/header inspection (path
traversal, scanners) remain active.
- ⚠️ **Requires restarting/recreating the WAF container** for the change
to take effect (`docker compose restart waf`). The Caddyfile is
bind-mounted (`./waf/Caddyfile:/etc/caddy/Caddyfile:ro`), so **no image
rebuild is needed** — Caddy re-reads it on restart.

### Added

- **Admin console — Owner field on "Create Space" is now a suggestion list.**
- The free-text `Owner` input is paired with a `<datalist>` populated from
**active token holders** (names deduplicated and sorted). Free-text entry
is still allowed for owners that have no token.
- Stored value is the **token name** (consistent with the Owner column
rendering). `owner` remains optional and purely informational
server-side — it has never been able to cause a creation failure.

### Changed

- `waf/Caddyfile` — new Coraza rule `id:900500` scoping
`requestBodyAccess=Off` to `/api/tool`.
- `src/live_mem/static/js/admin-app.js` — new `ownerOptionsHtml()` helper;
`showCreateSpace()` renders the Owner `<datalist>`.
- `DESIGN/live-mem/DEPLOIEMENT_PRODUCTION.md` — §5 "WAF Routes" documents the
`/api/tool` body-inspection exception.

---

## [2.5.1] — 2026-06-05

### Added
Expand Down
17 changes: 16 additions & 1 deletion DESIGN/live-mem/DEPLOIEMENT_PRODUCTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,27 @@ networks:
| Route | WAF Coraza | Timeout | Usage |
|---|---|---|---|
| `/mcp*` | ❌ (bypass) | Unlimited | Single MCP Streamable HTTP endpoint (POST/GET/DELETE) |
| `/api/*` | ✅ | 5min | REST API (web interface) |
| `/api/tool` | ⚠️ (headers/URI only, body not inspected) | 5min | Admin console tool proxy |
| `/api/*` (other) | ✅ | 5min | REST API (web interface) |
| `/live`, `/static/*` | ✅ | Standard | Web interface |
| Everything else | ✅ | Standard | Health (`/health`), etc. |

> **Note v0.5.0**: The former `/sse*` and `/messages/*` routes (SSE transport) have been removed and unified into `/mcp*` (Streamable HTTP).

> **Note v2.5.2 — `/api/tool` request body excluded from CRS inspection.**
> The admin console proxies tool calls through `POST /api/tool`, whose body
> carries free-form Markdown (e.g. Memory Bank rules: `<` comparisons,
> the word `delete`, `**`, backticks). The OWASP CRS flagged these as
> XSS/SQLi, pushed the anomaly score past the threshold, and Coraza
> returned an empty-body 403 — surfacing in the UI as `space_create`
> failing with *"Empty response"*. The endpoint is already gated by an
> HttpOnly cookie **and** the `write` permission, and only proxies
> structured `{tool, arguments}` calls, so its **request body** is
> excluded from CRS inspection (`ctl:requestBodyAccess=Off` scoped to
> `REQUEST_URI @beginsWith /api/tool`). Rate-limiting, body-size limits,
> and URI/header inspection (path traversal, scanners) stay active — same
> trust rationale already applied to `/mcp*`.

---

## 6. Backup & Restore
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.5.1
2.5.2
2 changes: 1 addition & 1 deletion src/live_mem/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
"""Live Memory — Memory Bank as a Service pour agents IA collaboratifs."""

__version__ = "2.5.1"
__version__ = "2.5.2"
10 changes: 9 additions & 1 deletion src/live_mem/static/js/admin-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,11 +287,19 @@ async function renderSpaces(){
}</tbody></table>`;
}

function ownerOptionsHtml(){
// Suggestions = détenteurs de tokens actifs, noms dédupliqués et triés.
const names=[...new Set((cache.tokens||[])
.filter(t=>!t.revoked && t.name)
.map(t=>t.name))].sort((a,b)=>a.localeCompare(b));
return names.map(n=>`<option value="${esc(n)}">`).join('');
}

function showCreateSpace(){
showModal('➕ Create Space',`
<div class="form-group"><label class="form-label">Space ID <span class="req">*</span></label><input class="form-input" data-1p-ignore id="m_space_id" placeholder="my-project"></div>
<div class="form-group"><label class="form-label">Description <span class="req">*</span></label><input class="form-input" data-1p-ignore id="m_desc"></div>
<div class="form-group"><label class="form-label">Owner</label><input class="form-input" data-1p-ignore id="m_owner"></div>
<div class="form-group"><label class="form-label">Owner</label><input class="form-input" data-1p-ignore id="m_owner" list="m_owner_list" placeholder="Choisir un détenteur de token ou saisir un nom"><datalist id="m_owner_list">${ownerOptionsHtml()}</datalist></div>
<div class="form-group"><label class="form-label">Rules (Markdown)</label><textarea class="form-input" id="m_rules" rows="6" placeholder="Leave empty for default rules"></textarea></div>
`,'Create Space',async()=>{
const sid=gv('m_space_id'),desc=gv('m_desc');if(!sid||!desc)return false;
Expand Down
4 changes: 2 additions & 2 deletions tests/test_security_hardening_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1621,5 +1621,5 @@ def test_sanity_imports_dont_break_existing_code():
from live_mem.tools import bank, admin, system, graph, backup as tb
from live_mem.auth import context, middleware

# Version bumpée à 2.5.0
assert live_mem.__version__ == "2.5.0"
# Version bumpée à 2.5.2
assert live_mem.__version__ == "2.5.2"
18 changes: 18 additions & 0 deletions waf/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,24 @@

SecAction "id:900220,phase:1,pass,nolog,\
setvar:'tx.allowed_request_content_type=|application/json|text/plain|multipart/form-data|application/x-www-form-urlencoded|application/octet-stream|'"

# ---------------------------------------------------------
# Exclusion ciblée : console admin authentifiée (/api/tool)
# ---------------------------------------------------------
# Le corps de POST /api/tool transporte du markdown légitime
# (rules de Memory Bank : caractères '<', mot 'delete', '**',
# backticks…) que l'OWASP CRS prend pour des attaques XSS/SQLi.
# Le score d'anomalie dépasse le seuil → 403 à corps vide, que
# l'UI affiche comme "Empty response" (échec de space_create).
#
# Cet endpoint est déjà protégé : cookie HttpOnly + permission
# write obligatoire, et il ne proxifie que des appels d'outils
# structurés {tool, arguments}. On désactive donc l'inspection
# du CORPS pour ce seul chemin (même logique assumée que /mcp*).
# Restent actifs : rate-limiting, limites de taille, inspection
# de l'URI et des headers (path traversal, scanners, etc.).
SecRule REQUEST_URI "@beginsWith /api/tool" \
"id:900500,phase:1,pass,nolog,ctl:requestBodyAccess=Off"
`
}

Expand Down
Loading