@shopify/create-app 4.9.1 → 4.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/{analytics-IECFN4JQ.js → analytics-TTLHU7RJ.js} +14 -14
  2. package/dist/{analytics-LBB7P36D.js → analytics-XMJCXJPW.js} +14 -14
  3. package/dist/{chunk-3YGXE2AP.js → chunk-3FNBF3J4.js} +3 -3
  4. package/dist/{chunk-XIVTHG4G.js → chunk-5H43E2UF.js} +14 -14
  5. package/dist/{chunk-U5GH53OY.js → chunk-7U2D7OQY.js} +16 -16
  6. package/dist/{chunk-IGYLFVSH.js → chunk-CJUEOAXA.js} +4 -4
  7. package/dist/{chunk-EJPPP2UR.js → chunk-GI3EXDGY.js} +4 -4
  8. package/dist/{chunk-27YRBZHB.js → chunk-GXR6N3MP.js} +2 -2
  9. package/dist/{chunk-AIZTGQJR.js → chunk-HAZKL7NQ.js} +6 -6
  10. package/dist/{chunk-DC6ZFOVX.js → chunk-HT6VPO56.js} +3 -3
  11. package/dist/{chunk-37A2FHUG.js → chunk-KFK6UTCG.js} +2 -2
  12. package/dist/{chunk-6OYK2NCF.js → chunk-MA5EMKMH.js} +2 -2
  13. package/dist/{chunk-WXRXVG7I.js → chunk-N46TBZG5.js} +2 -2
  14. package/dist/{chunk-XTW7GZNX.js → chunk-N5WBV6WX.js} +5 -5
  15. package/dist/{chunk-GXCPTCSE.js → chunk-QIGNQLSJ.js} +2 -2
  16. package/dist/{chunk-QWRYHKCI.js → chunk-R6VAFEZ5.js} +3 -3
  17. package/dist/{chunk-7NOX742S.js → chunk-T2OHLRUY.js} +4 -4
  18. package/dist/{chunk-63WOEPSQ.js → chunk-U53ZNKER.js} +80 -27
  19. package/dist/{chunk-XE2VSA7J.js → chunk-YGIFL56H.js} +9 -9
  20. package/dist/{chunk-7RUPPYI4.js → chunk-ZWNY6NQH.js} +7 -7
  21. package/dist/{command-json-schema-NKZVCRA4.js → command-json-schema-RCC6ZVRF.js} +7 -7
  22. package/dist/{conf-store-OXRRZ7GJ.js → conf-store-STBXZZZR.js} +5 -5
  23. package/dist/{custom-oclif-loader-GXZPW6CV.js → custom-oclif-loader-Q7GRLCIZ.js} +5 -5
  24. package/dist/{deprecations-DRF2EUF3.js → deprecations-T466QS35.js} +10 -10
  25. package/dist/{environments-4D4TK7F7.js → environments-L53AHQLZ.js} +11 -11
  26. package/dist/{error-handler-LQ3ZDIBZ.js → error-handler-DRCELNRR.js} +14 -14
  27. package/dist/{fs-PMEOY2EH.js → fs-VUYIAOSY.js} +3 -3
  28. package/dist/hooks/postrun.js +4 -4
  29. package/dist/hooks/prerun.js +6 -6
  30. package/dist/index.js +212 -130
  31. package/dist/{is-global-LYFZG4LV.js → is-global-C4ZYYOO2.js} +3 -3
  32. package/dist/{json-error-4FQODRLE.js → json-error-YAPVM6KF.js} +6 -6
  33. package/dist/{local-EVID5OFN.js → local-YQI64EPC.js} +2 -2
  34. package/dist/{metadata-WETP27SP.js → metadata-NABA27NK.js} +3 -3
  35. package/dist/{node-package-manager-BLIC3IFC.js → node-package-manager-GY7LMT5D.js} +12 -12
  36. package/dist/{notifications-system-45UW7RPJ.js → notifications-system-244NM3IW.js} +15 -15
  37. package/dist/{os-VO35CUIL.js → os-34M2RBVM.js} +4 -4
  38. package/dist/{output-7GWEONQ7.js → output-GT6WTA4L.js} +3 -3
  39. package/dist/{system-F25CZRFX.js → system-Q623ZJ6Z.js} +10 -10
  40. package/dist/{ui-KEWWVBIJ.js → ui-DVZCQR62.js} +10 -10
  41. package/dist/{upgrade-RSDWMQIO.js → upgrade-3FR3Z3QX.js} +16 -16
  42. package/dist/{version-XXESWBZG.js → version-GRDUIVHB.js} +11 -11
  43. package/dist/{version-MTILA6OW.js → version-GZMTMHG3.js} +2 -2
  44. package/oclif.manifest.json +1 -1
  45. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  decodeToml,
5
5
  loadEnvironment,
6
6
  require_toml_patch
7
- } from "./chunk-XTW7GZNX.js";
7
+ } from "./chunk-N5WBV6WX.js";
8
8
  import {
9
9
  require_normalize_path
10
10
  } from "./chunk-RIQNGBMO.js";
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  setCurrentCommandId,
17
17
  showNotificationsIfNeeded
18
- } from "./chunk-XE2VSA7J.js";
18
+ } from "./chunk-YGIFL56H.js";
19
19
  import {
20
20
  ClientError,
21
21
  DevServerCore,
@@ -81,7 +81,7 @@ import {
81
81
  setPathValue,
82
82
  shopifyFetch,
83
83
  storeAdminUrl
84
- } from "./chunk-XIVTHG4G.js";
84
+ } from "./chunk-5H43E2UF.js";
85
85
  import {
86
86
  setNextDeprecationDate
87
87
  } from "./chunk-MSED5ZFM.js";
@@ -103,26 +103,26 @@ import {
103
103
  usesWorkspaces,
104
104
  versionSatisfies,
105
105
  writePackageJSON
106
- } from "./chunk-AIZTGQJR.js";
106
+ } from "./chunk-HAZKL7NQ.js";
107
107
  import {
108
108
  import_websocket_server
109
109
  } from "./chunk-LDYT4FTV.js";
110
110
  import "./chunk-FKZ3NFHZ.js";
111
111
  import {
112
112
  CLI_KIT_VERSION
113
- } from "./chunk-6OYK2NCF.js";
113
+ } from "./chunk-MA5EMKMH.js";
114
114
  import "./chunk-G53O4PDO.js";
115
115
  import {
116
116
  LocalStorage,
117
117
  require_ajv,
118
118
  runAtMinimumInterval,
119
119
  serviceEnvironment
120
- } from "./chunk-IGYLFVSH.js";
120
+ } from "./chunk-CJUEOAXA.js";
121
121
  import {
122
122
  globalCLIVersion,
123
123
  isPreReleaseVersion,
124
124
  localCLIVersion
125
- } from "./chunk-GXCPTCSE.js";
125
+ } from "./chunk-QIGNQLSJ.js";
126
126
  import {
127
127
  AbortController as AbortController2,
128
128
  Alert,
@@ -170,10 +170,10 @@ import {
170
170
  use_input_default,
171
171
  use_stdin_default,
172
172
  use_stdout_default
173
- } from "./chunk-63WOEPSQ.js";
173
+ } from "./chunk-U53ZNKER.js";
174
174
  import {
175
175
  runWithCommandEventsForCommand
176
- } from "./chunk-7NOX742S.js";
176
+ } from "./chunk-T2OHLRUY.js";
177
177
  import {
178
178
  execa,
179
179
  isStream,
@@ -186,14 +186,14 @@ import {
186
186
  require_signal_exit,
187
187
  stripFinalNewline,
188
188
  username
189
- } from "./chunk-27YRBZHB.js";
189
+ } from "./chunk-GXR6N3MP.js";
190
190
  import "./chunk-JGGOQOPO.js";
191
191
  import {
192
192
  addPublicMetadata,
193
193
  addSensitiveMetadata,
194
194
  createRuntimeMetadataContainer,
195
195
  runWithTimer
196
- } from "./chunk-DC6ZFOVX.js";
196
+ } from "./chunk-HT6VPO56.js";
197
197
  import {
198
198
  fileHash,
199
199
  getAppAutomationToken,
@@ -206,20 +206,20 @@ import {
206
206
  } from "./chunk-SHCNJ47P.js";
207
207
  import {
208
208
  defineJsonOutputSchema
209
- } from "./chunk-WXRXVG7I.js";
209
+ } from "./chunk-N46TBZG5.js";
210
210
  import "./chunk-CM6ALOD7.js";
211
211
  import {
212
212
  AbortError,
213
213
  AbortSilentError,
214
214
  BugError,
215
215
  CancelExecution
216
- } from "./chunk-EJPPP2UR.js";
216
+ } from "./chunk-GI3EXDGY.js";
217
217
  import {
218
218
  waitForPostRunHookAndExit
219
- } from "./chunk-U5GH53OY.js";
219
+ } from "./chunk-7U2D7OQY.js";
220
220
  import {
221
221
  treeKill
222
- } from "./chunk-37A2FHUG.js";
222
+ } from "./chunk-KFK6UTCG.js";
223
223
  import {
224
224
  appendFile,
225
225
  asHumanFriendlyArray,
@@ -316,7 +316,7 @@ import {
316
316
  unstyled,
317
317
  writeFile,
318
318
  writeFileSync
319
- } from "./chunk-7RUPPYI4.js";
319
+ } from "./chunk-ZWNY6NQH.js";
320
320
  import {
321
321
  codespacePortForwardingDomain,
322
322
  codespaceURL,
@@ -330,7 +330,7 @@ import {
330
330
  isUnitTest,
331
331
  isVerbose,
332
332
  noInputFlag
333
- } from "./chunk-QWRYHKCI.js";
333
+ } from "./chunk-R6VAFEZ5.js";
334
334
  import {
335
335
  environmentVariables,
336
336
  themeKitAccessDomain
@@ -203943,7 +203943,7 @@ var import_core2 = __toESM(require_lib()), BaseCommand = class extends import_co
203943
203943
  }
