Skip to content

Guard CLI bundle upload against oversized and out-of-app paths#7542

Merged
isaacroldan merged 4 commits into
mainfrom
05-13-cli_bundle_guard_size_and_path
May 15, 2026
Merged

Guard CLI bundle upload against oversized and out-of-app paths#7542
isaacroldan merged 4 commits into
mainfrom
05-13-cli_bundle_guard_size_and_path

Conversation

@isaacroldan
Copy link
Copy Markdown
Contributor

@isaacroldan isaacroldan commented May 13, 2026

WHY are these changes introduced?

Follow-up from the Slack discussion in #app-management around uploaded bundle size (thread). Today the CLI has no guard rails on the asset zip we upload to GCS: an asset path pointing outside the app folder (e.g. source = "../../" or an absolute home directory) silently copies whatever it resolves to, and the resulting bundle is then uploaded regardless of size. Josh confirmed a 2GB end-to-end upload, with the byproduct that a failed deploy still leaves a giant zip in .shopify/.

Server-side enforcement (#713307, capping ProcessBundle at 100MB) is the security boundary. This PR adds the CLI-side fail-fast for the accidental-misuse cases so developers don't OOM their own machine or wait through a multi-GB upload before being told no.

WHAT is this pull request doing?

Two guards in the bundle pipeline:

  1. Asset paths must resolve inside the app directory. include_assets entries (configKey, static, pattern) now reject sources that resolve outside the app folder (where shopify.app.toml lives). Sibling-extension paths still work (the boundary is the app, not the extension). Implemented via a small assertPathWithinAppDir helper plus a threaded appDirectory param through the three entry handlers and include-assets-step.ts.

  2. uploadToGCS aborts when the compressed bundle exceeds 100MB. Single chokepoint covers both deploy (services/deploy/upload.ts) and dev session (services/dev/processes/dev-session/dev-session.ts). The cap matches the new server-side check so the CLI and server agree on the limit. Error message points the developer at their asset config.

Out of scope on purpose: file-count limits, streaming the upload, and any opt-in GCS-side header — those were debated in the thread but defer cleanly.

How to test your changes?

Unit tests cover both guards:

  • packages/app/src/cli/services/build/steps/include-assets/assert-path-within-app.test.ts
  • packages/app/src/cli/services/build/steps/include-assets/*.test.ts (existing tests adjusted; behaviour preserved when paths stay inside the app dir)
  • packages/app/src/cli/services/bundle.test.ts (new uploadToGCS describe block — happy path + 101MB rejection)

Manual repro of the path bound: in any app, set an asset entry to source = "../../" or tools = "/Users/me/some/dir" and run shopify app build/deploy — you should get a clear Asset path '...' resolves outside the app directory error instead of a silent home-directory copy.

Manual repro of the size guard: temporarily lower MAX_BUNDLE_SIZE_MB to 1 in bundle.ts, then run deploy — the CLI should abort before the GCS PUT.

Post-release steps

None.

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows) — path check uses relativePath from cli-kit which normalises both POSIX and Windows separators
  • I've considered possible documentation changes — no public flag/config surface changed; the only new behaviour is a fail-fast error that didn't have docs before
  • I've considered analytics changes to measure impact — not adding metrics; if we want a counter for "rejected oversized bundle" we can add it in a follow-up
  • Added a changeset (patch bump for @shopify/app)

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 Area: @shopify/app @shopify/app package issues label May 13, 2026
Copy link
Copy Markdown
Contributor

@JoshuaWhite1 JoshuaWhite1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tophatted:

image.png
- Dev/Deploy for valid assets path succeeds (note: bundle upload log)

Screenshot 2026-05-13 at 3.16.31 PM.png- Escaping the App directory correctly errors (no bundle upload)

Screenshot 2026-05-13 at 3.18.48 PM.png- Assets path that exceeds 100MB correctly errors (no bundle upload)

Side note:
"", ./ and ../ will still upload the entire app/extension contents. Out of the scope of this PR but related. PR for empty is in the works #7530 and I'll do a follow up PR restricting the other two cases tomorrow.

@isaacroldan isaacroldan marked this pull request as ready for review May 14, 2026 08:26
@isaacroldan isaacroldan requested review from a team as code owners May 14, 2026 08:26
Copilot AI review requested due to automatic review settings May 14, 2026 08:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds client-side guardrails to the app bundle pipeline to prevent accidental inclusion of out-of-app files and to fail fast before attempting oversized bundle uploads, aligning behavior with the new server-side 100MB enforcement.

Changes:

  • Enforce that include_assets sources (static, configKey, pattern baseDir) resolve within the app directory via a shared assertPathWithinAppDir helper.
  • Abort uploadToGCS when the bundle file exceeds 100MB before reading/uploading it, and add unit tests for the size guard.
  • Thread appDirectory through include-assets step handlers and update related tests.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/app/src/cli/services/dev/extension/server/middlewares.test.ts Updates middleware test setup to pass appDirectory.
packages/app/src/cli/services/bundle.ts Adds bundle size constants and an early size check in uploadToGCS.
packages/app/src/cli/services/bundle.test.ts Adds uploadToGCS tests for under-limit upload and over-limit rejection.
packages/app/src/cli/services/build/steps/include-assets/copy-source-entry.ts Adds app-directory boundary assertion for static source entries.
packages/app/src/cli/services/build/steps/include-assets/copy-source-entry.test.ts Updates tests to pass appDirectory.
packages/app/src/cli/services/build/steps/include-assets/copy-config-key-entry.ts Adds app-directory boundary assertion for configKey-resolved paths.
packages/app/src/cli/services/build/steps/include-assets/copy-config-key-entry.test.ts Updates tests to pass appDirectory.
packages/app/src/cli/services/build/steps/include-assets/copy-by-pattern.ts Adds app-directory boundary assertion for pattern sourceDir.
packages/app/src/cli/services/build/steps/include-assets/copy-by-pattern.test.ts Updates tests to pass new config values used for error reporting.
packages/app/src/cli/services/build/steps/include-assets/assert-path-within-app.ts Introduces shared helper to reject out-of-app resolved paths.
packages/app/src/cli/services/build/steps/include-assets/assert-path-within-app.test.ts Adds unit coverage for the new helper.
packages/app/src/cli/services/build/steps/include-assets-step.ts Threads appDirectory through include-assets dispatch to entry handlers.
packages/app/src/cli/services/build/steps/include-assets-step.test.ts Fixes test context to provide options.app.directory.
.changeset/guard-bundle-upload-size-and-paths.md Patch changeset for @shopify/app.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/app/src/cli/services/bundle.ts
Comment thread packages/app/src/cli/services/bundle.test.ts
Copy link
Copy Markdown
Member

@vividviolet vividviolet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Just some minor suggestions

Comment thread packages/app/src/cli/services/build/steps/include-assets/copy-by-pattern.ts Outdated
Comment thread packages/app/src/cli/services/bundle.ts
- Check `..` as a path segment (not prefix) in assertPathWithinAppDir so a sibling named `..cache` isn't false-rejected.
- Round up the displayed size in the bundle limit error via Math.ceil so a size just over the cap can't display as the cap.
- Mock fileSize in the over-limit test to avoid allocating ~101MB in CI memory.
- Move the app-directory boundary check before glob in copy-by-pattern so an out-of-app sourceDir fails fast; preserve missing-dir behavior via fileExists short-circuit.
The new fileExists short-circuit in copyByPattern broke the auto-mocked
pattern tests at the step level (fileExists returns undefined → early
return). Add a beforeEach default for the pattern-entries block, and
switch the pattern-only manifest test from a flat false to an
implementation that returns true for the sourceDir path.
@isaacroldan isaacroldan added this pull request to the merge queue May 15, 2026
Merged via the queue into main with commit ced0ad4 May 15, 2026
26 of 28 checks passed
@isaacroldan isaacroldan deleted the 05-13-cli_bundle_guard_size_and_path branch May 15, 2026 10:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: @shopify/app @shopify/app package issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants