Skip to content

Stop app security agent checks leaving the React Router template unresolved - #8830

Merged
jplhomer merged 5 commits into
mainfrom
jplhomer/app-security-unresolved-calibration
Oct 9, 2026
Merged

jplhomer merged 5 commits into
mainfrom
jplhomer/app-security-unresolved-calibration

Conversation

@jplhomer

@jplhomer jplhomer commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

WHY are these changes introduced?

A fresh shopify app init React Router app (main-cli template) can come back from shopify app security review with a warning: "3 checks unresolved" (METAFIELD_OFFLINE_TOKEN, MISSING_AUTHORIZATION_CHECK, STATIC_FRAME_ANCESTORS). Context: https://shopify.slack.com/archives/C0BT7EGQZ32/p1791391260952729

The template code is safe for all three, and the result depends on the agent. Against the unmodified template on main, codex recorded all three as unresolved in 5 of 7 passes, while claude recorded them clean in 3 of 3. The CLI never calls a model, so these statuses come only from the agent. Two things in the CLI's text push agents there:

  • The agent instructions say to record unresolved whenever the agent can't prove exploitability or affected authority. Agents read that as "unresolved unless I can prove a negative", even after they've verified the boundary.
  • The three prompts don't describe the React Router SDK defaults. METAFIELD_OFFLINE_TOKEN also contradicts itself: its catalog entry flags writes "in offline-token contexts", but its fix is authenticate.admin(request), which uses an offline session by default (useOnlineTokens ?? false). STATIC_FRAME_ANCESTORS's agent result overrides its deterministic pass (prefer-agent).

WHAT is this pull request doing?

  • Executed versus unresolved (INSTRUCTIONS.md, checks/index.ts): record executed when the code establishes the boundary a check asks about. Use unresolved only when the investigation couldn't be completed, or for a named candidate the agent could neither settle nor show to be broken.
  • METAFIELD_OFFLINE_TOKEN v2: decides on token provenance. Writes that no verified request for the shop authorizes are still reported, for example unauthenticated.admin(shop) or a stored offline session reached from unverified input. An offline session from authenticate.admin(request) isn't a finding by itself. It's still reported when the write bypasses an app-defined per-user permission, or when request-controlled values reach $app state that merchants can't edit. The $app namespace alone isn't treated as safe. The catalog title, description and fix now say the same thing.
  • MISSING_AUTHORIZATION_CHECK v3: adds a Shopify embedded-app section. authenticate.admin with offline scopes is Shopify's standard model and not a finding by itself. Three cases are still reported: a skipped app-defined role or allowlist (comparing loader, action and sibling routes), another app user's records, and privileged paths that fall back from online to offline sessions.
  • STATIC_FRAME_ANCESTORS v2: accepts the SDK's addDocumentResponseHeaders. The agent still inspects headers that app code sets after or instead of it, including values built from variables, which the deterministic regex can't evaluate. The deterministic version moves to 2 with it, as the registry requires.
  • Unit tests (checks.test.ts, deterministic-rules.test.ts): cover the new rule wording, the prompt versions and key guidance, the METAFIELD_OFFLINE_TOKEN catalog text, and the STATIC_FRAME_ANCESTORS deterministic version.

An eval of the default React Router template is deferred to the upcoming eval suite. In a manual pre-change comparison against the unmodified template, codex recorded all three checks as unresolved in 5 of 7 passes. With these prompts, codex and claude recorded them clean in 6 of 6 passes. Against a copy with one planted bug per check, they caught all three bugs in 6 of 6 passes.

How to manually test your changes?

  1. Scaffold the React Router template with shopify app init.
  2. Run shopify app security check, ask your coding agent to follow shopify app security instructions, then run shopify app security review.
  3. METAFIELD_OFFLINE_TOKEN, MISSING_AUTHORIZATION_CHECK and STATIC_FRAME_ANCESTORS should be passed rather than unresolved.

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

PR authored by Qlaw

A fresh React Router app came back with METAFIELD_OFFLINE_TOKEN,
MISSING_AUTHORIZATION_CHECK and STATIC_FRAME_ANCESTORS unresolved for
some agents, though the template code is safe. The instructions told the
agent to record unresolved whenever it couldn't prove exploitability,
and the three prompts didn't describe the React Router SDK defaults.

- Record executed when the code establishes the boundary a check asks
  about, and unresolved only for an incomplete investigation or a named
  candidate.
- METAFIELD_OFFLINE_TOKEN v2 checks token provenance, so an offline
  session from authenticate.admin is no longer a finding by itself, and
  its catalog text no longer contradicts its fix.
- MISSING_AUTHORIZATION_CHECK v3 treats authenticate.admin as Shopify's
  standard model, while still reporting skipped app-defined roles,
  other users' records and online-to-offline fallbacks.
- STATIC_FRAME_ANCESTORS v2 accepts addDocumentResponseHeaders and still
  inspects headers set after or instead of it, including values built
  from variables. The deterministic version moves with it.
- Add a deterministic fixture test of the template at a pinned commit,
  and a dev smoke script that runs codex and claude against the template
  and a planted-bug negative control.

Co-authored-by: Qlaw <noreply@qlaw.quick.shopify.io>
@jplhomer
jplhomer requested a review from a team as a code owner October 7, 2026 19:20
Copilot AI balanced review requested due to automatic review settings October 7, 2026 19:20
@github-actions github-actions Bot added the Area: @shopify/cli @shopify/cli package issues label Oct 7, 2026

Copilot AI 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.

🟡 Changes recommended

The smoke runner can falsely succeed without executing agents and unsafely interpolates workspace paths into shell commands.

1 open finding
What changed in this PR

Calibrates App Security checks to correctly pass safe React Router template patterns while preserving concrete findings.

Changes:

  • Refines unresolved-status guidance and three agent checks.
  • Adds pinned-template regression coverage.
  • Adds a reusable agent smoke-test harness and changeset.
File Description
.changeset/​app-security-unresolved-calibration.md Records the user-facing fix.
bin/​app-security-smoke/​README.md Documents smoke-test usage.
bin/​app-security-smoke/​run.js Adds template and negative-control agent runs.
packages/​app/​src/​cli/​services/​app-security-engine/​INSTRUCTIONS.md Clarifies executed versus unresolved status.
packages/​app/​src/​cli/​services/​app-security-engine/​checks/​METAFIELD_OFFLINE_TOKEN.md Refines offline-token provenance rules.
packages/​app/​src/​cli/​services/​app-security-engine/​checks/​MISSING_AUTHORIZATION_CHECK.md Documents React Router authorization defaults.
packages/​app/​src/​cli/​services/​app-security-engine/​checks/​STATIC_FRAME_ANCESTORS.md Accepts SDK-managed CSP headers.
packages/​app/​src/​cli/​services/​app-security-engine/​checks/​embedded.ts Regenerates embedded prompts and instructions.
packages/​app/​src/​cli/​services/​app-security-engine/​checks/​index.ts Aligns generated agent guidance.
packages/​app/​src/​cli/​services/​app-security-engine/​rules/​catalog.ts Updates metafield check metadata.
packages/​app/​src/​cli/​services/​app-security-engine/​scanners/​index.ts Bumps the deterministic CSP check version.
packages/​app/​src/​cli/​services/​app-security-engine/​tests/​checks.test.ts Tests revised prompt guidance.
packages/​app/​src/​cli/​services/​app-security-engine/​tests/​deterministic-rules.test.ts Verifies the CSP version bump.
packages/​app/​src/​cli/​services/​app-security-engine/​tests/​fixtures/​react-router-template.ts Adds the generated template fixture.
packages/​app/​src/​cli/​services/​app-security-engine/​tests/​react-router-template.test.ts Verifies expected deterministic results.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

jplhomer and others added 2 commits October 7, 2026 14:52
The default React Router template will be covered by the upcoming eval
suite, so this change keeps only the prompt, rule and catalog updates and
their unit tests. Add a unit test for the METAFIELD_OFFLINE_TOKEN catalog
text.

Co-authored-by: Qlaw <noreply@qlaw.quick.shopify.io>
STATIC_FRAME_ANCESTORS v2 covers policies built from variables as well as
literal ones, and agent snapshots take their description from the
catalog.

Co-authored-by: Qlaw <noreply@qlaw.quick.shopify.io>

@jek jek 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.

Worked on my template:

shopify app security review --check-id METAFIELD_OFFLINE_TOKEN --check-id MISSING_AUTHORIZATION_CHECK --check-id STATIC_FRAME_ANCESTORS

│  3 checks passed.                                                             │
│                                                                               │
│  METAFIELD_OFFLINE_TOKEN      passed                                          │
│  MISSING_AUTHORIZATION_CHECK  passed                                          │
│  STATIC_FRAME_ANCESTORS       passed                                          │
│                                                                               │

@jplhomer
jplhomer requested a review from dmerand October 8, 2026 14:38
@jplhomer

jplhomer commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

/snapit

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

🫰✨ Thanks @jplhomer! Your snapshot has been published to npm.

Built from 3611af7998f92cc1bdbb0899cffc6cdb6f2fb5c1. Workflow run.

Test the snapshot by installing your package globally:

pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20261008143951

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@dmerand dmerand 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.

LGTM, small LLM suggestion.

Comment thread packages/app/src/cli/services/app-security-engine/INSTRUCTIONS.md
jplhomer and others added 2 commits October 9, 2026 09:06
The glossary still said unresolved meant you couldn't finish the check or prove the issue, which contradicted the executed-versus-unresolved rule. It now means an incomplete investigation or a named candidate whose boundary is still unclear.

Co-authored-by: Qlaw <noreply@qlaw.quick.shopify.io>
…ssary

# Conflicts:
#	packages/app/src/cli/services/app-security-engine/tests/deterministic-rules.test.ts
@jplhomer
jplhomer added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit d4bec95 Oct 9, 2026
32 of 33 checks passed
@jplhomer
jplhomer deleted the jplhomer/app-security-unresolved-calibration branch October 9, 2026 14:42
dmerand added a commit that referenced this pull request Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: @shopify/cli @shopify/cli package issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants