fix: render file artifact links as media
This commit is contained in:
1 parent
0405fefb5e
commit
aae3b418f0
3 files changed
+37
-1
No files matched your search
@@ -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
|
||||
|
||||
+20
-1
@@ -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){
|
||||
: `<audio class="msg-media-player msg-media-audio" src="${safeSrc}" controls preload="metadata" title="${safeName}"></audio>`;
|
||||
return `<div class="msg-media-editor msg-media-editor--${kind}" data-media-kind="${kind}">${tag}<div class="msg-media-meta"><span class="msg-media-name">${safeName}</span></div></div>`;
|
||||
};
|
||||
if(/^file:\/\//i.test(ref)){
|
||||
try{
|
||||
const u=new URL(ref);
|
||||
ref=decodeURIComponent(u.pathname||ref.replace(/^file:\/\//i,''));
|
||||
}catch(_){
|
||||
try{ref=decodeURIComponent(ref.replace(/^file:\/\//i,''));}
|
||||
catch(__){ref=ref.replace(/^file:\/\//i,'');}
|
||||
}
|
||||
}
|
||||
// HTTP(S) URL
|
||||
if(/^https?:\/\//i.test(ref)){
|
||||
// Rewrite localhost/127.0.0.1 to the actual server base URL so remote
|
||||
|
||||
@@ -41,6 +41,15 @@ class TestMediaRenderMdStash(unittest.TestCase):
|
||||
self.assertIn("MEDIA:", UI_JS,
|
||||
"MEDIA: token regex must be present in renderMd()")
|
||||
|
||||
def test_bare_file_urls_are_stashed_as_media_artifacts(self):
|
||||
self.assertIn("file:// links for local artifacts", UI_JS)
|
||||
self.assertIn("file:\\/\\/[^\\s<>", UI_JS)
|
||||
|
||||
def test_file_urls_are_rewritten_through_media_endpoint(self):
|
||||
self.assertIn("new URL(ref)", UI_JS)
|
||||
self.assertIn("u.pathname", UI_JS)
|
||||
self.assertIn("api/media?path=", UI_JS)
|
||||
|
||||
def test_media_restore_produces_img_tag(self):
|
||||
self.assertIn("msg-media-img", UI_JS,
|
||||
"restore pass must produce <img class='msg-media-img'>")
|
||||
|
||||
Reference in new issue
Block a user