Status
PR #1926 implements npm-compatible previews through pkg.pr.new and is the current short-term path. This issue remains open for browser-ready assets on preview.cdn.rrweb.com. Those assets need the separate origin, validation, caching, and cleanup controls described below.
Motivation
Reviewers should be able to load the browser bundles produced by any pull request before it merges. These builds contain untrusted PR JavaScript, so they must use a separate origin. Publishing them on cdn.rrweb.com would let arbitrary PR code inherit the CSP trust that consumers already grant the production CDN.
The production CDN already uses immutable exact-version URLs plus short-lived redirect aliases. The preview CDN can reuse that model per PR.
Proposed design
For PR 123 at commit <full-sha>:
- Immutable assets:
https://preview.cdn.rrweb.com/pr/123/<full-sha>/<asset>
- Immutable manifest:
https://preview.cdn.rrweb.com/pr/123/<full-sha>/manifest.json
- Mutable alias:
https://preview.cdn.rrweb.com/pr/123/latest/<asset>, implemented as a redirect to the immutable URL
The manifest should record the repository, PR number, full commit SHA, workflow run ID, build timestamp, and each allowed file's path, byte size, and SHA-256 hash.
Use two workflows:
- An unprivileged
pull_request workflow checks out and builds PR code without secrets or write permissions, then uploads an Actions artifact containing only the allowlisted distributable files and manifest.
- A trusted
workflow_run workflow receives the completed build event and has pull-requests: write plus preview-only R2 credentials. It verifies the source repository/workflow, successful run, run ID, PR association, head SHA, archive paths, allowlist, file count, size limits, and hashes. It must treat every downloaded file as inert bytes: never source, import, evaluate, or execute the artifact or any script from the PR. After validation it uploads to R2 and updates one sticky PR comment with the commit, manifest, immutable URLs, and latest URLs.
Use a dedicated preview R2 bucket and credentials that cannot read, write, purge, or configure production CDN storage. Serve it only from preview.cdn.rrweb.com.
Caching:
- Immutable paths:
Cache-Control: public, max-age=31536000, immutable
latest redirects and any mutable metadata: short-lived caching, for example Cache-Control: public, max-age=60
- Update
latest only after every immutable object and the manifest have uploaded and passed hash verification.
Keep builds while a PR is open. A scheduled cleanup should delete the PR prefix 30 days after closure, and an R2 lifecycle rule should remove preview objects older than 90 days as a backstop. Remove or invalidate mutable aliases when their target is deleted.
Acceptance criteria
- Every PR, including fork PRs, can produce preview assets without exposing secrets to PR code.
- A trusted uploader validates artifacts but never executes them.
- Preview assets are unavailable through
cdn.rrweb.com and use preview-only R2 credentials.
- Immutable URLs remain content-stable and their bytes match the manifest SHA-256 hashes.
latest resolves to the newest successfully uploaded commit for that PR and is never updated after a failed or partial upload.
- One sticky PR comment is created or updated with working preview links and the current commit.
- Cache headers match the immutable and mutable behavior above.
- Closed PR assets are removed by the cleanup policy, with the R2 lifecycle rule as a backstop.
Status
PR #1926 implements npm-compatible previews through
pkg.pr.newand is the current short-term path. This issue remains open for browser-ready assets onpreview.cdn.rrweb.com. Those assets need the separate origin, validation, caching, and cleanup controls described below.Motivation
Reviewers should be able to load the browser bundles produced by any pull request before it merges. These builds contain untrusted PR JavaScript, so they must use a separate origin. Publishing them on
cdn.rrweb.comwould let arbitrary PR code inherit the CSP trust that consumers already grant the production CDN.The production CDN already uses immutable exact-version URLs plus short-lived redirect aliases. The preview CDN can reuse that model per PR.
Proposed design
For PR 123 at commit
<full-sha>:https://preview.cdn.rrweb.com/pr/123/<full-sha>/<asset>https://preview.cdn.rrweb.com/pr/123/<full-sha>/manifest.jsonhttps://preview.cdn.rrweb.com/pr/123/latest/<asset>, implemented as a redirect to the immutable URLThe manifest should record the repository, PR number, full commit SHA, workflow run ID, build timestamp, and each allowed file's path, byte size, and SHA-256 hash.
Use two workflows:
pull_requestworkflow checks out and builds PR code without secrets or write permissions, then uploads an Actions artifact containing only the allowlisted distributable files and manifest.workflow_runworkflow receives the completed build event and haspull-requests: writeplus preview-only R2 credentials. It verifies the source repository/workflow, successful run, run ID, PR association, head SHA, archive paths, allowlist, file count, size limits, and hashes. It must treat every downloaded file as inert bytes: never source, import, evaluate, or execute the artifact or any script from the PR. After validation it uploads to R2 and updates one sticky PR comment with the commit, manifest, immutable URLs, andlatestURLs.Use a dedicated preview R2 bucket and credentials that cannot read, write, purge, or configure production CDN storage. Serve it only from
preview.cdn.rrweb.com.Caching:
Cache-Control: public, max-age=31536000, immutablelatestredirects and any mutable metadata: short-lived caching, for exampleCache-Control: public, max-age=60latestonly after every immutable object and the manifest have uploaded and passed hash verification.Keep builds while a PR is open. A scheduled cleanup should delete the PR prefix 30 days after closure, and an R2 lifecycle rule should remove preview objects older than 90 days as a backstop. Remove or invalidate mutable aliases when their target is deleted.
Acceptance criteria
cdn.rrweb.comand use preview-only R2 credentials.latestresolves to the newest successfully uploaded commit for that PR and is never updated after a failed or partial upload.