203944
203944
  async catch(error) {
203945
203945
  error.skipOclifErrorHandling = !0;
203946
- let { errorHandler } = await import("./error-handler-LQ3ZDIBZ.js");
203946
+ let { errorHandler } = await import("./error-handler-DRCELNRR.js");
203947
203947
  return await errorHandler(error, this.config), import_core2.Errors.handle(error);
203948
203948
  }
203949
203949
  async _run() {
@@ -203951,11 +203951,11 @@ var import_core2 = __toESM(require_lib()), BaseCommand = class extends import_co
203951
203951
  }
203952
203952
  async init() {
203953
203953
  if (this.exitWithTimestampWhenEnvVariablePresent(), setCurrentCommandId(this.id ?? ""), !isDevelopment()) {
203954
- let { registerCleanBugsnagErrorsFromWithinPlugins } = await import("./error-handler-LQ3ZDIBZ.js");
203954
+ let { registerCleanBugsnagErrorsFromWithinPlugins } = await import("./error-handler-DRCELNRR.js");
203955
203955
  await registerCleanBugsnagErrorsFromWithinPlugins(this.config);
203956
203956
  }
203957
203957
  await removeDuplicatedPlugins(this.config), this.showNpmFlagWarning();
203958
- let { showNotificationsIfNeeded: showNotificationsIfNeeded2 } = await import("./notifications-system-45UW7RPJ.js");
203958
+ let { showNotificationsIfNeeded: showNotificationsIfNeeded2 } = await import("./notifications-system-244NM3IW.js");
203959
203959
  return await showNotificationsIfNeeded2(), super.init();
203960
203960
  }
203961
203961
  // NPM creates an environment variable for every flag passed to a script.
@@ -203963,7 +203963,7 @@ var import_core2 = __toESM(require_lib()), BaseCommand = class extends import_co
203963
203963
  // and warns the user to use the `--` separator.
203964
203964
  showNpmFlagWarning() {
203965
203965
  let commandVariables = this.constructor;
203966
- Object.keys(commandVariables.flags || {}).map((key) => `npm_config_${underscore(key).replace(/^no_/, "")}`).some((flag) => process.env[flag] !== void 0) && import("./ui-KEWWVBIJ.js").then(({ renderWarning: renderWarning2 }) => {
203966
+ Object.keys(commandVariables.flags || {}).map((key) => `npm_config_${underscore(key).replace(/^no_/, "")}`).some((flag) => process.env[flag] !== void 0) && import("./ui-DVZCQR62.js").then(({ renderWarning: renderWarning2 }) => {
203967
203967
  renderWarning2({
203968
203968
  body: [
203969
203969
  "NPM scripts require an extra",
@@ -204012,7 +204012,7 @@ ${explanation} in non-interactive terminal environments, such as a CI environmen
204012
204012
  let flags = originalResult.flags, environmentsFileName = this.environmentsFilename();
204013
204013
  if (!environmentsFileName)
204014
204014
  return originalResult;
204015
- let { environmentFilePath } = await import("./environments-4D4TK7F7.js"), environmentFileExists = await environmentFilePath(environmentsFileName, { from: flags.path }), environments = [];
204015
+ let { environmentFilePath } = await import("./environments-L53AHQLZ.js"), environmentFileExists = await environmentFilePath(environmentsFileName, { from: flags.path }), environments = [];
204016
204016
  flags.environment && (environments = Array.isArray(flags.environment) ? flags.environment : [flags.environment]);
204017
204017
  let environmentSpecified = environments.length > 0;
204018
204018
  if (!environmentFileExists && !environmentSpecified || environmentSpecified && environments.length > 1)
@@ -204033,7 +204033,7 @@ ${explanation} in non-interactive terminal environments, such as a CI environmen
204033
204033
  * is specified it will try to load a default environment.
204034
204034
  */
204035
204035
  async loadEnvironmentForCommand(path4, environmentsFileName, specifiedEnvironment) {
204036
- let { loadEnvironment: loadEnvironment2 } = await import("./environments-4D4TK7F7.js");
204036
+ let { loadEnvironment: loadEnvironment2 } = await import("./environments-L53AHQLZ.js");
204037
204037
  return specifiedEnvironment ? { environment: await loadEnvironment2(specifiedEnvironment, environmentsFileName, { from: path4 }), isDefaultEnvironment: !1 } : { environment: await loadEnvironment2("default", environmentsFileName, { from: path4, silent: !0 }), isDefaultEnvironment: !0 };
204038
204038
  }
204039
204039
  };
@@ -204056,7 +204056,7 @@ function reportEnvironmentApplication(noDefaultsFlags, flagsWithEnvironments, en
204056
204056
  if (Object.keys(changes).length === 0)
204057
204057
  return;
204058
204058
  let items = Object.entries(changes).map(([name, value]) => `${name}: ${value}`);
204059
- import("./ui-KEWWVBIJ.js").then(({ renderInfo: renderInfo2 }) => {
204059
+ import("./ui-DVZCQR62.js").then(({ renderInfo: renderInfo2 }) => {
204060
204060
  renderInfo2({
204061
204061
  headline: ["Using applicable flags from", { userInput: environmentName }, "environment:"],
204062
204062
  body: [{ list: { items } }]
@@ -204107,7 +204107,7 @@ async function removeDuplicatedPlugins(config2) {
204107
204107
  let plugins = Array.from(config2.plugins.values()), bundlePlugins = ["@shopify/app", "@shopify/plugin-cloudflare"], pluginsToRemove = plugins.filter((plugin) => bundlePlugins.includes(plugin.name));
204108
204108
  if (pluginsToRemove.length > 0) {
204109
204109
  let commandsToRun = pluginsToRemove.map((plugin) => ` - shopify plugins remove ${plugin.name}`).join(`
204110
- `), { renderWarning: renderWarning2 } = await import("./ui-KEWWVBIJ.js");
204110
+ `), { renderWarning: renderWarning2 } = await import("./ui-DVZCQR62.js");
204111
204111
  renderWarning2({
204112
204112
  headline: `Unsupported plugins detected: ${pluginsToRemove.map((plugin) => plugin.name).join(", ")}`,
204113
204113
  body: [
@@ -204129,20 +204129,20 @@ init_cjs_shims();
204129
204129
  init_cjs_shims();
204130
204130
  import { fileURLToPath as fileURLToPath2 } from "node:url";
204131
204131
  async function launchCLI(options) {
204132
- let { isDevelopment: isDevelopment2 } = await import("./local-EVID5OFN.js"), { ShopifyConfig } = await import("./custom-oclif-loader-GXZPW6CV.js"), oclifModule = await import("./lib-NYDMHWZM.js"), { run, flush, Errors: Errors2, settings } = oclifModule.default ?? oclifModule;
204132
+ let { isDevelopment: isDevelopment2 } = await import("./local-YQI64EPC.js"), { ShopifyConfig } = await import("./custom-oclif-loader-Q7GRLCIZ.js"), oclifModule = await import("./lib-NYDMHWZM.js"), { run, flush, Errors: Errors2, settings } = oclifModule.default ?? oclifModule;
204133
204133
  isDevelopment2() && (settings.debug = !0);
204134
204134
  try {
204135
204135
  let config2 = new ShopifyConfig({ root: fileURLToPath2(options.moduleURL) });
204136
204136
  await config2.load(), options.lazyCommandLoader && config2.setLazyCommandLoader(options.lazyCommandLoader);
204137
204137
  let argv = options.argv ?? process.argv.slice(2), passthroughIndex = argv.indexOf("--");
204138
204138
  if ((passthroughIndex === -1 ? argv : argv.slice(0, passthroughIndex)).includes("--json-schema") || isTruthy(process.env.SHOPIFY_FLAG_JSON_SCHEMA)) {
204139
- let { printCommandJsonSchema } = await import("./command-json-schema-NKZVCRA4.js");
204139
+ let { printCommandJsonSchema } = await import("./command-json-schema-RCC6ZVRF.js");
204140
204140
  await printCommandJsonSchema(config2, argv, options.lazyCommandLoader);
204141
204141
  return;
204142
204142
  }
204143
204143
  await run(argv, config2), await flush();
204144
204144
  } catch (error) {
204145
- let { errorHandler } = await import("./error-handler-LQ3ZDIBZ.js");
204145
+ let { errorHandler } = await import("./error-handler-DRCELNRR.js");
204146
204146
  await errorHandler(error);
204147
204147
  let handledError = error;
204148
204148
  return Errors2.handle({
@@ -204160,7 +204160,7 @@ var import_core3 = __toESM(require_lib());
204160
204160
  async function exitIfOldNodeVersion(versions2 = process.versions) {
204161
204161
  let nodeVersion = versions2.node, nodeMajorVersion = Number(nodeVersion.split(".")[0]);
204162
204162
  if (nodeMajorVersion < 22) {
204163
- let { renderError: renderError2 } = await import("./ui-KEWWVBIJ.js");
204163
+ let { renderError: renderError2 } = await import("./ui-DVZCQR62.js");
204164
204164
  renderError2({
204165
204165
  headline: "Upgrade to a supported Node version now.",
204166
204166
  body: [
@@ -204187,7 +204187,7 @@ async function runCLI(options, launchCLI2 = launchCLI, argv = process.argv, env2
204187
204187
  return setupEnvironmentVariables(options, argv, env2), options.runInCreateMode && await addInitToArgvWhenRunningCreateCLI(options, argv), forceNoColor(argv, env2), await exitIfOldNodeVersion(versions2), launchCLI2({ moduleURL: options.moduleURL, lazyCommandLoader: options.lazyCommandLoader });
204188
204188
  }
204189
204189
  async function addInitToArgvWhenRunningCreateCLI(options, argv = process.argv) {
204190
- let { findUpAndReadPackageJson: findUpAndReadPackageJson2 } = await import("./node-package-manager-BLIC3IFC.js"), { moduleDirectory: moduleDirectory2 } = await import("./path-2KZ7FSGN.js"), name = (await findUpAndReadPackageJson2(moduleDirectory2(options.moduleURL))).content.name.replace("@shopify/create-", "");
204190
+ let { findUpAndReadPackageJson: findUpAndReadPackageJson2 } = await import("./node-package-manager-GY7LMT5D.js"), { moduleDirectory: moduleDirectory2 } = await import("./path-2KZ7FSGN.js"), name = (await findUpAndReadPackageJson2(moduleDirectory2(options.moduleURL))).content.name.replace("@shopify/create-", "");
204191
204191
  if (argv.findIndex((arg) => arg.includes("init")) === -1) {
204192
204192
  let initIndex2 = argv.findIndex((arg) => arg.match(new RegExp(`bin(\\/|\\\\)+(create-${name}|dev|run)`))) + 1;
204193
204193
  argv.splice(initIndex2, 0, "init");
@@ -217373,7 +217373,7 @@ init_cjs_shims();
217373
217373
 
217374
217374
  // ../cli-kit/dist/public/node/liquid.js
217375
217375
  init_cjs_shims();
217376
- var import_liquidjs = __toESM(require_liquid_node(), 1);
217376
+ var import_liquidjs = __toESM(require_liquid_node());
217377
217377
  function renderLiquidTemplate(templateContent, data) {
217378
217378
  let engine = new import_liquidjs.Liquid();
217379
217379
  return engine.render(engine.parse(templateContent), data);
@@ -220472,7 +220472,7 @@ init_cjs_shims();
220472
220472
  init_cjs_shims();
220473
220473
  var import_core29 = __toESM(require_lib(), 1), blockingLevels = ["high", "medium", "low", "none"], appSecurityBlockingFlag = {
220474
220474
  blocking: import_core29.Flags.custom({
220475
- description: "The minimum finding severity that causes a non-zero exit code.",
220475
+ description: "The minimum finding severity that causes a non-zero exit code: high, medium, or low. Defaults to none, which never fails on findings.",
220476
220476
  options: blockingLevels,
220477
220477
  default: "none",
220478
220478
  env: "SHOPIFY_FLAG_APP_SECURITY_BLOCKING"
@@ -220595,7 +220595,7 @@ Do not report:
220595
220595
  `,
220596
220596
  `---
220597
220597
  id: APP_PROXY_LIQUID_INJECTION
220598
- version: 2
220598
+ version: 3
220599
220599
  severity: high
220600
220600
  precedence: prefer-agent
220601
220601
  ---
@@ -220700,10 +220700,10 @@ Do not report:
220700
220700
  - Protocol-only canonicalization concerns with no demonstrated app-local exploit path
220701
220701
  - Test handlers
220702
220702
  `,
220703
- "---\nid: COMMITTED_SECRET\nversion: 3\nseverity: high\nprecedence: union\n---\n\n# Committed Secret\n\nInspect files skipped by deterministic secret scanning for committed credentials. Never quote or reproduce a secret; cite only the file and redacted credential kind, and recommend rotation.\n\nDo not report placeholders, public client identifiers (`SHOPIFY_API_KEY`, Stripe `pk_`), or files git confirms are untracked and ignored. Template env files (`.env.example`, `.sample`, `.template`, `.dist`) are findings only when they contain a known credential format.\n",
220703
+ "---\nid: COMMITTED_SECRET\nversion: 4\nseverity: high\nprecedence: union\n---\n\n# Committed Secret\n\nInspect files skipped by deterministic secret scanning for committed credentials. Never quote or reproduce a secret; cite only the file and redacted credential kind, and recommend rotation.\n\nDo not report placeholders, public client identifiers (`SHOPIFY_API_KEY`, Stripe `pk_`), or files git confirms are untracked and ignored. Template env files (`.env.example`, `.sample`, `.template`, `.dist`) are findings only when they contain a known credential format.\n",
220704
220704
  `---
220705
220705
  id: CREDENTIAL_BROWSER_LEAKAGE
220706
- version: 1
220706
+ version: 2
220707
220707
  severity: high
220708
220708
  precedence: prefer-agent
220709
220709
  ---
@@ -220714,7 +220714,7 @@ Trace credentials, access tokens, session tokens, and client secrets into loader
220714
220714
  `,
220715
220715
  `---
220716
220716
  id: CREDENTIAL_LOG_LEAKAGE
220717
- version: 1
220717
+ version: 2
220718
220718
  severity: high
220719
220719
  precedence: prefer-agent
220720
220720
  ---
@@ -221025,7 +221025,7 @@ Do not report:
221025
221025
  `,
221026
221026
  `---
221027
221027
  id: DEPRECATED_SCRIPT_TAG_SCOPE
221028
- version: 1
221028
+ version: 2
221029
221029
  severity: medium
221030
221030
  precedence: prefer-agent
221031
221031
  ---
@@ -221036,7 +221036,7 @@ Inspect parsed app scopes and JavaScript/TypeScript Admin API operations for dep
221036
221036
  `,
221037
221037
  `---
221038
221038
  id: EOL_API_VERSION
221039
- version: 1
221039
+ version: 2
221040
221040
  severity: low
221041
221041
  precedence: prefer-agent
221042
221042
  ---
@@ -221045,7 +221045,7 @@ precedence: prefer-agent
221045
221045
 
221046
221046
  Inspect the selected \`shopify.app*.toml\` plus React Router \`app/shopify.server.*\` declarations. Shopify publishes quarterly versions in January, April, July, and October and supports each stable version for 12 months; the app security check allows a documented 30-day extension grace period before reporting it as end-of-life. Cite the exact declaration. For malformed config, computed \`ApiVersion\` values, or a Shopify-announced exceptional extension, inspect the source and current lifecycle policy rather than inferring from unrelated constants.
221047
221047
  `,
221048
- "---\nid: EXPIRING_OFFLINE_TOKEN\nversion: 1\nseverity: medium\nprecedence: prefer-agent\n---\n\n# Expiring Offline Token\n\nFor supported React Router apps, verify `expiringOfflineAccessTokens` is enabled and the selected session storage persists `expires`, `refreshToken`, and `refreshTokenExpires` metadata needed for refresh and rotation. `isOnline: false` selects an offline session; it does not disable token expiry and is not a finding. Report an explicit `expiringOfflineAccessTokens: false`. Treat absent or computed flags, custom storage, and ambiguous Prisma schemas as unresolved investigation: inspect storage adapters, migrations, and serialization before returning a clean result. Config-only and unsupported frameworks are handled by the runtime applicability boundary.\n",
221048
+ "---\nid: EXPIRING_OFFLINE_TOKEN\nversion: 2\nseverity: medium\nprecedence: prefer-agent\n---\n\n# Expiring Offline Token\n\nFor supported React Router apps, verify `expiringOfflineAccessTokens` is enabled and the selected session storage persists `expires`, `refreshToken`, and `refreshTokenExpires` metadata needed for refresh and rotation. `isOnline: false` selects an offline session; it does not disable token expiry and is not a finding. Report an explicit `expiringOfflineAccessTokens: false`. Treat absent or computed flags, custom storage, and ambiguous Prisma schemas as unresolved investigation: inspect storage adapters, migrations, and serialization before returning a clean result. Config-only and unsupported frameworks are handled by the runtime applicability boundary.\n",
221049
221049
  `---
221050
221050
  id: INSECURE_WEBHOOK_URL
221051
221051
  version: 2
@@ -221060,17 +221060,24 @@ Inspect webhook destinations and OAuth redirect URLs in the selected Shopify app
221060
221060
  "---\nid: LIQUID_UNSAFE_RENDER\nversion: 1\nseverity: medium\nprecedence: union\n---\n\n# Liquid Unsafe Render\n\nInspect only theme-extension Liquid/HTML files the parser could not analyze. Liquid output is not automatically HTML-escaped. Check the destination: use `escape`/`escape_once` for HTML text and ordinary attributes, `json` when embedding a value as JavaScript data, and `metafield_tag` only for supported rich metafield rendering in HTML content. HTML escaping is not sufficient for event handlers, `srcdoc`, or a `<script src>` URL; avoid merchant-controlled output there and validate any dynamic URL. An ordinary non-script `src` attribute is not itself an executable sink. `{% raw %}` suppresses Liquid parsing, so apparent output inside it is literal text. Follow rendered snippets and inspect ambiguous filters or parser failures before reporting.\n",
221061
221061
  `---
221062
221062
  id: METAFIELD_OFFLINE_TOKEN
221063
- version: 1
221063
+ version: 2
221064
221064
  severity: high
221065
221065
  ---
221066
221066
 
221067
221067
  # Metafield Offline Token
221068
221068
 
221069
- Trace metafield writes and determine whether they use an offline Admin API context where an authenticated online context is required. Confirm token provenance and compensating authorization before reporting.
221069
+ Trace metafield and metaobject writes and determine their token provenance. Report a write that runs in an offline Admin API context that no verified request for the same shop authorizes: for example \`unauthenticated.admin(shop)\` or a stored offline session reached from an unauthenticated route, an unverified webhook or app proxy request, or a job whose shop or values come from request-controlled input.
221070
+
221071
+ An offline session returned by \`authenticate.admin(request)\` (the React Router SDK default when \`useOnlineTokens\` isn't set) is a verified, shop-bound context: Shopify verified the session token and the staff member's access to the app. Don't report a write only because its token is offline or because an online token would add user attribution or expiry. Report it when:
221072
+
221073
+ - the app defines its own per-user permission, for example an app-level role or allowlist check, and this write bypasses it. Name that permission.
221074
+ - request-controlled values reach an app-reserved (\`$app\`) metafield or metaobject that the app treats as trusted state and merchants can't edit themselves, for example a definition with \`access.admin = "merchant_read"\` or no merchant access, or billing, entitlement or feature-flag data. Writing to the \`$app\` namespace isn't safe by itself.
221075
+
221076
+ Confirm token provenance and compensating authorization before reporting.
221070
221077
  `,
221071
221078
  `---
221072
221079
  id: MISSING_AUTHORIZATION_CHECK
221073
- version: 2
221080
+ version: 3
221074
221081
  severity: high
221075
221082
  ---
221076
221083
 
@@ -221127,6 +221134,16 @@ actions.
221127
221134
  changes, direct URLs that survive permission downgrades, and backend actions
221128
221135
  that remain callable after a feature is hidden or disabled in the UI.
221129
221136
 
221137
+ ## Shopify embedded apps (React Router)
221138
+
221139
+ \`authenticate.admin(request)\` verifies the Shopify session token, so the caller is a staff member of that shop whom Shopify has granted access to this app. With offline tokens (the SDK default), Admin API calls use the app's granted scopes rather than the staff member's own permissions. That is Shopify's standard embedded app model, not a finding by itself. Report a handler only when it skips a concrete authorization boundary inside the app:
221140
+
221141
+ - The app defines its own roles, owners, or allowlists (for example a check on \`sessionToken.sub\`, \`onlineAccessInfo.associated_user\`, \`account_owner\`, or an app database role), and a handler that performs the same or a more privileged operation doesn't apply it. Compare each route's loader and action, and sibling routes.
221142
+ - A request-supplied identifier selects an app-owned record that belongs to another user of the app.
221143
+ - The app uses online tokens to respect staff permissions on some paths, but a privileged path falls back to an offline session.
221144
+
221145
+ If none of these applies, record the check as executed with no finding.
221146
+
221130
221147
  ## What to report
221131
221148
 
221132
221149
  For each action that exposes a concrete unauthorized operation beyond authentication:
@@ -221530,7 +221547,7 @@ Do not report:
221530
221547
  `,
221531
221548
  `---
221532
221549
  id: REQUEST_CONTROLLED_ADMIN_CONTEXT
221533
- version: 3
221550
+ version: 4
221534
221551
  severity: high
221535
221552
  precedence: prefer-agent
221536
221553
  ---
@@ -222129,17 +222146,7 @@ Do not report:
222129
222146
  - \`exec()\` with literal strings (no user input)
222130
222147
  - Shell calls with properly escaped arguments
222131
222148
  `,
222132
- `---
222133
- id: STATIC_FRAME_ANCESTORS
222134
- version: 1
222135
- severity: high
222136
- precedence: prefer-agent
222137
- ---
222138
-
222139
- # Static Frame Ancestors
222140
-
222141
- Inspect embedded-app Content-Security-Policy construction. Report wildcard or static cross-shop frame-ancestors policies; accept a policy derived safely for the authenticated shop plus Shopify Admin.
222142
- `,
222149
+ "---\nid: STATIC_FRAME_ANCESTORS\nversion: 2\nseverity: high\nprecedence: prefer-agent\n---\n\n# Static Frame Ancestors\n\nInspect embedded-app Content-Security-Policy construction, including headers that app code sets after or instead of the SDK's. Report wildcard or static cross-shop frame-ancestors policies, such as `*`, `https:`, `*.myshopify.com`, or a fixed list of shop domains, whether the value is written literally or built from variables. Accept a policy derived safely for the shop plus Shopify Admin.\n\nThe React Router SDK's `addDocumentResponseHeaders` (from `shopifyApp()`, usually called in `entry.server`) is the accepted pattern: it sets `frame-ancestors` to the sanitized request shop plus Shopify Admin origins, including Shopify-operated development admin origins. Don't report it unless app code overrides or extends its header.\n",
222143
222150
  `---
222144
222151
  id: TEXT_SETTING_HTML_SMUGGLING
222145
222152
  version: 2
@@ -222334,7 +222341,7 @@ Do not report:
222334
222341
  `,
222335
222342
  `---
222336
222343
  id: UNAUTHENTICATED_ENDPOINT
222337
- version: 2
222344
+ version: 3
222338
222345
  severity: high
222339
222346
  precedence: union
222340
222347
  ---
@@ -222471,7 +222478,7 @@ pack instructions.
222471
222478
  `,
222472
222479
  `---
222473
222480
  id: UNSAFE_INNERHTML
222474
- version: 2
222481
+ version: 3
222475
222482
  severity: high
222476
222483
  precedence: union
222477
222484
  ---
@@ -222745,7 +222752,10 @@ For each check:
222745
222752
  5. Keep the check \`id\` and \`version\` exactly as they appear in {{AGENT_CHECKS_PATH}}.
222746
222753
  6. Include concise evidence citations. Never include a detected secret value or unnecessary personal data.
222747
222754
 
222748
- A check with no verified issue must not produce a fabricated finding. If you cannot establish exploitability or affected authority, record the check as \`unresolved\` with a reason instead.
222755
+ A check with no verified issue must not produce a fabricated finding. Choose the status from what you were able to investigate, not from whether you can prove a negative:
222756
+
222757
+ - \`executed\`: you traced the paths the check directs you to and found no concrete issue. This includes code that follows the safe pattern the check or its \`docs_url\` describes. A candidate is ruled out when the code establishes the boundary the check asks about, even if you can't rule out hypothetical policies or requirements that the repository doesn't define.
222758
+ - \`unresolved\`: you couldn't complete the investigation, for example because required code was unreadable, out of scope, or outside the repository and you couldn't inspect it, or you found a specific candidate whose boundary you could neither establish nor show to be missing. Name the candidate's file and line, or the missing input, in the reason.
222749
222759
 
222750
222760
  ### 4. Write one findings document
222751
222761
 
@@ -222789,7 +222799,7 @@ Write a single JSON document that covers every check you ran:
222789
222799
  - \`status\` is one of:
222790
222800
  - \`executed\`: you investigated the check, whether or not it produced findings.
222791
222801
  - \`not_applicable\`: the capability the check covers is absent. It can't have findings.
222792
- - \`unresolved\`: you couldn't finish the check or prove the issue. An unresolved check didn't pass; never describe it as passing.
222802
+ - \`unresolved\`: you couldn't complete the investigation, or you named a specific candidate whose boundary is still unclear. An unresolved check didn't pass; never describe it as passing.
222793
222803
  - \`not_applicable\` and \`unresolved\` require a \`reason\` with a short \`code\` and a \`message\`.
222794
222804
  - Each finding needs \`file\`, \`line\` (1 or greater), \`message\`, and at least one \`evidence\` item with \`file\`, \`line\`, and \`quote\`.
222795
222805
  - Optional finding fields: \`snippet\`, \`confidence\` (\`high\`, \`medium\`, or \`low\`), \`reasoning\`, and \`suppression\` (\`{"justification": "..."}\`).
@@ -222860,6 +222870,36 @@ init_cjs_shims();
222860
222870
 
222861
222871
  // ../app/dist/cli/services/app-security-engine/rules/secret-rules.js
222862
222872
  init_cjs_shims();
222873
+
222874
+ // ../app/dist/cli/services/app-security-engine/scanners/git.js
222875
+ init_cjs_shims();
222876
+ var UNTRUSTED_REPOSITORY_PROTECTIONS = {
222877
+ // Reading the index runs the file-system monitor program. The empty value turns it off on every Git version;
222878
+ // older versions take `false` as the program's name.
222879
+ fileSystemMonitor: { args: ["-c", "core.fsmonitor="] },
222880
+ // Refuses a bare repository Git would otherwise find in the working directory, such as one committed as files.
222881
+ bareRepository: { args: ["-c", "safe.bareRepository=explicit"] },
222882
+ // A partial clone fetches a missing object on demand, running the repository's transport commands.
222883
+ lazyFetch: { env: { GIT_NO_LAZY_FETCH: "1" } },
222884
+ // An empty allow-list refuses every transport, for Git versions that predate GIT_NO_LAZY_FETCH.
222885
+ transports: { env: { GIT_ALLOW_PROTOCOL: "" } }
222886
+ }, protections = Object.values(UNTRUSTED_REPOSITORY_PROTECTIONS), protectionArguments = protections.flatMap((protection) => protection.args ?? []);
222887
+ function protectionEnvironment() {
222888
+ return Object.fromEntries(protections.flatMap((protection) => Object.entries(protection.env ?? {})));
222889
+ }
222890
+ async function runGit(directory, args) {
222891
+ try {
222892
+ let result = await captureOutputWithExitCode("git", [...protectionArguments, ...args], {
222893
+ cwd: directory,
222894
+ env: protectionEnvironment()
222895
+ });
222896
+ return { exitCode: result.exitCode, stdout: result.stdout };
222897
+ } catch {
222898
+ return;
222899
+ }
222900
+ }
222901
+
222902
+ // ../app/dist/cli/services/app-security-engine/rules/secret-rules.js
222863
222903
  var SHOPIFY_TOKEN_PATTERN = {
222864
222904
  regex: /shp(?:(?:at|ca|pa|ss)_[a-fA-F0-9]{16,}|(?:rt|sb|tka|ua)_[a-zA-Z0-9]{16,})/,
222865
222905
  name: "Shopify token",
@@ -222950,7 +222990,7 @@ function isEnvFile(path4) {
222950
222990
  return envFileBasename(path4) !== void 0;
222951
222991
  }
222952
222992
  async function gitTopLevel(cwd2) {
222953
- let result = await runGit(cwd2, ["rev-parse", "--show-toplevel"]);
222993
+ let result = await gitProbe(cwd2, ["rev-parse", "--show-toplevel"]);
222954
222994
  return result.exitCode === 0 && result.out !== "" ? result.out : void 0;
222955
222995
  }
222956
222996
  async function ignoredFileScanReason(file, gitIgnoreListing, appTopLevel) {
@@ -223031,16 +223071,12 @@ async function scanCommittedSecrets(secretEvidenceFiles, appRoot, gitIgnoreListi
223031
223071
  }
223032
223072
  return issues;
223033
223073
  }
223034
- async function runGit(cwd2, args) {
223035
- try {
223036
- let result = await captureOutputWithExitCode("git", args, { cwd: cwd2 });
223037
- return { exitCode: result.exitCode, out: result.stdout.trim() };
223038
- } catch {
223039
- return { exitCode: void 0, out: "" };
223040
- }
223074
+ async function gitProbe(cwd2, args) {
223075
+ let result = await runGit(cwd2, args);
223076
+ return { exitCode: result?.exitCode, out: result?.stdout.trim() ?? "" };
223041
223077
  }
223042
223078
  async function gitStatusFor(file) {
223043
- let run = async (args) => runGit(dirname(file.absolutePath), args), name = basename(file.absolutePath), inRepo = await run(["rev-parse", "--is-inside-work-tree"]);
223079
+ let run = async (args) => gitProbe(dirname(file.absolutePath), args), name = basename(file.absolutePath), inRepo = await run(["rev-parse", "--is-inside-work-tree"]);
223044
223080
  if (inRepo.exitCode !== 0 || inRepo.out !== "true")
223045
223081
  return {
223046
223082
  tracked: void 0,
@@ -223156,11 +223192,11 @@ var CHECK_DOCS_BASE_URL = "https://shopify.dev/docs/apps/build/security/app-secu
223156
223192
  },
223157
223193
  {
223158
223194
  id: "METAFIELD_OFFLINE_TOKEN",
223159
- title: "Metafield write using offline token context",
223195
+ title: "Metafield write in an unverified offline token context",
223160
223196
  severity: "high",
223161
223197
  points: -15,
223162
- description: "Detects metafield writes (metafieldsSet) in offline-token contexts. Online tokens provide user attribution and expiry.",
223163
- fix: "Use authenticate.admin(request) instead of unauthenticated.admin() for metafield writes.",
223198
+ description: "Investigates metafield and metaobject writes made with an offline Admin API context that no verified request for the same shop authorizes.",
223199
+ fix: "Make the write from a request verified with authenticate.admin(request), or derive the shop and values from a verified source.",
223164
223200
  guide: "https://shopify.dev/docs/apps/build/authentication-authorization/access-token-types/online-access-tokens",
223165
223201
  docsUrl: `${CHECK_DOCS_BASE_URL}/metafield-offline-token`
223166
223202
  },
@@ -223300,7 +223336,7 @@ var CHECK_DOCS_BASE_URL = "https://shopify.dev/docs/apps/build/security/app-secu
223300
223336
  title: "Embedded app frame-ancestors uses a wildcard",
223301
223337
  severity: "high",
223302
223338
  points: -12,
223303
- description: "Detects literal wildcard or clearly permissive frame-ancestors policies in embedded app code.",
223339
+ description: "Detects wildcard or static cross-shop frame-ancestors policies in embedded app code, whether written literally or built from variables.",
223304
223340
  fix: "Restrict frame-ancestors to Shopify Admin and the authenticated shop origin.",
223305
223341
  guide: "https://shopify.dev/docs/apps/build/security/set-up-iframe-protection",
223306
223342
  requires: "embedded_app",
@@ -223499,7 +223535,8 @@ Rules:
223499
223535
  - Record unresolved and not_applicable checks with a reason: { "code": "...", "message": "..." }.
223500
223536
  - A not_applicable check can't have findings.
223501
223537
  - An unsupported or unresolved check didn't pass. Never describe it as passing or complete.
223502
- - If you can't prove exploitability or affected authority, record the check as unresolved instead of reporting a finding.
223538
+ - If you can't prove exploitability or affected authority for a specific candidate, don't report a finding: record the check as unresolved and name the candidate in the reason.
223539
+ - If you investigated and the code establishes the boundary the check asks about, record the check as executed. An unprovable hypothetical is not a reason to leave a check unresolved.
223503
223540
  - Don't report things you couldn't confirm \u2014 uncertainty is not a finding.`, catalogEntry = (checkId) => {
223504
223541
  let entry = RULE_CATALOG.find((candidate) => candidate.id === checkId);
223505
223542
  if (!entry)
@@ -223815,7 +223852,7 @@ async function listGitIgnoredPaths(directory) {
223815
223852
  return listing.status !== "listed" && outputDebug(`app security check: git ignore listing skipped (${listing.status})`), listing;
223816
223853
  }
223817
223854
  async function runGitIgnoreListing(directory) {
223818
- let location2 = await runGit2(directory, ["rev-parse", "--is-inside-work-tree"]);
223855
+ let location2 = await runGit(directory, ["rev-parse", "--is-inside-work-tree"]);
223819
223856
  if (location2 === void 0)
223820
223857
  return { status: "failed" };
223821
223858
  if (location2.exitCode !== 0)
@@ -223824,7 +223861,7 @@ async function runGitIgnoreListing(directory) {
223824
223861
  return { status: "not-a-repository" };
223825
223862
  if (location2.stdout.trim() !== "true")
223826
223863
  return { status: "failed" };
223827
- let listed = await runGit2(directory, [
223864
+ let listed = await runGit(directory, [
223828
223865
  "ls-files",
223829
223866
  "-z",
223830
223867
  "--others",
@@ -223838,10 +223875,10 @@ async function runGitIgnoreListing(directory) {
223838
223875
  }
223839
223876
  async function isIgnoredByParentRepository(directory) {
223840
223877
  let parent = dirname(directory);
223841
- return parent === directory ? !1 : (await runGit2(parent, ["check-ignore", "--no-index", "-q", "--", basename(directory)]))?.exitCode === 0;
223878
+ return parent === directory ? !1 : (await runGit(parent, ["check-ignore", "--no-index", "-q", "--", basename(directory)]))?.exitCode === 0;
223842
223879
  }
223843
223880
  async function listTrackedFiles(directory) {
223844
- let listed = await runGit2(directory, ["ls-files", "-z", "--cached", "--", "."]);
223881
+ let listed = await runGit(directory, ["ls-files", "-z", "--cached", "--", "."]);
223845
223882
  if (!(listed === void 0 || listed.exitCode !== 0))
223846
223883
  return splitNullSeparated(listed.stdout);
223847
223884
  }
@@ -223856,14 +223893,6 @@ async function listNestedRepository(directory) {
223856
223893
  function splitNullSeparated(output) {
223857
223894
  return output.split("\0").filter((path4) => path4 !== "");
223858
223895
  }
223859
- async function runGit2(directory, args) {
223860
- try {
223861
- let result = await captureOutputWithExitCode("git", args, { cwd: directory });
223862
- return { exitCode: result.exitCode, stdout: result.stdout };
223863
- } catch {
223864
- return;
223865
- }
223866
- }
223867
223896
 
223868
223897
  // ../app/dist/cli/services/app-security-engine/rules/dependency-automation-rules.js
223869
223898
  init_cjs_shims();
@@ -224252,6 +224281,13 @@ function readRepositoryFile(absolutePath) {
224252
224281
  let located = configuredReader().explicitInputs.has(path4) ? explicitInputPath(path4) : containedRepositoryPath(path4), result = located.path ? readBoundedFile(located.path) : located.failure;
224253
224282
  return repositoryFileCache.set(path4, result), result.ok || recordSkippedFile(path4, result), result;
224254
224283
  }
224284
+ function readFileContainedBy(directory, path4) {
224285
+ let inspected = inspectRepositoryPath(directory, path4);
224286
+ if (inspected.status === "missing")
224287
+ return;
224288
+ let result = inspected.status === "file" ? readBoundedFile(inspected.path) : repositoryPathFailure(inspected.reason);
224289
+ return result.ok || recordSkippedFile(joinPath(directory, path4), result), result;
224290
+ }
224255
224291
  function readRepositoryText(absolutePath) {
224256
224292
  let result = readRepositoryFile(absolutePath);
224257
224293
  return result.ok ? result.content.toString() : void 0;
@@ -224354,15 +224390,14 @@ function isProbablyBinary(content) {
224354
224390
  return sample.length > 0 && suspiciousControlBytes / sample.length > 0.1;
224355
224391
  }
224356
224392
  function findSensitiveFiles(appRoot, repositoryFiles, selectedAppConfigPath) {
224357
- return repositoryFiles.filter(isSensitiveFile).filter((path4) => !LOCKFILE_MANAGERS.has(path4)).filter((path4) => !isValidFormatAppConfigurationFileName(basename(path4)) || path4 === selectedAppConfigPath).flatMap((path4) => {
224393
+ return repositoryFiles.filter(isSensitiveFile).filter((path4) => !LOCKFILE_MANAGERS.has(basename(path4))).filter((path4) => !isValidFormatAppConfigurationFileName(basename(path4)) || path4 === selectedAppConfigPath).flatMap((path4) => {
224358
224394
  let absolutePath = joinPath(appRoot, path4), result = readRepositoryFile(absolutePath);
224359
224395
  return result.ok ? isProbablyBinary(result.content) ? [] : [{ path: path4, absolutePath, ext: extname(path4), content: result.content.toString() }] : [{ path: path4, absolutePath, ext: extname(path4), content: void 0 }];
224360
224396
  });
224361
224397
  }
224362
- function nestedRepositoryReason(appRoot) {
224398
+ function dependencyAutomationRoot(appRoot) {
224363
224399
  let marker = findRepositoryMarker(appRoot);
224364
- if (marker.status !== "none")
224365
- return marker.status === "ambiguous" ? marker.reason : marker.directory === appRoot ? void 0 : "App root is nested below a parent Git repository";
224400
+ return marker.status === "ambiguous" ? { unresolvedReason: marker.reason } : { directory: marker.status === "found" ? marker.directory : appRoot };
224366
224401
  }
224367
224402
  function findDependencyAutomationInputs(appRoot, gatheredPaths) {
224368
224403
  let canonicalRoot;
@@ -224372,25 +224407,28 @@ function findDependencyAutomationInputs(appRoot, gatheredPaths) {
224372
224407
  } catch (error) {
224373
224408
  return { files: [], unresolvedReason: inspectErrorReason("app root", error) };
224374
224409
  }
224375
- let repositoryReason = nestedRepositoryReason(canonicalRoot);
224376
- if (repositoryReason)
224377
- return { files: [], unresolvedReason: repositoryReason };
224378
- let gathered = new Set(gatheredPaths), files = [], unresolvedReason;
224379
- for (let relative of DEPENDENCY_AUTOMATION_CONFIG_PATHS) {
224380
- if (!gathered.has(relative))
224381
- continue;
224382
- let absolutePath = joinPath(canonicalRoot, relative), result = readRepositoryFile(absolutePath);
224383
- if (!result.ok) {
224384
- unresolvedReason ??= result.reason === "too_large" ? `${relative} is too large to inspect` : result.detail ?? `Could not read ${relative}`;
224410
+ let configurationRoot = dependencyAutomationRoot(canonicalRoot);
224411
+ if ("unresolvedReason" in configurationRoot)
224412
+ return { files: [], unresolvedReason: configurationRoot.unresolvedReason };
224413
+ let repositoryRoot = configurationRoot.directory, { scanDirectories } = configuredReader(), isScanned = (absolutePath) => repositoryRoot === canonicalRoot || scanDirectories.some((directory) => isSubpath(directory, absolutePath)), gathered = new Set(gatheredPaths), files = [], unresolvedReason;
224414
+ for (let configurationPath of DEPENDENCY_AUTOMATION_CONFIG_PATHS) {
224415
+ let absolutePath = joinPath(repositoryRoot, configurationPath), relative = normalizePath(relativePath(canonicalRoot, absolutePath)), scanned = isScanned(absolutePath);
224416
+ if (scanned && !gathered.has(relative))
224385
224417
  continue;
224418
+ let result = scanned ? readRepositoryFile(absolutePath) : readFileContainedBy(repositoryRoot, configurationPath);
224419
+ if (result !== void 0) {
224420
+ if (!result.ok) {
224421
+ unresolvedReason ??= result.reason === "too_large" ? `${relative} is too large to inspect` : result.detail ?? `Could not read ${relative}`;
224422
+ continue;
224423
+ }
224424
+ files.push({
224425
+ path: relative,
224426
+ absolutePath,
224427
+ ext: extname(relative),
224428
+ content: result.content.toString()
224429
+ });
224430
+ break;
224386
224431
  }
224387
- files.push({
224388
- path: relative,
224389
- absolutePath,
224390
- ext: extname(relative),
224391
- content: result.content.toString()
224392
- });
224393
- break;
224394
224432
  }
224395
224433
  return files.length > 0 ? { files } : { files, ...unresolvedReason ? { unresolvedReason } : {} };
224396
224434
  }
@@ -224436,6 +224474,7 @@ function findManifests(appRoot, discoveredPaths) {
224436
224474
 
224437
224475
  // ../app/dist/cli/services/app-security-engine/capabilities/detect.js
224438
224476
  init_cjs_shims();
224477
+ var REACT_ROUTER_PACKAGE = "@shopify/shopify-app-react-router";
224439
224478
  function detectCapabilities(appToml, extensions, sourceFiles, appTomls = appToml ? [appToml] : []) {
224440
224479
  let themeExtension = extensions.some((extension) => extension.type === "theme"), appEmbed = extensions.some((extension) => extension.type === "theme" && hasAppEmbedBlock(extension)), embeddedApp = appTomls.some((configuration) => configuration.raw.embedded === !0), scriptTags = sourceFiles.some((file) => file.content ? /script[_-]?tags?|ScriptTag/i.test(file.content) : !1), storefrontMetafieldWrites = sourceFiles.some((file) => file.content ? /metafields?Set|metafields?\/.*(?:POST|PUT|create|update)|write.*metafield|metafield.*write/i.test(file.content) : !1), hasBackend = sourceFiles.some(detectRouteDefinitions);
224441
224480
  return {
@@ -224451,11 +224490,49 @@ function detectCapabilities(appToml, extensions, sourceFiles, appTomls = appToml
224451
224490
  checkout_extension: extensions.some((extension) => extension.type === "checkout_ui" || extension.type === "checkout_ui_extension")
224452
224491
  };
224453
224492
  }
224454
- function detectProject(manifests, extensions, candidates) {
224455
- let dependencyNames = new Set(manifests.flatMap((manifest) => [
224456
- ...Object.keys(manifest.dependencies),
224457
- ...Object.keys(manifest.devDependencies ?? {})
224458
- ])), candidatePaths = new Set(candidates.map((candidate) => candidate.path)), hasReactRouterPackage = dependencyNames.has("@shopify/shopify-app-react-router"), hasReactRouterStructure = [...candidatePaths].some((path4) => path4.startsWith("app/routes/")) && [...candidatePaths].some((path4) => /^app\/shopify\.server\.[cm]?[jt]sx?$/.test(path4)), reactRouter = hasReactRouterPackage && hasReactRouterStructure, themeExtensions = extensions.filter((extension) => extension.type === "theme"), themeExtension = themeExtensions.length > 0, themePaths = new Set(themeExtensions.flatMap((extension) => extension.files.map((file) => file.path))), hasSources = candidates.length > 0, surface;
224493
+ function detectReactRouterRoots(manifests, candidates, otherAppDirectories = []) {
224494
+ let declaringManifests = manifests.filter((manifest) => manifest.dependencies[REACT_ROUTER_PACKAGE] !== void 0 || manifest.devDependencies?.[REACT_ROUTER_PACKAGE] !== void 0);
224495
+ if (declaringManifests.length === 0)
224496
+ return [];
224497
+ let candidatePaths = candidates.map((candidate) => candidate.path);
224498
+ return [.../* @__PURE__ */ new Set([".", ...declaringManifests.map((manifest) => dirname(manifest.path))])].filter((root) => hasReactRouterStructure(root, candidatePaths)).filter((root) => !belongsToOtherApp(root, otherAppDirectories)).sort();
224499
+ }
224500
+ function isReactRouterServerPath(root, path4) {
224501
+ let pathInRoot = pathWithinRoot(root, path4);
224502
+ return pathInRoot !== void 0 && /^app\/shopify\.server\.[cm]?[jt]sx?$/.test(pathInRoot);
224503
+ }
224504
+ function isReactRouterSourcePath(path4, reactRouterRoots, otherAppDirectories) {
224505
+ let otherAppCode = otherAppCodeDirectories(otherAppDirectories).filter((directory) => !isInsideReactRouterRoot(directory, reactRouterRoots));
224506
+ return (!climbsOutOfDirectory(path4) || reactRouterRoots.some((root) => pathWithinRoot(root, path4) !== void 0)) && !otherAppCode.some((directory) => pathWithinRoot(directory, path4) !== void 0);
224507
+ }
224508
+ function isInsideReactRouterRoot(directory, reactRouterRoots) {
224509
+ return reactRouterRoots.some((root) => root === "." ? !climbsOutOfDirectory(directory) : directory === root || pathWithinRoot(root, directory) !== void 0);
224510
+ }
224511
+ function climbsOutOfDirectory(path4) {
224512
+ return path4 === ".." || path4.startsWith("../");
224513
+ }
224514
+ function pathWithinRoot(root, path4) {
224515
+ if (root === ".")
224516
+ return path4;
224517
+ if (!path4.startsWith(`${root}/`))
224518
+ return;
224519
+ let pathInRoot = path4.slice(root.length + 1);
224520
+ return climbsOutOfDirectory(pathInRoot) ? void 0 : pathInRoot;
224521
+ }
224522
+ function hasReactRouterStructure(root, paths) {
224523
+ return paths.some((path4) => pathWithinRoot(root, path4)?.startsWith("app/routes/")) && paths.some((path4) => isReactRouterServerPath(root, path4));
224524
+ }
224525
+ function belongsToOtherApp(root, otherAppDirectories) {
224526
+ return root === "." ? !1 : otherAppDirectories.includes(root) || otherAppCodeDirectories(otherAppDirectories).some((directory) => root === directory || pathWithinRoot(directory, root) !== void 0);
224527
+ }
224528
+ function otherAppCodeDirectories(otherAppDirectories) {
224529
+ return otherAppDirectories.flatMap((directory) => isAncestorOfAppDirectory(directory) ? [`${directory}/app`, `${directory}/web`] : [directory]);
224530
+ }
224531
+ function isAncestorOfAppDirectory(directory) {
224532
+ return directory.split("/").every((segment) => segment === "..");
224533
+ }
224534
+ function detectProject(extensions, candidates, reactRouterRoots) {
224535
+ let reactRouter = reactRouterRoots.length > 0, themeExtensions = extensions.filter((extension) => extension.type === "theme"), themeExtension = themeExtensions.length > 0, themePaths = new Set(themeExtensions.flatMap((extension) => extension.files.map((file) => file.path))), hasSources = candidates.length > 0, surface;
224459
224536
  reactRouter && themeExtension ? surface = "mixed" : reactRouter ? surface = "react_router" : themeExtension && candidates.some((candidate) => !themePaths.has(candidate.path)) ? surface = "mixed" : themeExtension ? surface = "theme_app_extension" : hasSources ? surface = "unknown" : surface = "config_only";
224460
224537
  let framework;
224461
224538
  reactRouter ? framework = "react_router" : surface === "config_only" || surface === "theme_app_extension" ? framework = "none" : surface === "mixed" ? framework = "mixed" : framework = "unknown";
@@ -225115,11 +225192,11 @@ function scanEolApiVersions(context, referenceDate = /* @__PURE__ */ new Date())
225115
225192
  ]);
225116
225193
  if (context.detection.framework !== "react_router")
225117
225194
  return configIssues;
225118
- let sourceIssues = context.sourceFiles.flatMap((file) => scanReactRouterApiVersion(file, referenceDate));
225195
+ let sourceIssues = context.sourceFiles.flatMap((file) => scanReactRouterApiVersion(file, context.reactRouterRoots, referenceDate));
225119
225196
  return [...configIssues, ...sourceIssues];
225120
225197
  }
225121
- function scanReactRouterApiVersion(file, referenceDate) {
225122
- if (!file.content || !/^app\/shopify\.server\.[cm]?[jt]sx?$/.test(file.path))
225198
+ function scanReactRouterApiVersion(file, reactRouterRoots, referenceDate) {
225199
+ if (!file.content || !reactRouterRoots.some((root) => isReactRouterServerPath(root, file.path)))
225123
225200
  return [];
225124
225201
  let content = maskStringsExceptVersions(stripComments4(file.content));
225125
225202
  return [
@@ -226064,7 +226141,7 @@ var JAVASCRIPT_EXTENSIONS6 = [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mt
226064
226141
  configRule(missingComplianceWebhooks),
226065
226142
  {
226066
226143
  id: "MISSING_DEPENDENCY_SECURITY_AUTOMATION",
226067
- version: 1,
226144
+ version: 2,
226068
226145
  lifecycle: "active",
226069
226146
  analysisMode: "structured_config",
226070
226147
  target: "dependency_automation",
@@ -226072,7 +226149,7 @@ var JAVASCRIPT_EXTENSIONS6 = [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mt
226072
226149
  },
226073
226150
  {
226074
226151
  id: "EOL_API_VERSION",
226075
- version: 1,
226152
+ version: 2,
226076
226153
  lifecycle: "active",
226077
226154
  analysisMode: "regex",
226078
226155
  target: "config_and_source",
@@ -226080,16 +226157,16 @@ var JAVASCRIPT_EXTENSIONS6 = [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mt
226080
226157
  runner: (context) => scanEolApiVersions(context)
226081
226158
  },
226082
226159
  {
226083
- ...jsCheck("EXPIRING_OFFLINE_TOKEN", (context) => scanExpiringOfflineTokens(context)),
226160
+ ...jsCheck("EXPIRING_OFFLINE_TOKEN", (context) => scanExpiringOfflineTokens(context), "source", 2),
226084
226161
  extensions: [...JAVASCRIPT_EXTENSIONS6, ".prisma"]
226085
226162
  },
226086
226163
  {
226087
- ...jsCheck("UNAUTHENTICATED_ENDPOINT", (context) => scanUnauthenticatedEndpoints(context.sourceFiles), "source", 2),
226164
+ ...jsCheck("UNAUTHENTICATED_ENDPOINT", (context) => scanUnauthenticatedEndpoints(context.sourceFiles), "source", 3),
226088
226165
  requires: "has_backend"
226089
226166
  },
226090
- jsCheck("REQUEST_CONTROLLED_ADMIN_CONTEXT", (context) => scanRequestControlledAdminContext(context.sourceFiles), "source", 3),
226167
+ jsCheck("REQUEST_CONTROLLED_ADMIN_CONTEXT", (context) => scanRequestControlledAdminContext(context.sourceFiles), "source", 4),
226091
226168
  {
226092
- ...configRule(deprecatedScriptTagScope),
226169
+ ...configRule(deprecatedScriptTagScope, 2),
226093
226170
  target: "config_and_source",
226094
226171
  analysisMode: "regex",
226095
226172
  extensions: JAVASCRIPT_EXTENSIONS6,
@@ -226101,14 +226178,14 @@ var JAVASCRIPT_EXTENSIONS6 = [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mt
226101
226178
  configRule(insecureWebhookUrl, 2),
226102
226179
  {
226103
226180
  id: "COMMITTED_SECRET",
226104
- version: 3,
226181
+ version: 4,
226105
226182
  lifecycle: "active",
226106
226183
  analysisMode: "regex",
226107
226184
  target: "secrets",
226108
226185
  runner: (context) => scanCommittedSecrets(context.sensitiveFiles, context.appRoot, context.gitIgnoreListing)
226109
226186
  },
226110
- jsCheck("CREDENTIAL_LOG_LEAKAGE", (context) => scanCredentialLogLeakage(context.sourceFiles)),
226111
- jsCheck("CREDENTIAL_BROWSER_LEAKAGE", (context) => scanCredentialBrowserLeakage(context.sourceFiles)),
226187
+ jsCheck("CREDENTIAL_LOG_LEAKAGE", (context) => scanCredentialLogLeakage(context.sourceFiles), "source", 2),
226188
+ jsCheck("CREDENTIAL_BROWSER_LEAKAGE", (context) => scanCredentialBrowserLeakage(context.sourceFiles), "source", 2),
226112
226189
  {
226113
226190
  id: "LIQUID_UNSAFE_RENDER",
226114
226191
  version: 1,
@@ -226120,16 +226197,16 @@ var JAVASCRIPT_EXTENSIONS6 = [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mt
226120
226197
  runner: (context) => liquidRunner(context, "LIQUID_UNSAFE_RENDER")
226121
226198
  },
226122
226199
  {
226123
- ...jsCheck("UNSAFE_INNERHTML", unsafeInnerHtmlRunner, "source_and_theme", 2),
226200
+ ...jsCheck("UNSAFE_INNERHTML", unsafeInnerHtmlRunner, "source_and_theme", 3),
226124
226201
  analysisMode: "regex",
226125
226202
  extensions: [...JAVASCRIPT_EXTENSIONS6, ".liquid", ".html"]
226126
226203
  },
226127
226204
  {
226128
- ...jsCheck("APP_PROXY_LIQUID_INJECTION", (context) => scanAppProxyLiquidInjection(context.sourceFiles), "source", 2),
226205
+ ...jsCheck("APP_PROXY_LIQUID_INJECTION", (context) => scanAppProxyLiquidInjection(context.sourceFiles), "source", 3),
226129
226206
  requires: "app_proxy"
226130
226207
  },
226131
226208
  {
226132
- ...jsCheck("STATIC_FRAME_ANCESTORS", (context) => scanStaticFrameAncestors(context.sourceFiles), "app_source"),
226209
+ ...jsCheck("STATIC_FRAME_ANCESTORS", (context) => scanStaticFrameAncestors(context.sourceFiles), "app_source", 2),
226133
226210
  requires: "embedded_app"
226134
226211
  }
226135
226212
  ];
@@ -226237,8 +226314,11 @@ function appSourceFiles(context) {
226237
226314
  let themePaths = new Set(themeFiles(context).map((file) => file.path));
226238
226315
  return context.sourceFiles.filter((file) => !themePaths.has(file.path));
226239
226316
  }
226317
+ function isReactRouterFilePath(path4, context) {
226318
+ return context.detection.framework !== "react_router" || isReactRouterSourcePath(path4, context.reactRouterRoots, context.otherAppDirectories);
226319
+ }
226240
226320
  function reactRouterFiles(context) {
226241
- return appSourceFiles(context);
226321
+ return appSourceFiles(context).filter((file) => isReactRouterFilePath(file.path, context));
226242
226322
  }
226243
226323
  function selectedFiles(definition, context) {
226244
226324
  let configurations = context.appTomls.map((toml) => relativePath(context.appRoot, toml.path).replace(/\\/g, "/"));
@@ -226345,8 +226425,8 @@ function manifestHasDependencies(manifest) {
226345
226425
  return Object.keys(manifest.dependencies).length > 0 || Object.keys(manifest.devDependencies ?? {}).length > 0;
226346
226426
  }
226347
226427
  function skippedInputsForCheck(definition, context, skippedFiles2) {
226348
- let themeDirectories = context.extensions.filter((extension) => extension.type === "theme").map((extension) => extension.path.replace(/shopify\.extension\.toml$/, "")), isThemePath = (path4) => themeDirectories.some((directory) => path4.startsWith(directory)) || path4.endsWith("shopify.extension.toml"), isSourcePath = (path4) => !isThemePath(path4) && !!definition.extensions?.some((extension) => path4.endsWith(extension)), isConfig = (path4) => /^shopify\.app(?:\.[^/]+)?\.toml$/.test(path4), isDependencyAutomationInput = (path4) => /(^|\/)package\.json$/.test(path4) || context.dependencyAutomation.files.some((file) => file.path === path4), isSecretInput = (file) => !file.detail?.includes("could not be parsed") && (context.sourceCandidates.some((candidate) => candidate.path === file.path) || context.sensitiveFiles.some((sensitiveFile) => sensitiveFile.path === file.path) || /(^|\/)(?:\.env(?:\.[^/]+)?|secrets\.json|credentials\.json)$/.test(file.path));
226349
- return skippedFiles2.filter((file) => definition.target === "config" ? isConfig(file.path) : definition.target === "dependency_automation" ? isDependencyAutomationInput(file.path) : definition.target === "config_and_source" ? isConfig(file.path) || isSourcePath(file.path) : definition.target === "source" || definition.target === "app_source" ? isSourcePath(file.path) : definition.target === "theme" ? isThemePath(file.path) : definition.target === "source_and_theme" ? isSourcePath(file.path) || isThemePath(file.path) : isSecretInput(file));
226428
+ let themeDirectories = context.extensions.filter((extension) => extension.type === "theme").map((extension) => extension.path.replace(/shopify\.extension\.toml$/, "")), isThemePath = (path4) => themeDirectories.some((directory) => path4.startsWith(directory)) || path4.endsWith("shopify.extension.toml"), isSourcePath = (path4) => !isThemePath(path4) && !!definition.extensions?.some((extension) => path4.endsWith(extension)), isReactRouterInput = (path4) => isSourcePath(path4) && isReactRouterFilePath(path4, context), isConfig = (path4) => /^shopify\.app(?:\.[^/]+)?\.toml$/.test(path4), isDependencyAutomationInput = (path4) => /(^|\/)package\.json$/.test(path4) || context.dependencyAutomation.files.some((file) => file.path === path4), isSecretInput = (file) => !file.detail?.includes("could not be parsed") && (context.sourceCandidates.some((candidate) => candidate.path === file.path) || context.sensitiveFiles.some((sensitiveFile) => sensitiveFile.path === file.path) || /(^|\/)(?:\.env(?:\.[^/]+)?|secrets\.json|credentials\.json)$/.test(file.path));
226429
+ return skippedFiles2.filter((file) => definition.target === "config" ? isConfig(file.path) : definition.target === "dependency_automation" ? isDependencyAutomationInput(file.path) : definition.target === "config_and_source" ? isConfig(file.path) || isReactRouterInput(file.path) : definition.target === "source" ? isReactRouterInput(file.path) : definition.target === "app_source" ? isSourcePath(file.path) : definition.target === "theme" ? isThemePath(file.path) : definition.target === "source_and_theme" ? isReactRouterInput(file.path) || isThemePath(file.path) : isSecretInput(file));
226350
226430
  }
226351
226431
  function skippedInputReason(definition, files) {
226352
226432
  return {
@@ -226385,7 +226465,7 @@ async function scan(input, options = {}) {
226385
226465
  scanDirectories,
226386
226466
  explicitInputs: new Set(appConfigFilePath ? [appConfigFilePath] : [])
226387
226467
  });
226388
- let selectedAppConfigPath = appConfigFilePath ? normalizePath(relativePath(appRoot, appConfigFilePath)) : void 0, appToml = appConfigFilePath ? loadAppToml(appConfigFilePath, appRoot) : null, appTomls = appToml ? [appToml] : [], { paths: repositoryFiles, ignoredScanDirectories, otherAppDirectories, listingStatus } = await gatherScanPaths(input, options), extensions = findExtensions(appRoot, repositoryFiles), sourceCandidates = findSourceCandidates(repositoryFiles), sourceFiles = findAppSourceFiles(appRoot, repositoryFiles), sensitiveFiles = findSensitiveFiles(appRoot, repositoryFiles, selectedAppConfigPath), manifestPaths = findManifestPaths(repositoryFiles), manifests = findManifests(appRoot, manifestPaths), dependencyAutomation = manifests.some(manifestHasDependencies) ? findDependencyAutomationInputs(appRoot, repositoryFiles) : { files: [] }, capabilities = detectCapabilities(appToml, extensions, sourceFiles, appTomls), detection = detectProject(manifests, extensions, sourceCandidates), context = {
226468
+ let selectedAppConfigPath = appConfigFilePath ? normalizePath(relativePath(appRoot, appConfigFilePath)) : void 0, appToml = appConfigFilePath ? loadAppToml(appConfigFilePath, appRoot) : null, appTomls = appToml ? [appToml] : [], { paths: repositoryFiles, ignoredScanDirectories, otherAppDirectories, listingStatus } = await gatherScanPaths(input, options), extensions = findExtensions(appRoot, repositoryFiles), sourceCandidates = findSourceCandidates(repositoryFiles), sourceFiles = findAppSourceFiles(appRoot, repositoryFiles), sensitiveFiles = findSensitiveFiles(appRoot, repositoryFiles, selectedAppConfigPath), manifestPaths = findManifestPaths(repositoryFiles), manifests = findManifests(appRoot, manifestPaths), dependencyAutomation = manifests.some(manifestHasDependencies) ? findDependencyAutomationInputs(appRoot, repositoryFiles) : { files: [] }, capabilities = detectCapabilities(appToml, extensions, sourceFiles, appTomls), relativeOtherAppDirectories = otherAppDirectories.map((directory) => normalizePath(relativePath(appRoot, directory))), reactRouterRoots = detectReactRouterRoots(manifests, sourceCandidates, relativeOtherAppDirectories), detection = detectProject(extensions, sourceCandidates, reactRouterRoots), context = {
226389
226469
  appRoot,
226390
226470
  appToml,
226391
226471
  appTomls,
@@ -226396,6 +226476,8 @@ async function scan(input, options = {}) {
226396
226476
  sensitiveFiles,
226397
226477
  capabilities,
226398
226478
  detection,
226479
+ reactRouterRoots,
226480
+ otherAppDirectories: relativeOtherAppDirectories,
226399
226481
  sourceCandidates,
226400
226482
  gitIgnoreListing: listingStatus
226401
226483
  }, issues = [], checksExecuted = [], requiredCheckIds = /* @__PURE__ */ new Set();
@@ -228914,7 +228996,7 @@ var SecurityCheck = class _SecurityCheck extends base_command_default {
228914
228996
  env: "SHOPIFY_FLAG_YES"
228915
228997
  }),
228916
228998
  "skip-instructions": import_core32.Flags.boolean({
228917
- description: "Don't offer to show coding-agent instructions.",
228999
+ description: "Don't offer the coding-agent instructions after the scan. Use it when you want only the deterministic scan results.",
228918
229000
  default: !1,
228919
229001
  exclusive: ["yes"],
228920
229002
  env: "SHOPIFY_FLAG_APP_SECURITY_SKIP_INSTRUCTIONS"