Skip to content

Release v0.51.739 — session-backed HTML/PDF media previews (#5157, @santastabber) - #5191

Merged
nesquena-hermes merged 2 commits into
masterfrom
fix/5157-session-media-preview
Jun 29, 2026
Merged

nesquena-hermes merged 2 commits into
masterfrom
fix/5157-session-media-preview

Conversation

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Release v0.51.739 — session-backed HTML/PDF media previews (#5157, @santastabber)

Ships #5157 by @santastabber. Maintainer-approved after live browser testing (Nathan signed off on the inline preview rendering).

What it fixes

Inline PDF/HTML previews fetch the artifact through api/media. Session-backed artifacts (files outside the workspace roots, authorized by the per-session media token) were fetched without the session_id, so the request couldn't present the grant → the preview silently failed to load. The PDF and HTML inline loaders now thread the active session_id into the api/media fetch URL (and the download / open-full-page fallbacks). HTML still renders only inside the sandbox="allow-scripts" iframe, SVG stays a download, and the secret/state hard-deny runs before any grant. +159/-25, 4 files (api/routes.py, static/ui.js, 2 test files).

Gate

  • Gate-certified GREEN by the gate-certifier at head 055e57fac173 (this PR's head): Codex SAFE TO SHIP on a clean rebase (round-1 kick-back was all stale-base artifacts from other shipped PRs, not this diff), Opus no-blockers, full suite 11073 passed, live HTTP boundary test 6/6 (assistant-path + matching session → 200 + Content-Security-Policy: sandbox allow-scripts; no-token / wrong-session / /etc/passwd / user-authored-token / SVG-inline all 403/attachment), in-process grant 5/5.
  • Re-validated this session: rebased onto current master (clean, diff still exactly the 4 PR files), 108 media tests pass in the rebased checkout.
  • Live browser test: rendered the inline HTML preview card — "HTML Preview (sandboxed)" header + "Open full page ↗" + sandboxed iframe (400px, full-width) rendering the artifact. Maintainer visual sign-off obtained.

Closes #5157.

@nesquena-hermes
nesquena-hermes merged commit 537ff28 into master Jun 29, 2026
11 checks passed
@nesquena-hermes
nesquena-hermes deleted the fix/5157-session-media-preview branch June 29, 2026 05:22
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes silent inline preview failures for session-backed HTML and PDF artifacts by threading the active session_id into the api/media fetch calls in loadPdfInline and loadHtmlInline, and by widening the server-side session-token MIME allowlist to cover HTML, PDF, audio, and video types alongside images.

  • api/routes.py: _session_media_token_allows_image_path is generalized into _session_media_token_allows_path (image-only wrapper kept for back-compat); _SESSION_MEDIA_TOKEN_TYPES adds text/html, PDF, audio, and video; _AUDIO_VIDEO_PDF_TYPES is extracted as a shared constant.
  • static/ui.js: loadPdfInline and loadHtmlInline now pass session_id to the fetch() call, but all browser-navigated fallback URLs (dlUrl, openUrl) are still built from publicMediaUrl (no session_id), so download and "Open full page" links 403 for session-backed files outside workspace roots.

Confidence Score: 3/5

The inline preview fetch works correctly for session-backed artifacts, but every browser-navigated fallback link (PDF download, HTML Open full page, error/too-large fallbacks) still uses the URL without session_id and will 403 for the same files the PR is designed to unlock.

The core fetch fix and server-side MIME expansion are sound, but the fallback href/download URLs across both loadPdfInline and loadHtmlInline were left on publicMediaUrl. The PR description explicitly claims these fallbacks were fixed, so this is an unintended gap. Every user-facing link that is not the initial preview fetch remains broken for session-backed artifacts outside workspace roots.

static/ui.js — the dlUrl and openUrl variables in both loadPdfInline and loadHtmlInline need to be derived from mediaUrl (which carries session_id) rather than publicMediaUrl.

Important Files Changed

Filename Overview
api/routes.py Generalizes _session_media_token_allows_image_path into _session_media_token_allows_path accepting any MIME set; widens the session-token allowlist to include HTML, PDF, audio, and video types. Server-side logic looks correct — hard-deny runs before the grant check, HTML is CSP-sandboxed, SVG stays download-only.
static/ui.js Threads session_id into fetch() calls for PDF and HTML inline loaders — the core fix is correct. However, all browser-navigated href/download URLs (dlUrl, openUrl) are built from publicMediaUrl (no session_id), breaking download and "Open full page" links for session-backed artifacts outside workspace roots.
tests/test_media_inline.py Adds unit tests for the new _session_media_token_allows_path API and an integration test verifying 200 + CSP sandbox for a session-authorized HTML artifact.
tests/test_pdf_html_preview.py Adds snapshot-style tests for session_id threading; notably validates the current broken fallback behavior (asserts publicMediaUrl is used), so these would need updating alongside the fix.
CHANGELOG.md Release changelog entry; no issues.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Browser
    participant UI as ui.js
    participant Server as api/routes.py

    Browser->>UI: render session-backed artifact
    UI->>UI: build mediaUrl with session_id
    UI->>Server: fetch(mediaUrl) session_id present
    Server->>Server: _session_media_token_allows_path checks MEDIA token
    Server-->>UI: 200 file bytes
    UI->>Browser: render inline PDF or sandboxed iframe

    Browser->>UI: click Open full page or download
    UI->>UI: build openUrl/dlUrl from publicMediaUrl (no session_id)
    UI->>Server: GET without session_id
    Server->>Server: "session_media_allowed=False within_allowed=False"
    Server-->>Browser: 403
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Browser
    participant UI as ui.js
    participant Server as api/routes.py

    Browser->>UI: render session-backed artifact
    UI->>UI: build mediaUrl with session_id
    UI->>Server: fetch(mediaUrl) session_id present
    Server->>Server: _session_media_token_allows_path checks MEDIA token
    Server-->>UI: 200 file bytes
    UI->>Browser: render inline PDF or sandboxed iframe

    Browser->>UI: click Open full page or download
    UI->>UI: build openUrl/dlUrl from publicMediaUrl (no session_id)
    UI->>Server: GET without session_id
    Server->>Server: "session_media_allowed=False within_allowed=False"
    Server-->>Browser: 403
Loading

Reviews (1): Last reviewed commit: "docs(changelog): authorize session-backe..." | Re-trigger Greptile

Comment thread static/ui.js
Comment on lines +14917 to 14919
const openUrl=publicMediaUrl+'&inline=1';
const safeHtml=html.replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
el.outerHTML=`<div class="html-preview-wrap"><div class="html-preview-header"><span>${t('html_sandbox_label')}</span><a href="${openUrl}" target="_blank" rel="noopener" class="html-open-link">${t('html_open_full')} ↗</a></div><iframe srcdoc="${safeHtml}" sandbox="allow-scripts" class="html-preview-iframe" loading="lazy"></iframe></div>`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 "Open full page ↗" link omits session_id for session-backed artifacts

openUrl is constructed from publicMediaUrl (no session_id), so when a user clicks the "Open full page ↗" link for a session-backed HTML artifact (the exact use case this PR fixes), the server resolves session_media_allowed = False and the within_allowed check also fails → 403. The PR description explicitly states this fallback was fixed, but it still uses publicMediaUrl instead of mediaUrl. Same issue applies to the "too large" branch on line 14913 and the catch branch's dlUrl on line 14922.

Comment thread static/ui.js
Comment on lines +14819 to +14820
const dlUrl=publicMediaUrl+'&download=1';
el.outerHTML=`<div class="pdf-preview-fallback"><a class="msg-media-link" href="${dlUrl}" download="${esc(fname)}">📎 ${esc(fname)}</a><br><span style="color:var(--muted);font-size:12px">${t('pdf_too_large')}</span></div>`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 PDF download links omit session_id for session-backed artifacts

Every dlUrl in loadPdfInline is built from publicMediaUrl (no session_id). For a session-backed PDF outside the workspace root, fetch(mediaUrl) succeeds and the preview renders, but clicking the download link (or any of the four fallback branches — too-large, success header, catch, and pdfjs-timeout) hits the server without a session_id, so session_media_allowed is False and the request 403s. dlUrl should be derived from mediaUrl (not publicMediaUrl) in all four occurrences.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants