Skip to content

Validate extensions.targeting assets value in include-assets-step#7504

Open
JoshuaWhite1 wants to merge 1 commit intomainfrom
05-08-validate_extensions.targeting_assets_value_in_include-assets-step
Open

Validate extensions.targeting assets value in include-assets-step#7504
JoshuaWhite1 wants to merge 1 commit intomainfrom
05-08-validate_extensions.targeting_assets_value_in_include-assets-step

Conversation

@JoshuaWhite1
Copy link
Copy Markdown
Contributor

@JoshuaWhite1 JoshuaWhite1 commented May 8, 2026

WHY are these changes introduced?

closes https://github.com/shop/issues-admin-extensibility/issues/2521

When a developer sets an invalid assets value in [[extensions.targeting]] (empty string, ., ./, ../foo, etc.), the CLI currently builds the extension, compresses the bundle, and uploads it to GCS before the server rejects the deployment. When we pivoted to the extensions approach for hosted apps, we lost the contract validations from the admin module. That included: empty static_root (now called assets), length limit of 80 char, root path ./, ., and parent directories like ../.

The app will still currently fail, but with an incorrect error message, and further downstream. i.e. defining assets = ./, it is attempting to upload the entire project directory which contains node modules and fails the file limit validation:

image.png

WHAT is this pull request doing?

Adds a build-time guard in the include_assets step that validates assets values before bundling. Specifically:

  • In include-assets-step.ts: when a configKey inclusion targets extension_points[].assets, walk extension_points[] and validate each assets value against the extension directory.
  • Rejection cases:
    • Empty / whitespace-only → 'assets' for target '<target>' can't be empty.
    • ., ./, ../foo, ../../../foo, or any value that resolves to or outside the extension root → 'assets' for target '<target>' is not a valid path: '<value>'
  • Allowed: interior .. segments that resolve back inside the extension (e.g. public/../public) — validated via resolvePath + isSubpath rather than string-matching, so any chain length of ../ is handled.
  • Tests: 10 new cases in include-assets-step.test.ts covering each rejection shape plus the round-trip allowed case.

The guard fires before copyConfigKeyEntry runs, so a misconfigured value short-circuits the build instead of producing a bundle that the server later rejects.

How to test your changes?

  • configure a hosted app: shopify app init --template https://github.com/Shopify/shopify-app-template-extension-only
  • modify the assets value under extensions.targeting in extensions/app-home/shopify.extension.toml
    • set it to empty
    • set it to .
    • set it to ./
    • set it to ../
  • run shopify app dev + shopify app deploy in your local CLI, pointing to your app template:
    • SHOPIFY_CLI_NEVER_USE_PARTNERS_API=1 SHOPIFY_SERVICE_ENV=local pnpm run shopify app dev --path="<PATH_TO_APP>"
  • assert dev/deploy fails on empty and edge cases
  • modify the assets value in your toml back to valid cases
    • set it to assets
    • set it to ./assets
  • assert that correct cases do not cause errors

Considerations

Considered applying this in two alternative layers:

  • Along side file-size/file count validations in core
    • This formats the messages more consistently but there's a major flaw architecturally that I overlooked. By the time this stage has been reached, the CLI has already uploaded the compressed bundle to GCS. If the assets path is the root of the folder, it's uploading thousands of files
  • Schema level validation in cli/models/extensions/schemas.ts
    • currently: assets: zod.string().optional()
    • proposed:
assets: zod
    .string()
    .trim()
    .min(1, "'assets' can't be empty") // catches all empty cases like "", " ", etc 
    .refine( //logic for handling ./, . etc
    ...
    .optional
  • these validations are more than just structural validations, deemed less appropriate

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
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions github-actions Bot added the no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users. label May 8, 2026
@JoshuaWhite1 JoshuaWhite1 temporarily deployed to breaking-change-approval May 8, 2026 22:35 — with GitHub Actions Inactive
@JoshuaWhite1 JoshuaWhite1 force-pushed the 05-08-validate_extensions.targeting_assets_value_in_include-assets-step branch from 64c1e3a to 6ef723c Compare May 8, 2026 22:41
@JoshuaWhite1 JoshuaWhite1 deployed to breaking-change-approval May 8, 2026 22:43 — with GitHub Actions Active
@JoshuaWhite1 JoshuaWhite1 force-pushed the 05-08-validate_extensions.targeting_assets_value_in_include-assets-step branch from 6ef723c to 6feec59 Compare May 8, 2026 22:45
@JoshuaWhite1 JoshuaWhite1 marked this pull request as ready for review May 9, 2026 01:57
@JoshuaWhite1 JoshuaWhite1 requested a review from a team as a code owner May 9, 2026 01:57
@JoshuaWhite1 JoshuaWhite1 requested review from MitchLillie, Copilot, elanalynn, melissaluu and vividviolet and removed request for Copilot May 9, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant