diff --git a/CHANGELOG.md b/CHANGELOG.md index 9feb92f1..8d7f61e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ ## [Unreleased] +## [v0.51.183] — 2026-05-31 — Release FC (stage-batchC — inline file:// media artifacts + /api/media state-file confinement) + +### Fixed +- Render assistant-emitted `file://` image artifact links inline through the authenticated `/api/media` route, so browser clients can view generated local images instead of seeing unusable server-local file paths. Only bare (line-start or whitespace-delimited) `file://` URLs are rewritten — `[label](file://...)` markdown anchors keep the normal link path (#3219). + +### Security +- `/api/media` now hard-denies WebUI state and secret/config files even when they fall under an allowed root (the WebUI state dir, `settings.json`, `state.db`, `auth.json`, `auth.lock`, `config.yaml`, `.env`, signing/PBKDF2 keys, the `sessions`/`memories`/`profiles` state subdirs). Previously the whole Hermes home was an allowed root, so an authenticated session viewing attacker-influenced agent output that emitted a `file://`/`MEDIA:` link to such a file could fetch it. Hardened the boundary at the route for every entry path (bare `file://`, markdown anchors, and `MEDIA:` tokens), closing #3234. + ## [v0.51.182] — 2026-05-31 — Release FB (stage-batchB2 — headless browser smoke gate + consolidated client-disconnect handling) ### Added diff --git a/static/ui.js b/static/ui.js index 4ca46c0f..94aeabe5 100644 --- a/static/ui.js +++ b/static/ui.js @@ -3014,6 +3014,16 @@ function renderMd(raw){ media_stash.push(raw_ref); return '\x00D'+(media_stash.length-1)+'\x00'; }); + // Some gateway/tool surfaces still emit file:// links for local artifacts + // instead of MEDIA: tokens. Browser clients cannot open the server's + // filesystem directly, so treat bare file:// URLs as media artifacts and + // route them through /api/media. This intentionally runs only for bare URLs + // (line-start or whitespace-delimited), not inside markdown links, so normal + // [label](file://...) anchors continue to use the link path below. + s=s.replace(/(^|\s)(file:\/\/[^\s<>"')\]]+)/g,(_,lead,raw_ref)=>{ + media_stash.push(raw_ref); + return lead+'\x00D'+(media_stash.length-1)+'\x00'; + }); // ── End MEDIA stash ───────────────────────────────────────────────────────── // Pre-pass: decode HTML entities first so markdown processing works correctly. // This prevents double-escaping when LLM outputs entities like < > & @@ -3449,7 +3459,7 @@ function renderMd(raw){ s=s.replace(/\x00E(\d+)\x00/g,(_,i)=>_pre_stash[+i]); // ── Restore MEDIA stash → inline images or download links ───────────────── s=s.replace(/\x00D(\d+)\x00/g,(_,i)=>{ - const ref=media_stash[+i]; + let ref=media_stash[+i]; // Keep this logic self-contained: some tests extract renderMd() alone and // execute it in node, without the top-level helper functions from ui.js. const mediaKindForName=(name='')=>{ @@ -3468,6 +3478,15 @@ function renderMd(raw){ : ``; return `