Skip to content

Validate --client-id in App Security check, record and review - #8764

Merged
jek merged 4 commits into
mainfrom
app-security/client-id-validate
Oct 6, 2026
Merged

jek merged 4 commits into
mainfrom
app-security/client-id-validate

Conversation

@jek

@jek jek commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

WHY are these changes introduced?

shopify app security check, record and review accept any --client-id value. A mistyped ID isn't caught: check writes results under it, record records findings under it, and review reports on results that don't belong to any app. app deploy catches the same mistake by looking the ID up first.

WHAT is this pull request doing?

check (including --list-files), record and review look up the --client-id value in the user's account, using appFromIdentifiers, as deploy does. An unknown ID aborts with deploy's account-aware message before anything is gathered, scanned, written or read, and before the "scan without app configuration?" prompt.

  • Only the --client-id value is checked. The TOML's own client_id isn't, and a client ID picked from the app list already comes from the API.
  • clean and instructions don't check it, so results saved under a mistyped ID can still be removed. The commands share one selection resolver, so the lookup is an explicit opt-in (validateClientIdFlag) and the lookup itself is an injected dependency (lookUpApp). Tests don't need the network or a login.
  • The local checks still run first: the --path directory, the app configuration, and --without-app-config requiring --client-id.
  • Passing --client-id now requires a login. The help for the three commands says so, and --list-files now says the ID is still checked.

After

Invalid ids are rejected

image

Valid ids are accepted

image image image image (expected validation error)

How to manually test your changes?

From a logged-in CLI, with APP an app directory, GOOD a client ID from your account and BAD a made-up one:

  1. pnpm shopify app security check --path $APP --client-id $BAD --skip-instructions fails with "No app with client ID … found", and $APP/.shopify/app-security/ has no $BAD directory. Repeat with --list-files instead of --skip-instructions, then with review, and with echo '{}' | pnpm shopify app security record: each fails the same way.
  2. The same commands with --client-id $GOOD proceed. With {} on stdin, record fails on document validation instead.
  3. pnpm shopify app security check --path $APP --skip-instructions, without --client-id, makes no lookup.
  4. mkdir -p $APP/.shopify/app-security/$BAD && pnpm shopify app security clean --path $APP --client-id $BAD removes the directory without a lookup.
  5. pnpm shopify app security check --path <directory with no TOML> --client-id $BAD fails without asking whether to scan without app configuration first.

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

@github-actions github-actions Bot added the no-changelog This PR doesn't include a changeset entry. Is an internal only change not relevant to end users. label Oct 5, 2026
@jek
jek marked this pull request as ready for review October 5, 2026 20:57
@jek
jek requested review from a team as code owners October 5, 2026 20:57
@jek
jek requested a review from jplhomer October 5, 2026 20:57
@jek
jek force-pushed the app-security/client-id-validate branch from f1ca397 to c44a3ca Compare October 5, 2026 22:45
Use \`--exclude\` to skip more paths. Each value is a glob that is matched against the path relative to the working directory, so a path above it starts with \`../\`, and a name at any depth needs \`**/\`, for example \`--exclude '**/generated'\`. Repeat the flag to add globs. An exclusion can't remove the selected app configuration file. Quote each value so your shell doesn't expand \`*\`. The coding-agent instructions this check offers repeat the globs. Other \`app security\` commands don't take \`--exclude\` or \`--no-git-ignore\`, so pass the same flags each time you run the check.

Use \`--list-files\` to check the scope before scanning: it prints the files the check would gather, one path per line and relative to the app directory (\`{"files": [...]}\` with \`--json\`), and then stops. It writes no results and never prompts. \`--client-id\` is accepted but has no effect on the list.
Use \`--list-files\` to check the scope before scanning: it prints the files the check would gather, one path per line and relative to the app directory (\`{"files": [...]}\` with \`--json\`), and then stops. It writes no results and never prompts. \`--client-id\` is still checked, but doesn't change the list.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking suggestion: qualify never prompts now that --client-id can start login.

The compiled-CLI smoke entered device login and requested a browser launch with --list-files and --json, including redirected execution outside CI and a synthetic expired session. --no-input and CI correctly prevented the browser request and polling. The login requirement is intentional, and the CLI treats JSON output and no-input as separate controls.

Could the help say that these modes skip App Security's selection/instructions prompts, but authentication may still require user action, and show --no-input for automation? The later JSON output never prompts sentence needs the same qualification. If the stronger no-prompt promise is intended, it needs to reach authentication too.

The smoke kept the real resolver, lookup and auth code. Transport responses were synthetic and the browser launch was blocked; no live account or completed login was tested.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will address this in #8771

}),
pickClientId: async (appDirectory) => (await fetchOrCreateOrganizationApp(appCreationDefaults(appDirectory))).apiKey,
lookUpApp: async (clientId) => {
await appFromIdentifiers({apiKey: clientId})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking suggestion: omit the deploy-only --reset advice for App Security callers.

The shared missing-app error says Pass --reset to your command to create a new app. In the compiled-CLI smoke, check, record and review all displayed that step, but replaying it on each command failed with Nonexistent flag: --reset (exit 2). Saved findings stayed unchanged.

Could we keep the account/permission guidance but omit or replace the reset step for these callers, while preserving it for commands that support it? Please add coverage for the real error's recovery text, not only a mocked one-line AbortError.

This help smoke replaced only the leaf platform-client lookup/account methods. The shared error formatter and command parsers were real; real missing-app API responses were not tested.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5da30fe. appFromIdentifiers now takes offerReset (default true, so other callers are unchanged), and the app security lookup passes false: the permission and shopify auth login steps stay, the --reset step is gone. Covered against the real error text: context.test.ts builds the error with offerReset: false, and app-security-selection.test.ts runs the real appFromIdentifiers behind the default lookup with a client that finds no app, and asserts the next steps mention shopify auth login and not --reset.

Separately: app init and app config link also call appFromIdentifiers and keep the --reset step. I haven't checked whether they take --reset, so I left them alone here.

@jek
jek requested a review from dmerand October 6, 2026 02:52
@jek
jek added this pull request to stack #8772 October 6, 2026 03:04
jek added 4 commits October 6, 2026 07:02
Look up the --client-id value in the user's account, as deploy does, before
gathering, scanning, writing or reading results, and before the
no-app-configuration prompt. The TOML's client_id and the picker's client ID
aren't looked up. clean and instructions don't opt in, so results saved under
a mistyped client ID can still be removed.
--client-id= passed an empty string, which the truthiness check skipped, so check --list-files listed files without the lookup. The value was passed, so it's looked up like any other.
The shared missing-app error suggests passing --reset, which the app security commands don't take. appFromIdentifiers takes offerReset, true by default, and the app security lookup passes false; the account and login steps stay.
@jek
jek force-pushed the app-security/client-id-validate branch from e441552 to 764ecc1 Compare October 6, 2026 14:02
@jek
jek added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit e3b4c4f Oct 6, 2026
30 checks passed
@jek
jek deleted the app-security/client-id-validate branch October 6, 2026 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants