mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-03 02:27:57 +09:00
A Form on `site.example.com` can now send a Resource request to `api.example.com`. Cloudflare fetches use public routing, while requests back into the same app are stopped even when they arrive through a different hostname. ## Checklist - [x] Enable public `fetch()` routing in the self-hosted Cloudflare Worker and Pages templates. - [x] Narrow Cloudflare Form Resource hostname checks to site hostnames, including published aliases; keep the Node transport policy intact. - [x] Explain the routing and recursion boundary in code comments. - [x] Add focused regression tests and verify they fail before the fix and pass afterward. - [x] Reject Cloudflare Resource requests that re-enter the app through an alias, and verify both Worker and Pages paths. - [x] Run relevant package tests, typechecks, lint, and fixture builds. - [x] Review the authoritative `docs/` content. No user guidance currently describes this hostname policy, so no docs change is needed. The `global_fetch_strictly_public` flag affects all Cloudflare `fetch()` calls in the generated app. It routes same-zone requests through the public front door. The known site hostnames are blocked before fetch; a reserved request marker carrying the originating project ID stops direct reentry through any additional self-hosted alias while allowing other projects. The marker is set on every redirect hop. Verification: SDK suite (914 tests), CLI prebuild suite (143 tests), SDK and CLI typechecks, published types, lint, package boundaries, formatting, full fixture pipeline, and both Cloudflare fixture typechecks/builds passed. The new marker test failed before the fix and passed afterward. Local Pages returned 508 for a marked request and 200 for an ordinary request. Local Worker returned 508 for a marked request; an ordinary local request hit a fixture-only `assets/process` module error. The optional local fixture-size check lacked the unrelated React Router Netlify built client artifact; the prior PR CI fixture-size check passed. Staging test: moved from #6621 to `frm.staging`. The Builder deployed at https://frm.staging.webstudio.is/ and the Cloudflare template artifact build and validation passed. - [x] Publish the test site from staging and verify a Resource call to a sibling API hostname, plus recursive-call protection. Live staging verification: Published `ee-trpc-test-ivan.wstd.work` from Builder commit `35d22fc8a`. A temporary Resource to `api.wstd.work` produced that host's HTTP 401 response in published page data, confirming the request reached the sibling host. A marked request initially returned 200 because the SaaS publish Worker uses a separate entrypoint. The matching guard is in webstudio-is/webstudio-saas#673; deploying that branch made the marked request return 508 while an ordinary request returned 200. The temporary Resource and binding were removed and the clean build was republished with the SaaS fix. ## Resource response limit follow-up - [x] Enforce 1 MiB (1,048,576 decoded bytes) for all HTTP Resource responses, including page Resources, dependencies, Form actions, and cache hits; reject before cache writes. - [x] Cancel oversized responses and report `RESPONSE_TOO_LARGE` without retrying. Show: “Resource response exceeds 1 MiB. Request fewer fields or use pagination.” - [x] Verify exact boundaries, streamed overflow, compressed responses, cache behavior, and Form/loader error propagation with regression tests. - [x] Update Resource and Form documentation to explain the limit and recovery. - [x] Monitor CI after pushing the verified implementation. All checks passed for `f59b770d45112581514eaa8bc42f57b7f27d290b`, including all ten browser E2E jobs, visual regression, Linux/Windows fixtures, and staging builds/deployments. Verified locally: all 926 SDK tests, SDK typecheck and published types, lint, package boundaries, Form browser boundary, formatting, full fixture pipeline, and Cloudflare React Router fixture typecheck/build passed. All 12 new tests and the updated transport test failed against the previous implementation; disabling only the retry guard also reproduced a duplicate attempt. A local Cloudflare runtime test with gzip accepted exactly 1 MiB of decoded data and rejected one byte over. The full fixture sync pulled an unrelated saved-project text change; the original fixture source was restored and regenerated, leaving no fixture diff. The response limit is enforced before JSON parsing and before Resource cache writes, and checked again when loading cache hits. Oversized responses return `RESPONSE_TOO_LARGE` with `retryable: false`; the graph honors that flag. System and Email Resources retain their own limits. The implementation adds 59 net non-test code lines plus 5 documentation lines, below the 70–100 estimate. Browser E2E passed in CI. Agent evaluations were not run; they require separate permission and were not needed for this transport change. ## Simplification follow-up - [x] Reuse Content Engine's bounded byte reader for Resource bodies and Form uploads; remove duplicate accumulation and cancellation loops (estimate: about 40 fewer production lines). - [x] Preserve response limits, request limits, cancellation, error messages, and retry behavior; run existing SDK and Content Engine coverage plus package/build checks. - [x] Review documentation impact; behavior and approved copy should remain unchanged. - [x] Push the simplification and verify CI. All checks passed for `755ec4210a7858fe298ad4d3a725a0167fb5bb26`, including all ten browser E2E jobs and staging deployment checks. The simplification removes 37 net non-test lines, reducing the response-limit implementation from 59 added lines to 22. Content Engine now exposes its existing byte-stream module directly and accepts a caller-provided limit error. Resource bodies and Form uploads reuse that reader. Native Response construction also replaces redundant status/header copying. All existing tests remain. Verification: 926 SDK tests and 655 Content Engine tests passed, along with both packages' typechecks/builds, published types, lint, package boundaries, Form browser boundary, formatting, the full fixture pipeline, and a Cloudflare fixture build. Local Cloudflare runtime checks passed for gzip at/over the limit and empty 204/205/304 responses. The generated fixture vendor change is limited to the shared reader's optional error argument. Documentation and approved copy remain accurate and unchanged. ## Response handling cleanup - [x] Share bounded response reconstruction between protected fetches and the cache; remove stale encoding/length headers after decoding (estimate: 5–10 fewer non-test lines). - [x] Reproduce the protected-response header bug, then verify the fix, limits, empty responses, and existing SDK coverage. - [x] Review documentation and comments for accurate scope; preserve approved user-facing copy. - [x] Run relevant checks, regenerate fixtures, push, and monitor CI. The shared response reader fixes stale compression and length headers in protected fetches and removes four net production lines (slightly below the 5–10 estimate). All 927 SDK tests, types, published types, lint, package boundaries, and Form browser boundary passed. The new regression test failed on the old code and passed after the fix. Cloudflare runtime checks passed for decoded bodies and empty 204/205/304 responses. The existing Resource/Form documentation remains accurate; approved copy is unchanged. The reentry marker now carries the originating project ID. A different project can serve the request; the same project rejects direct reentry through any hostname. This is cooperative loop detection, not a sandbox for arbitrary code. - [x] Reproduce and fix overflow and redirect hangs when a response has an unread clone; preserve stream cancellation and error handling for all shared-reader callers. The clone regressions failed before the fixes and passed afterward. Cancellation still starts immediately and releases the reader lock, but no longer waits for an unread tee branch. Both overflow rejection and redirect following are covered. All 929 SDK tests and 655 Content Engine tests passed, along with both typechecks and lint. A local Cloudflare runtime also rejected a cloned oversized response promptly. Combined with response reconstruction reuse, this cleanup removes five net production lines. Fixture regeneration changes only the bundled cancellation logic. Final verification for `7761f67d6d1e0a853a6ef239f482ebaf9d70c1ae`: all CI checks passed, including all ten browser E2E shards, visual regression, package/Builder/CLI tests, size checks, Linux and Windows fixtures, and staging deployment workflows. Full fixture regeneration and the Cloudflare fixture build also passed locally. No agent evaluations were run; they require separate permission and were not needed for these fixes. ## Scope reentry protection to the project - [x] Replace the global marker with the originating project ID and compare it with generated project metadata before handling requests, including cached requests. - [x] Cover unrelated sites, same-project aliases, and redirects with regression tests; demonstrate failure before the fix. - [x] Review documentation and clarify that this is cooperative loop detection, not a sandbox for untrusted code. - [x] Regenerate fixtures, run relevant checks, push, and monitor CI. Estimate: 20–30 implementation lines across both repositories, plus generated metadata and tests. Implementation: reuse the project ID already exported by the generated home-page module; no new metadata file or package. The generated Cloudflare Resource adapter passes the same ID to the SDK factory, which now requires it. Guards compare that ID before rendering or cache lookup. SDK and generated-adapter regression tests cover another project returning 200 and an unknown same-project alias returning 508. Hosted Worker tests verify the guard runs before the cache and permits another project's cached response. The original guard and a blanket-guard mutation fail the regressions. A local Cloudflare runtime reproduced the expected 200/508 behavior. No user-facing copy or documentation changes are needed; code comments state the cooperative boundary. The fix adds 19 implementation lines across both repos, below the 20–30 estimate, plus tests/configuration and regenerated fixtures. Existing deployments require republishing with the updated sender and guard to use project-specific markers. Final project-specific reentry verification: 930 SDK tests, 143 CLI prebuild tests, and 41 hosted-template tests passed. SDK/CLI types, published SDK types, lint, package boundaries, full fixture regeneration, both Cloudflare fixture builds, and the Pages function bundle passed. The actual Pages function returned 200 for another project's marker and 508 for its own marker through an unknown alias in a local Cloudflare runtime. All CI checks passed for Builder `433f9a359d469ca3669e383c4516c3e805ea22a1`, including all ten E2E shards and staging workflows; companion SaaS `3e28e7e33813dd968f33caac35dbbba52a3847d3` checks and preview deployment also passed. Agent evaluations were not run because they require separate approval; no evaluation changes were needed.