From 0a2dabc730659938710a2ef57e2736fa3dec9b90 Mon Sep 17 00:00:00 2001 From: nesquena-hermes <[email protected]> Date: Thu, 28 May 2026 18:20:25 +0000 Subject: [PATCH] stage-batch36: tighten #3064 MEDIA: token gate to non-user-role messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Opus advisor on stage-batch36: skip role='user' messages in _session_media_token_allows_image_path so a user-injected MEDIA: token cannot mint an allow-list entry for the user's own request. Preserves the original use case (assistant/tool emitted artifacts outside the active workspace) while making the implicit threat model explicit. Defense-in-depth — the single-user WebUI scope means same-origin user input already had the same effective access, but multi-user / shared WebUI deployments would benefit from the restriction. --- api/routes.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/routes.py b/api/routes.py index c5da3146..33fbf2c5 100644 --- a/api/routes.py +++ b/api/routes.py @@ -7619,6 +7619,13 @@ def _session_media_token_allows_image_path(sid: str, target: Path, image_mimes: for message in getattr(session, "messages", []) or []: if not isinstance(message, dict): continue + # Only honor MEDIA: tokens that the assistant/tool emitted. User-authored + # content cannot mint allow-list entries even if it contains a MEDIA: + # token — keeps the implicit threat model (assistant-emitted artifacts + # only) explicit. + role = str(message.get("role") or "").strip().lower() + if role == "user": + continue text = _message_content_text(message.get("content")) if "MEDIA:" not in text: continue