Skip to content

Fix Dev Server to allow serving static assets from a shared folder outside of the extension directory#7386

Open
vividviolet wants to merge 1 commit into04-22-address_png_encoding_bug_path_changesfrom
04-23-fix_dev_server_to_allow_serving_static_assets_from_a_shared_folder_outside_of_the_extension_directory
Open

Fix Dev Server to allow serving static assets from a shared folder outside of the extension directory#7386
vividviolet wants to merge 1 commit into04-22-address_png_encoding_bug_path_changesfrom
04-23-fix_dev_server_to_allow_serving_static_assets_from_a_shared_folder_outside_of_the_extension_directory

Conversation

@vividviolet
Copy link
Copy Markdown
Member

@vividviolet vividviolet commented Apr 23, 2026

WHY are these changes introduced?

When two extension points within the same extension declare assets whose source basenames collide (e.g. both reference a tools.json, or both compile to main.js), the dev server previously served whichever file happened to win the filesystem race. The URL shape (/assets/<basename>) gave no way to distinguish which extension point a request belonged to, so one target's asset would silently shadow another's.

Additionally, the middleware served files directly from the extension's source directory, which allowed path traversal to files outside the extension's bundle and made it impossible to serve include_assets-copied files that had been renamed by uniqueBasename during the build step.

WHAT is this pull request doing?

Opaque, target-scoped asset URLs

Asset URLs are now emitted as /assets/<target>/<assetKey>[.ext] instead of /assets/<basename>. This makes every URL unique per extension point, even when two targets share a source file.

Asset resolver

A new AssetResolver (Map<string, string>) is introduced per extension. During payload generation, each emitted URL subpath is registered in the resolver alongside the output-relative filesystem path the middleware should read. The resolver is cleared and rebuilt on every payload regeneration so stale entries from removed targets or renamed assets don't persist.

The resolver is stored in ExtensionsPayloadStore (keyed by devUUID) and exposed via getAssetResolver(devUUID). It is cleaned up when an extension is deleted.

Middleware rewrite

getExtensionAssetMiddleware now:

  1. Looks up the request's assetPath in the extension's resolver to find the actual output-relative filename (falling back to the raw path for direct compiled-artefact fetches).
  2. Resolves the candidate path against the extension's outputDir (the bundle directory that include_assets populates).
  3. Rejects any path — whether from the URL or from a resolver value — that escapes outputDir via traversal, returning 404 rather than 403 to avoid leaking directory structure.

Static directory assets

A new staticAssetsMapper handles directory-valued config entries (e.g. assets = "./assets"). It emits a directory-prefix URL with a trailing slash and registers one resolver entry per copied file, including nested subdirectories.

resolveOutputDir extracted

The logic for deriving an output directory from outputPath (file with extension → dirname; no extension → path itself) is extracted into a shared resolveOutputDir helper used by both the build step and the middleware.

How to test your changes?

  1. Create a UI extension with two extension points that both declare a tools asset pointing at different source files.
  2. Run shopify app dev and observe that each extension point's payload contains a distinct URL (/<target-a>/tools.json vs /<target-b>/tools.json).
  3. Fetch each URL from the dev server and confirm the correct file content is returned for each target.
  4. Verify that requesting a file path not present in the resolver (e.g. a raw ../secret.txt) returns 404.
  5. Create an extension point with assets = "./assets" containing nested files; confirm each file is individually servable via its resolver-mapped URL.

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes
  • I've considered analytics changes to measure impact
  • The change is user-facing — I've identified the correct bump type (patch for bug fixes · minor for new features · major for breaking changes) and added a changeset with pnpm changeset add

Copy link
Copy Markdown
Member Author

vividviolet commented Apr 23, 2026

@vividviolet vividviolet changed the base branch from main to graphite-base/7386 April 24, 2026 03:23
@vividviolet vividviolet force-pushed the 04-23-fix_dev_server_to_allow_serving_static_assets_from_a_shared_folder_outside_of_the_extension_directory branch from 8cac9b8 to 5fa082f Compare April 24, 2026 03:23
@vividviolet vividviolet changed the base branch from graphite-base/7386 to 04-21-add_assets_config_key_support_for_ui_extension_points April 24, 2026 03:24
@vividviolet vividviolet marked this pull request as ready for review April 24, 2026 03:27
@vividviolet vividviolet requested a review from a team as a code owner April 24, 2026 03:28
@vividviolet vividviolet changed the base branch from 04-21-add_assets_config_key_support_for_ui_extension_points to graphite-base/7386 April 24, 2026 21:53
@vividviolet vividviolet force-pushed the 04-23-fix_dev_server_to_allow_serving_static_assets_from_a_shared_folder_outside_of_the_extension_directory branch from 5fa082f to 26c602f Compare April 24, 2026 21:53
@vividviolet vividviolet changed the base branch from graphite-base/7386 to 04-22-address_png_encoding_bug_path_changes April 24, 2026 21:53
@elanalynn elanalynn force-pushed the 04-23-fix_dev_server_to_allow_serving_static_assets_from_a_shared_folder_outside_of_the_extension_directory branch from 26c602f to 5deb0e5 Compare April 24, 2026 23:15
@elanalynn elanalynn force-pushed the 04-22-address_png_encoding_bug_path_changes branch from b6d7170 to 10a3496 Compare April 24, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant