Conversation
…l carries Every `download_url` is built off the resolved portal base URL, so a deployment that fronts its agents on a dedicated public hostname hands the portal page a cross-origin url. The preview `fetch()` was then refused by CSP `connect-src` before it left the browser, and the CSP cannot be the lever: the portal base URL is a runtime setting, not a build-time constant, so no static nginx or Vite header can allowlist it. Download was unaffected, being an anchor navigation that `connect-src` does not govern. `sameOriginPath` now reduces a url under `/api/` to a path even when its origin differs from the page's. The portal page's own origin proxies those routes to the same backend, and a share's authority is its `?sig=` token rather than the hostname the url was minted with, so the origin is decoration there. Any other cross-origin url is still returned unchanged: reducing one to a path would aim the fetch at a route this origin does not serve. The doc comment called a portal base URL pointing elsewhere "the deployment's problem". It is a supported topology, so the comment now says that with the issue number, and the vitest case that pinned the old behaviour is reversed rather than deleted, with the same note. Fixes Abilityai#2733
|
Thank you for this — you were first to #2733, and your diagnosis (the fetch leaves the page origin, Two fixes for the same bug were open, and the merge train validated both (no defect in either — yours is correct on every input If you'd like a follow-up: the |
Description
The portal Files rail previewed a shared file by fetching
download_url, whichportal_documentsbuilds off the resolved portal base URL. On a deployment that fronts agents on a dedicated public hostname, that url carries another origin than the portal page, so CSPconnect-srcrefused the fetch before it was sent. Download kept working, being an anchor navigation.sameOriginPathnow reduces a url under/api/to${pathname}${search}even when its origin differs from the page's, so the preview fetch lands on the portal page's own origin and stays insideconnect-src 'self'. That origin proxies/api/*to the same backend, and a share's authority is its?sig=token rather than the hostname the url was minted with, so the origin is decoration for these routes. Any other cross-origin url is still returned unchanged.This is the frontend-only fix the issue names as preferred. Widening the CSP is not available: the portal base URL is a runtime setting and cannot be baked into a static nginx or Vite header.
Related Issue
Fixes #2733
Journey Impact
Journey Impact: none: the client portal's Files rail preview is not one of the eleven promises in
tests/journeys/catalog.yaml. This restores an existing surface on a supported deployment topology and adds no user-facing capability.Type of Change
Acceptance criteria
connect-srcviolation. The fetch target is now/api/files/<id>?sig=…&preview=1, same-origin by construction.download_urlcarries.sharePreviewPath('https://files.example.com/api/files/f1?sig=t&download=1', 'https://app.example.com')returns/api/files/f1?sig=t&download=1&preview=1. The?sig=token andpreview=1survive, so the transfer still does not count as a download.download_urlunchanged. Nothing in the diff touchesportal_documentsor the anchor path;PortalRailFiles.vuestill assignsa.href = row.item.download_url, and a test pins that the string handed tosharePreviewPathis not mutated.portal.fetchUploadBlob, which this diff does not touch.download_urlwas already reduced to a path and still is, pinned by its own case.portalFiles.spec.jsplustests/unit/test_2733_portal_preview_same_origin.py, the sibling oftest_1400_csp_blob_preview.py, which asserts that preview does not depend on the portal base URL's origin: the rail previews through the rewrite rather than offdownload_url, the/api/exemption exists, Download keeps the absolute url, and neither CSP interpolates a deployment-specific origin.sameOriginPathcomment. It said a portal base URL pointing elsewhere was "the deployment's problem"; it now records the topology as supported, with the issue number.Testing
Negative control: replacing the new condition with the old
if (crossOrigin) return rawfails three vitest cases and the Python guard, so the tests hold the fix rather than the surroundings.One reversed assertion, called out rather than buried.
portalFiles.spec.jspinned the old behaviour twice, inleaves a genuinely cross-origin url alone rather than pretendingand at the end ofmarks full-blob previews without changing the download link or bearer token. The first now uses a non-/api/url, which is where that rule still holds, and the second expects the path. Both carry a comment naming #2733, since the issue states this topology is supported rather than a misconfiguration.What I could not verify. I have no deployment with a split portal hostname, so the browser half of AC 1 is not something I observed. The evidence here is the unit behaviour of the rewrite, the CSP guard, and the reading of
service.py::portal_documentsandsecurity-headers.confthe issue lays out.Checklist