Fix session media image rendering - #3064
1 commit merged into
Conversation
SummaryRead the full diff (4 files, +113/-6), the new helpers in What I verifiedThe contract chain holds end-to-end:
Two concernsResolve-time check assumes message provenance
Test coverage gap: list-of-parts content
session = SimpleNamespace(messages=[{
"role": "assistant",
"content": [{"type": "text", "text": f"MEDIA:{image}"}],
}])would close it. Not a blocker. On the existing test shapeThe three new tests at VerdictApproved. The frontend/server pair is well-scoped, the security boundary holds, the regex in |
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.
6267716
|
Shipped in v0.51.154 / Release DZ (stage-batch36, commit 6267716). Thanks for the contribution! |
# Conflicts: # CHANGELOG.md
9-PR medium-risk cleanup: - nesquena#3037 routes.py: argv-style prefill hook + env-var override for notes drawer - nesquena#3046 models.py: compression parent not repaired as stale interrupted turn - nesquena#3048 session_discoverability.py: --repair-safe CLI with default dry-run - nesquena#3053 ui.js: streaming KaTeX guard for parser-owned equations - nesquena#3059 models.py: empty partial activity rows excluded from sidebar recency - nesquena#3060 profiles.py: API key writes to .env (chmod 600), not config.yaml - nesquena#3064 routes.py: MEDIA: image tokens allow exact session-referenced paths - nesquena#3069 models.py: cron sessions with project_id surface via Cron Jobs chip - nesquena#3077 gateway_chat.py: HTTP 401 maps to gateway_auth_error event
# Conflicts: # CHANGELOG.md
9-PR medium-risk cleanup: - nesquena#3037 routes.py: argv-style prefill hook + env-var override for notes drawer - nesquena#3046 models.py: compression parent not repaired as stale interrupted turn - nesquena#3048 session_discoverability.py: --repair-safe CLI with default dry-run - nesquena#3053 ui.js: streaming KaTeX guard for parser-owned equations - nesquena#3059 models.py: empty partial activity rows excluded from sidebar recency - nesquena#3060 profiles.py: API key writes to .env (chmod 600), not config.yaml - nesquena#3064 routes.py: MEDIA: image tokens allow exact session-referenced paths - nesquena#3069 models.py: cron sessions with project_id surface via Cron Jobs chip - nesquena#3077 gateway_chat.py: HTTP 401 maps to gateway_auth_error event
…le messages 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.
# Conflicts: # CHANGELOG.md
9-PR medium-risk cleanup: - nesquena#3037 routes.py: argv-style prefill hook + env-var override for notes drawer - nesquena#3046 models.py: compression parent not repaired as stale interrupted turn - nesquena#3048 session_discoverability.py: --repair-safe CLI with default dry-run - nesquena#3053 ui.js: streaming KaTeX guard for parser-owned equations - nesquena#3059 models.py: empty partial activity rows excluded from sidebar recency - nesquena#3060 profiles.py: API key writes to .env (chmod 600), not config.yaml - nesquena#3064 routes.py: MEDIA: image tokens allow exact session-referenced paths - nesquena#3069 models.py: cron sessions with project_id surface via Cron Jobs chip - nesquena#3077 gateway_chat.py: HTTP 401 maps to gateway_auth_error event
…le messages 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.
Thinking Path
MEDIA:tokens.MEDIA:/absolute/path.pngtokens are rewritten to/api/media?path=...so the browser can show thumbnails./api/mediacorrectly blocks arbitrary local paths, but it had no session-scoped allowance for exact image paths already present in the transcript.What Changed
session_idto localMEDIA:image URLs generated byrenderMd().MEDIA:image paths.session_idURL parameter.Why It Matters
Agent-generated images can live in project artifact folders outside the active WebUI workspace. Those images should render when the transcript itself explicitly references them, without expanding
/api/mediainto broad home-directory access.Closes #3063.
Verification
/Users/xuefusong/hermes-webui/.venv/bin/python -m pytest tests/test_media_inline.py -q46 passed/Users/xuefusong/hermes-webui/.venv/bin/python -m pytest tests/test_renderer_js_behaviour.py::TestRendererSanitization::test_media_token_image_uses_delegated_lightbox_not_inline_js -q1 passed8787:MEDIA:image request returned403 Forbidden206 Partial ContentwithContent-Type: image/png1080 x 1440Risks / Follow-ups
MEDIA:tokens; very large sessions could add a small amount of work only when a media request includes a path outside the normal allowed roots.Model Used
OpenAI GPT-5.3 Codex, via Codex desktop. AI assisted with diagnosis, implementation, testing, live runtime verification, issue creation, and PR preparation.