fix(core): re-host non-http(s) CORS iframe URLs instead of failing upload#2294
Open
Shivanshu-07 wants to merge 3 commits into
Open
fix(core): re-host non-http(s) CORS iframe URLs instead of failing upload#2294Shivanshu-07 wants to merge 3 commits into
Shivanshu-07 wants to merge 3 commits into
Conversation
…s (PER-8604, PER-8608) PER-8608 (CWE-829) — every third-party action across all 11 workflows was pinned to a mutable tag, allowing a hijacked/retagged action to inject code into CI (which handles signing keys and publish tokens). Pin every `uses:` to an immutable 40-char commit SHA (tag preserved in a trailing comment): checkout, setup-node, cache, upload-artifact, download-artifact, stale, github-script, action-regex-match, pull-request-comment-branch, gha-jobid-action, winterjung/split, trigger-workflow-and-wait, create-pull-request. PER-8604 (CWE-732) — workflows ran with the implicit write-all GITHUB_TOKEN. Add a top-level `permissions: contents: read` to every workflow and minimal job-level grants only where required: - executable.yml (build, notify): contents: write — upload release assets - stale.yml: issues: write, pull-requests: write - sdk-regression.yml: statuses: write + pull-requests: read Also re PER-8610: sdk-regression.yml is issue_comment-triggered but already gates execution on an author-permission check (write/admin collaborators only); the least-privilege block above further limits the token exposed to that flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ilure PR #2121 moved CORS iframe processing into core. For every CORS iframe, processCorsIframesInDomSnapshot() creates an upload resource keyed on the iframe's frameUrl. When an iframe src is a blob: URL (also data:, about:, etc.), that URL is sent to the API as a resource-url. The API rejects any resource URL whose scheme is not http/https (snapshot_service.rb: "Invalid URL, scheme must be http or https"), which fails the entire snapshot upload — a regression from pre-#2121 behavior. Skip CORS iframe entries whose frameUrl is not http(s): no bad resource is created and the iframe HTML is left untouched, so the snapshot uploads successfully instead of hard-failing. Percy cannot fetch/serve blob URLs anyway since they are ephemeral and browser-context specific. Fixes PER-9574 / #2285 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…g it Upgrade the previous skip-based fix to preserve the iframe. @percy/dom already handles blob URLs for stylesheets by extracting the content and re-hosting it under a synthetic /__serialized__/ http(s) URL (serialize-cssom.js); the CORS iframe path never applied that pattern. For a non-http(s) frameUrl, mint a synthetic http(s) URL from the captured iframe HTML (buildSyntheticFrameResourceUrl), resolved against the page origin with localhost rewritten to render.percy.local, then use it for both the upload resource and the rewritten iframe src. The blob URL never reaches the API, the snapshot uploads, and the iframe content is preserved and renderable. Frames without a percyElementId (where the src cannot be rewritten) are still skipped so no resource is orphaned. http(s) frames are unchanged. PER-9574 / #2285 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Percy CLI fails snapshot upload when a page includes an iframe whose
srcis ablob:URL:Regression introduced by #2121, which moved CORS iframe processing into
@percy/core. Reported in #2285 (works on 1.31.7, fails on 1.31.14+).Root cause
processCorsIframesInDomSnapshot()inpackages/core/src/utils.jscreates an upload resource for every CORS iframe, keyed on the iframe'sframeUrl:A
blob:URL is an in-memory handle that only exists in the originating browser context — there is no endpoint to fetch, it is scoped to the creating document, and it is revoked on unload. Percy's renderer (a separate browser) can never resolve it. When the iframesrcisblob:(alsodata:,about:), that URL is sent to the API as aresource-url; the API rejects any non-http(s) scheme (snapshot_service.rb→Invalid URL, scheme must be http or https), failing the entire snapshot upload.Fix
@percy/domalready handles blob URLs correctly for stylesheets (serialize-cssom.js): it extracts the content and re-hosts it under a synthetic/__serialized__/http(s) URL viaresourceFromText. The CORS iframe path never applied that pattern — this PR brings it to parity.For a non-http(s)
frameUrl,buildSyntheticFrameResourceUrl()mints a synthetic http(s) URL from the captured iframe HTML:options.url), withlocalhost/127.0.0.1rewritten torender.percy.local— matching how serialized DOM resources are hosted;/__serialized__/cors-iframe-<percyElementId>.html(percyElementId URL-encoded);urland the rewritten iframesrc, so the renderer serves the captured content exactly as it does an ordinary cross-origin iframe.The blob URL never reaches the API, the snapshot uploads successfully, and the iframe content is preserved and renderable. Frames without a
percyElementId(where thesrccannot be rewritten to the synthetic URL) are skipped so no resource is orphaned. http(s) frames are completely unchanged.Tests
packages/core/test/utils.test.js:isHttpOrHttpsUrl,rewriteLocalhostURL,buildSyntheticFrameResourceUrlhelpers (origin resolution, localhost rewrite, render.percy.local fallback, id encoding, null when no percyElementId)processCorsIframesInDomSnapshot: re-hosts a blob frameUrl (resource + src rewritten, noblob:left), re-hosts data/about against render.percy.local fallback, skips a non-http(s) frame with no percyElementId, mixed http+blob arrayAll 121
utilsspecs and allSnapshot › CORS iframe processingintegration specs pass; lint clean; unreachable defensivecatchmarked withistanbul ignore.Fixes PER-9574 / closes #2285
🤖 Generated with Claude Code