Skip to content

Warn when App Security scans another app's files - #8763

Merged
jek merged 8 commits into
mainfrom
app-security/nested-app-configs
Oct 6, 2026
Merged

jek merged 8 commits into
mainfrom
app-security/nested-app-configs

Conversation

@jek

@jek jek commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

WHY are these changes introduced?

Since #8738, file gathering no longer skips a directory that holds another shopify.app*.toml, so a nested app's files are scanned as part of this app, and nothing tells the user. Secret scanning also compared app configuration files by name only. A nested app's shopify.app.toml was scanned when the selected file had the same name, while its shopify.app.staging.toml wasn't.

Adding a warning that suggests --exclude exposed two gaps in --exclude:

  • It never removed a scan directory itself. --include-dir ../backend --exclude ../backend still scanned ../backend, and --exclude ../shared didn't remove --include-dir ../shared/sdk.
  • Patterns were matched as typed against the shortest relative path. From inside app, ./src, src/, ../app/src, . and ../app all excluded nothing.

WHAT is this pull request doing?

  • Scans only the selected app configuration file for secrets, matched by its path relative to the app directory.
  • Warns about each directory other than the app directory that holds a gathered app configuration file, and suggests the --exclude that leaves it out. The suggestion is relative to the real working directory, escaped as a glob and quoted for the user's shell. The warning shows on a scan and with --list-files. Once you exclude the directory, the warning stops. A nested app whose configuration file is gitignored doesn't trigger it.
  • --exclude now removes a whole scan directory when it, or any directory above it up to the working directory, matches. Adding a directory can't undo an exclusion. This includes the app directory when you run from above it, for example --exclude apps/web from the repository root. The selected app configuration file is still scanned. An excluded directory doesn't get the "ignored by Git" warning.
  • Resolves the literal start of each --exclude pattern, up to its first glob segment, against the working directory. ./src and src/ mean src, and from inside app, so does ../app/src. . means the working directory itself. The rest of the pattern is kept as typed, so **/generated and ../backend/** behave as before. An empty pattern is ignored.
  • Fixes the findExtensions docstring: extensions in a nested app or an --include-dir directory count as this app's.

How to manually test your changes?

mkdir -p /tmp/exclude-demo/app/src /tmp/exclude-demo/app/apps/child /tmp/exclude-demo/backend
cat > /tmp/exclude-demo/app/shopify.app.toml <<'TOML'
client_id = "parent"
name = "Parent"
application_url = "https://example.com"
embedded = true

[auth]
redirect_urls = ["https://example.com/auth"]

[webhooks]
api_version = "2025-07"
TOML
printf 'name = "Child"\n' > /tmp/exclude-demo/app/apps/child/shopify.app.toml
printf 'export const child = true\n' > /tmp/exclude-demo/app/apps/child/index.ts
printf 'export const a = true\n' > /tmp/exclude-demo/app/src/a.ts
printf 'name = "Backend"\n' > /tmp/exclude-demo/backend/shopify.app.toml
printf 'export const server = true\n' > /tmp/exclude-demo/backend/server.ts
cd /tmp/exclude-demo/app
pnpm --dir <path to this checkout> shopify app security check --path /tmp/exclude-demo/app --list-files --include-dir ../backend

The output warns about ../backend and apps/child, and suggests --exclude '../backend' and --exclude 'apps/child'. Rerun with --exclude ../backend --exclude apps/child --exclude ./src: the warnings go away, and only shopify.app.toml and the .shopify/ files are listed.

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

@jek
jek marked this pull request as ready for review October 5, 2026 21:06
@jek
jek requested a review from a team as a code owner October 5, 2026 21:06
@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 added this pull request to stack #8768 October 5, 2026 22:01
Secret scanning kept any app configuration file named like the selected one,
so a nested app's shopify.app.toml was scanned while its
shopify.app.staging.toml wasn't. Compare the path relative to the app
directory instead, so only the selected file is scanned.

Gathering no longer skips a directory that holds another app configuration
file, so its files are scanned as this app's. Warn about each such directory
and suggest the --exclude that leaves it out.
@jek
jek force-pushed the app-security/nested-app-configs branch from 472e640 to 51e76cc Compare October 5, 2026 22:50
jek added 4 commits October 5, 2026 19:46
The directory name comes from the repository, so a name such as
$(touch PWNED) ran when the user pasted the suggestion into a shell. Quote
it the way the generated commands are quoted, for the user's shell.
--exclude globs are matched from the real working directory, but the
warnings used the working directory as given. When that path went through a
symbolic link, the suggested --exclude didn't match the directory it named.
--exclude takes globs, so a directory name with glob syntax didn't match
itself, and a leading ! negated the pattern and excluded everything else.
Escape the name so the suggestion matches only that directory.
Cover a directory name that is shell and glob syntax, a working directory
reached through a symbolic link, the warning with --list-files, and an
--exclude built from the suggestion removing only that directory. Pin that a
directory whose app configuration file Git ignores isn't reported.

@gonzaloriestra gonzaloriestra left a comment

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.

I added one comment, but not-blocking

// The directory name comes from the repository, so it's quoted before the user can paste it into a shell.
body: [
'Use',
{command: `--exclude ${quoteShellArgument(literalGlobPattern(path), shellForPlatform())}`},

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.

With --include-dir ../backend, the warning suggests --exclude '../backend'. But the walker checks exclusions against entries inside each scan directory, so this pattern never excludes the included root. Its files remain scanned and the warning persists.

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.

Good catch. The walker never tested a scan directory itself, or the directories above it, against --exclude, so --exclude ../shared didn't remove --include-dir ../shared/sdk either. Fixed in 53a0db3: the suggested --exclude '../backend' now removes ../backend, and the warning goes away. While there, 9a5ef30 resolves each pattern's literal start against the working directory, so ./src, src/ and ../app/src (from inside app) now work too.

jek added 3 commits October 6, 2026 06:11
The walker only tested the entries inside a scan directory, so an --exclude
that matched an --include-dir, or a directory above it, removed nothing. Test
the scan directory and its ancestors up to the working directory too, so
adding a directory can't undo an exclusion, and skip the ignored-scan-directory
warning for a directory that gathers nothing.
…tory

Patterns were matched as typed against the shortest path relative to the
working directory, so ./src, src/ and, from inside app, ../app/src excluded
nothing, and neither did . or ../app. Resolve each pattern up to its first
glob segment the same way paths are, and keep the rest as typed. Drop an
empty pattern, which would otherwise match the working directory.
node:path's join wrote the pattern with backslashes on Windows, which
--exclude reads as glob escapes, so the pattern was kept as typed.
@jek
jek added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit a9bc823 Oct 6, 2026
30 checks passed
@jek
jek deleted the app-security/nested-app-configs branch October 6, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants