Skip to content

[4.9] Backport app security fixes (#8825, #8826, #8827, #8830, #8853) - #8855

Merged
dmerand merged 5 commits into
stable/4.9from
jplhomer/app-security-4.9-backports
Oct 9, 2026
Merged

dmerand merged 5 commits into
stable/4.9from
jplhomer/app-security-4.9-backports

Conversation

@jplhomer

@jplhomer jplhomer commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

WHY are these changes introduced?

Backports the merged shopify app security patch fixes from main to stable/4.9. Following Donald's guidance: "Patch backports can be grouped into one PR, ideally as separate commits + not squashed, with backrefs to the original so we don't lose auditing."

Please merge without squashing (keep one commit per original PR).

WHAT is this pull request doing?

One commit per original PR. The first four are in main merge order. Each is git cherry-pick -x -m 1 <merge commit>, so each carries a (cherry picked from commit …) trailer and a Backport of … link. #8827 was still in the merge queue, so its commit is the PR's net diff (merge base d4bec95 to approved head 92afbdf) as one commit, with no cherry-pick trailer.

Original PR Merge commit on main Backport commit
#8825 Skip lockfiles in every app security check scan directory 85a2e29 5a175f4
#8826 Check dependency automation at the repository root for nested apps 3e5e60c efc1987
#8830 Stop app security agent checks leaving the React Router template unresolved d4bec95 a7ec921
#8853 Port app security flag descriptions from the shopify.dev docs 87b806e 5e9df02
#8827 Detect React Router apps outside the app directory in app security check not merged yet: from approved PR head 92afbdf (in the merge queue) de830e0

All four picked cleanly onto stable/4.9, which already has #8842 (the 4.9 port of #8841). After the first four, packages/app/src/cli/services/app-security-engine and packages/app/src/cli/commands/app/security are identical to main (87b806e). With #8827 they match main merged with 92afbdf, so check versions should match main once #8827 lands.

Changesets (reviewer decision): each original PR's changeset is kept, so the next 4.9 patch gets changelog entries and a Version Packages run. Note that #8842's changeset was dropped on stable/4.9 in c06045a ("Remove app security changeset from 4.9 backport", no reason given). If these should also ship without changelog entries, drop the five .changeset/app-security-*.md files before merging.

How to manually test your changes?

  1. Check out this branch and build the CLI.
  2. In a monorepo with a root lockfile and an app in a subdirectory, run shopify app security check --path <app dir>. The secret check should not report as unresolved because of the root lockfile, and dependency automation config at the Git root should be detected.
  3. Run shopify app security check --help and confirm the updated --blocking and --skip-instructions descriptions.

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

@jplhomer
jplhomer requested review from a team as code owners October 9, 2026 15:07
@github-actions github-actions Bot added shopify.dev preview Area: @shopify/cli @shopify/cli package issues labels Oct 9, 2026
…ity check (#8827)

Backport of #8827 (from approved PR head 92afbdf; it was in the merge queue when backported)
@jplhomer jplhomer changed the title [4.9] Backport app security fixes (#8825, #8826, #8830, #8853) [4.9] Backport app security fixes (#8825, #8826, #8827, #8830, #8853) Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

We found no new type declarations in this PR

Existing type declarations

packages/cli-kit/dist/private/node/constants.d.ts
@@ -8,7 +8,6 @@ export declare const environmentVariables: {
     env: string;
     noAnalytics: string;
     optOutInstrumentation: string;
-    organizationAutomationToken: string;
     appAutomationToken: string;
     partnersToken: string;
     runAsUser: string;
packages/cli-kit/dist/private/node/json-error.d.ts
@@ -1,3 +1,16 @@
+interface FatalErrorLike {
+    type?: number;
+    message?: unknown;
+    formattedMessage?: unknown;
+    tryMessage?: unknown;
+    nextSteps?: unknown;
+    customSections?: unknown;
+    stack?: unknown;
+    command?: unknown;
+    args?: unknown;
+    details?: unknown;
+    code?: unknown;
+}
 /**
  * Writes the public JSON representation of a fatal error to stdout.
  *
@@ -6,4 +19,5 @@
  *
  * @param error - Fatal error to serialize.
  */
-export declare function renderFatalErrorAsJson(error: unknown): void;
\ No newline at end of file
+export declare function renderFatalErrorAsJson(error: FatalErrorLike): void;
+export {};
\ No newline at end of file
packages/cli-kit/dist/public/common/version.d.ts
@@ -1 +1 @@
-export declare const CLI_KIT_VERSION = "4.9.0";
\ No newline at end of file
+export declare const CLI_KIT_VERSION = "4.9.2";
\ No newline at end of file
packages/cli-kit/dist/public/node/environment.d.ts
@@ -10,13 +10,10 @@
  */
 export declare function getEnvironmentVariables(): NodeJS.ProcessEnv;
 /**
- * Returns the automation token the CLI authenticates with, from the first of these variables that is set:
- * SHOPIFY_ORGANIZATION_AUTOMATION_TOKEN, SHOPIFY_APP_AUTOMATION_TOKEN, or the deprecated SHOPIFY_CLI_PARTNERS_TOKEN.
+ * Returns the value of the SHOPIFY_APP_AUTOMATION_TOKEN environment variable,
+ * falling back to the deprecated SHOPIFY_CLI_PARTNERS_TOKEN.
  *
- * Returns undefined when the variables can't be used (an empty value, or the organization variable set alongside
- * another one). Callers then fall back to the login flow, which reports the problem instead of logging in.
- *
- * @returns The automation token, or undefined if there is no usable one.
+ * @returns The app automation token value, or undefined if neither env var is set.
  */
 export declare function getAppAutomationToken(): string | undefined;
 /**
packages/cli-kit/dist/public/node/session.d.ts
@@ -128,35 +128,14 @@ export declare function ensureAuthenticatedAdmin(store: string, scopes?: AdminAP
  * @returns The access token and store.
  */
 export declare function ensureAuthenticatedThemes(store: string, password: string | undefined, scopes?: AdminAPIScope[], options?: EnsureAuthenticatedAdditionalOptions): Promise<AdminSession>;
-/**
- * Options for `ensureAuthenticatedBusinessPlatform`.
- */
-export interface EnsureAuthenticatedBusinessPlatformOptions extends EnsureAuthenticatedAdditionalOptions {
-    /**
-     * Authenticate with the automation token set in the environment, when there is one, instead of the
-     * logged-in user. Only commands that support automation tokens opt in; other callers, such as
-     * Hydrogen's login, keep using the user's session.
-     */
-    allowAutomationToken?: boolean;
-}
 /**
  * Ensure that we have a valid session to access the Business Platform API.
  *
- * @param scopes - Optional array of extra scopes to authenticate with. Ignored when an automation token is used.
+ * @param scopes - Optional array of extra scopes to authenticate with.
  * @param options - Optional extra options to use.
  * @returns The access token for the Business Platform API.
  */
-export declare function ensureAuthenticatedBusinessPlatform(scopes?: BusinessPlatformScope[], options?: EnsureAuthenticatedBusinessPlatformOptions): Promise<string>;
-/**
- * Fails when SHOPIFY_ORGANIZATION_AUTOMATION_TOKEN is set, for commands that can't run with that token.
- *
- * Organization automation tokens can't log in as a user or call a store's Admin API, so commands that need either
- * refuse to run instead of quietly using the person's own login. `ensureAuthenticated` runs this check before any
- * login. Commands that run on a login saved by `shopify store auth` call it before loading that login.
- *
- * @throws AbortError when SHOPIFY_ORGANIZATION_AUTOMATION_TOKEN is set.
- */
-export declare function ensureNoOrganizationAutomationToken(): void;
+export declare function ensureAuthenticatedBusinessPlatform(scopes?: BusinessPlatformScope[], options?: EnsureAuthenticatedAdditionalOptions): Promise<string>;
 /**
  * Logout from Shopify.
  *
packages/cli-kit/dist/public/node/upgrade.d.ts
@@ -1,5 +1,4 @@
 import { getAutoUpgradeEnabled, setAutoUpgradeEnabled } from '../../private/node/conf-store.js';
-import type { UpgradeResult } from './upgrade/types.js';
 export { getAutoUpgradeEnabled, setAutoUpgradeEnabled };
 /**
  * Utility function for generating an install command for the user to run
@@ -28,13 +27,6 @@ export interface RunCLIUpgradeOptions {
  * @throws AbortError if the package manager or command cannot be determined.
  */
 export declare function runCLIUpgrade(options?: RunCLIUpgradeOptions): Promise<void>;
-/**
- * Upgrades the CLI and returns the outcome independently of final presentation.
- *
- * @param options - Whether the upgrade was triggered automatically.
- * @returns The verified global version, local dependency update, or skip reason.
- */
-export declare function upgradeCLI(options?: RunCLIUpgradeOptions): Promise<UpgradeResult>;
 /**
  * Returns the version to auto-upgrade to, or undefined if auto-upgrade should be skipped.
  * Auto-upgrade is enabled by default and can be disabled via `setAutoUpgradeEnabled(false)`.
packages/cli-kit/dist/private/node/session/exchange.d.ts
@@ -45,10 +45,9 @@ export declare function exchangeAppAutomationTokenForAppManagementAccessToken(to
 /**
  * Given a custom app automation token passed as ENV variable, request a valid Business Platform API token.
  * @param token - The app automation token passed as ENV variable `SHOPIFY_APP_AUTOMATION_TOKEN`
- * @param scopes - The scopes to request. An empty list makes Identity issue every Business Platform scope the token holds.
  * @returns An instance with the application access tokens.
  */
-export declare function exchangeAppAutomationTokenForBusinessPlatformAccessToken(token: string, scopes?: string[]): Promise<{
+export declare function exchangeAppAutomationTokenForBusinessPlatformAccessToken(token: string): Promise<{
     accessToken: string;
     userId: string;
 }>;
packages/cli-kit/dist/public/node/error/schema.d.ts
@@ -30,24 +30,24 @@ export declare const JsonAbortErrorSchema: zod.ZodObject<{
     type: "abort";
     message: string;
     code?: string | undefined;
-    tryMessage?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }, {
     type: "abort";
     message: string;
     code?: string | undefined;
-    tryMessage?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }>;
 export declare const JsonBugErrorSchema: zod.ZodObject<{
     stack: zod.ZodOptional<zod.ZodString>;
@@ -71,7 +71,6 @@ export declare const JsonBugErrorSchema: zod.ZodObject<{
     type: "bug";
     message: string;
     code?: string | undefined;
-    tryMessage?: string | undefined;
     stack?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
@@ -79,11 +78,11 @@ export declare const JsonBugErrorSchema: zod.ZodObject<{
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }, {
     type: "bug";
     message: string;
     code?: string | undefined;
-    tryMessage?: string | undefined;
     stack?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
@@ -91,6 +90,7 @@ export declare const JsonBugErrorSchema: zod.ZodObject<{
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }>;
 export declare const JsonExternalErrorSchema: zod.ZodObject<{
     command: zod.ZodString;
@@ -117,26 +117,26 @@ export declare const JsonExternalErrorSchema: zod.ZodObject<{
     command: string;
     args: string[];
     code?: string | undefined;
-    tryMessage?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }, {
     type: "external";
     message: string;
     command: string;
     args: string[];
     code?: string | undefined;
-    tryMessage?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }>;
 export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
     message: zod.ZodString;
@@ -159,24 +159,24 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
     type: "abort";
     message: string;
     code?: string | undefined;
-    tryMessage?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }, {
     type: "abort";
     message: string;
     code?: string | undefined;
-    tryMessage?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }>, zod.ZodObject<{
     stack: zod.ZodOptional<zod.ZodString>;
     message: zod.ZodString;
@@ -199,7 +199,6 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
     type: "bug";
     message: string;
     code?: string | undefined;
-    tryMessage?: string | undefined;
     stack?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
@@ -207,11 +206,11 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }, {
     type: "bug";
     message: string;
     code?: string | undefined;
-    tryMessage?: string | undefined;
     stack?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
@@ -219,6 +218,7 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }>, zod.ZodObject<{
     command: zod.ZodString;
     args: zod.ZodArray<zod.ZodString, "many">;
@@ -244,26 +244,26 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
     command: string;
     args: string[];
     code?: string | undefined;
-    tryMessage?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }, {
     type: "external";
     message: string;
     command: string;
     args: string[];
     code?: string | undefined;
-    tryMessage?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
         title?: string | undefined;
     }[] | undefined;
     details?: unknown;
+    tryMessage?: string | undefined;
 }>]>;
 export declare const jsonErrorOutputSchema: import("../json-output-schema.js").JsonOutputSchema<zod.ZodObject<{
     error: zod.ZodUnion<[zod.ZodObject<{
@@ -287,24 +287,24 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
         type: "abort";
         message: string;
         code?: string | undefined;
-        tryMessage?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     }, {
         type: "abort";
         message: string;
         code?: string | undefined;
-        tryMessage?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     }>, zod.ZodObject<{
         stack: zod.ZodOptional<zod.ZodString>;
         message: zod.ZodString;
@@ -327,7 +327,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
         type: "bug";
         message: string;
         code?: string | undefined;
-        tryMessage?: string | undefined;
         stack?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
@@ -335,11 +334,11 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     }, {
         type: "bug";
         message: string;
         code?: string | undefined;
-        tryMessage?: string | undefined;
         stack?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
@@ -347,6 +346,7 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     }>, zod.ZodObject<{
         command: zod.ZodString;
         args: zod.ZodArray<zod.ZodString, "many">;
@@ -372,44 +372,43 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
         command: string;
         args: string[];
         code?: string | undefined;
-        tryMessage?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     }, {
         type: "external";
         message: string;
         command: string;
         args: string[];
         code?: string | undefined;
-        tryMessage?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     }>]>;
 }, "strict", zod.ZodTypeAny, {
     error: {
         type: "abort";
         message: string;
         code?: string | undefined;
-        tryMessage?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     } | {
         type: "bug";
         message: string;
         code?: string | undefined;
-        tryMessage?: string | undefined;
         stack?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
@@ -417,37 +416,37 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     } | {
         type: "external";
         message: string;
         command: string;
         args: string[];
         code?: string | undefined;
-        tryMessage?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     };
 }, {
     error: {
         type: "abort";
         message: string;
         code?: string | undefined;
-        tryMessage?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     } | {
         type: "bug";
         message: string;
         code?: string | undefined;
-        tryMessage?: string | undefined;
         stack?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
@@ -455,18 +454,19 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     } | {
         type: "external";
         message: string;
         command: string;
         args: string[];
         code?: string | undefined;
-        tryMessage?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
             title?: string | undefined;
         }[] | undefined;
         details?: unknown;
+        tryMessage?: string | undefined;
     };
 }>>;
\ No newline at end of file

@dmerand
dmerand merged commit e2631d3 into stable/4.9 Oct 9, 2026
76 of 79 checks passed
@dmerand
dmerand deleted the jplhomer/app-security-4.9-backports branch October 9, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants