Skip to content

Treat shared-namespace metafields as writable by other apps in app security checks - #8893

Merged
jplhomer merged 3 commits into
mainfrom
jplhomer/app-security-shared-namespace-metafield
Oct 20, 2026
Merged

jplhomer merged 3 commits into
mainfrom
jplhomer/app-security-shared-namespace-metafield

Conversation

@jplhomer

Copy link
Copy Markdown
Contributor

WHY are these changes introduced?

The XSS agent checks describe metafields as merchant-writable only. But metafields and metaobjects outside the app-owned namespace can also be written by any other app installed on the shop with write access to that resource. If one of those apps is compromised, it can overwrite a value this app renders. CROSS_SITE_SCRIPTING step 6 also told agents that intentional author-controlled HTML isn't a finding, so an agent would dismiss an app rendering its own widget HTML from a shared namespace, such as reviews.widget_html, even though another app can replace it.

Shopify docs confirm the ownership model:

  • Metafield ownership: merchant-owned metafields use any non-reserved namespace and are "readable and writable by merchants and all apps with appropriate scopes". App-owned metafields use $app (GraphQL) or app (TOML). shopify-- namespaces are Shopify-reserved, but their metafields are typically merchant-owned.
  • Metaobject ownership: the same split for $app: types and non-reserved types.
  • Metafield definitions: $app:<sub-namespace> is stored as app--{id}--<sub-namespace>, and TOML definitions are always declared under [<resource>.metafields.app.<key>].

WHAT is this pull request doing?

  • Adds the shared-namespace trust model to THEME_EXTENSION_XSS, UNSAFE_INNERHTML, CROSS_SITE_SCRIPTING and TEXT_SETTING_HTML_SMUGGLING: another installed app is a lower-trust writer, and HTML or script in a shared namespace isn't trusted author content even when this app usually writes it. CROSS_SITE_SCRIPTING step 6 no longer dismisses this case.
  • Adds the SHARED_NAMESPACE_METAFIELD_RENDER agent check (medium). It reports shared-namespace metafield or metaobject values that reach HTML, JavaScript or URL output without context-appropriate escaping, or that are deliberately rendered as raw HTML, script or a URL. This covers Liquid, Storefront API to DOM, Admin API in the embedded app, and App Proxy responses. It decides ownership from the namespace prefix only, without reading shopify.app.toml. It leaves dynamic namespaces unresolved, and it treats any app-- prefix as owned (a known gap, because the prefix could belong to another app). It reports alongside the XSS checks rather than replacing them.
  • Bumps versions: THEME_EXTENSION_XSS 2 → 3, UNSAFE_INNERHTML 3 → 4 (agent and deterministic, because the registry requires shared IDs to match), CROSS_SITE_SCRIPTING 1 → 2, TEXT_SETTING_HTML_SMUGGLING 2 → 3.

It doesn't change the deterministic Liquid rule.

How to manually test your changes?

  1. In a test app with a theme app extension, render {{ product.metafields.reviews.widget_html }} from a block.
  2. Run shopify app security check.
  3. Confirm that agent-checks.json lists SHARED_NAMESPACE_METAFIELD_RENDER version 1, plus the bumped versions of the four XSS checks.
  4. Run the agent review. Confirm it reports the block under SHARED_NAMESPACE_METAFIELD_RENDER and doesn't dismiss the XSS finding as author-controlled.

Post-release steps

The new check's docs_url (https://shopify.dev/docs/apps/build/security/app-security-checks/shared-namespace-metafield-render) needs a shopify.dev page.

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 a review from a team as a code owner October 14, 2026 16:43
Copilot AI balanced review requested due to automatic review settings October 14, 2026 16:43
@github-actions github-actions Bot added the Area: @shopify/cli @shopify/cli package issues label Oct 14, 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 new check can produce false positives, and the changeset uses patch rather than required minor bumps.

3 open findings
What changed in this PR

Updates App Security to treat shared-namespace metafields and metaobjects as lower-trust input.

Changes:

  • Adds SHARED_NAMESPACE_METAFIELD_RENDER.
  • Updates four XSS agent prompts and versions.
  • Adds registry, generated-source, catalog, and contract coverage.
File Description
.changeset/​app-security-shared-namespace-metafield.md Adds release metadata.
checks/​CROSS_SITE_SCRIPTING.md Expands shared-namespace trust guidance.
checks/​SHARED_NAMESPACE_METAFIELD_RENDER.md Defines the new agent check.
checks/​TEXT_SETTING_HTML_SMUGGLING.md Recognizes other apps as potential writers.
checks/​THEME_EXTENSION_XSS.md Updates Liquid source classification.
checks/​UNSAFE_INNERHTML.md Updates metafield and metaobject trust guidance.
checks/​embedded.ts Embeds regenerated agent prompts.
rules/​catalog.ts Registers the new check.
scanners/​index.ts Aligns the deterministic shared-ID version.
tests/​catalog.test.ts Adds the documentation slug.
tests/​checks.test.ts Tests prompt registration and metadata.
tests/​deterministic-rules.test.ts Tests the deterministic version bump.

🧠 Review effort: Balanced


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

Comment thread .changeset/app-security-shared-namespace-metafield.md Outdated
@jplhomer
jplhomer requested a review from jek October 15, 2026 14:16

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

Tested on a local app & found violations

@jplhomer
jplhomer requested a review from dmerand October 16, 2026 14:16
…curity checks

Metafields and metaobjects outside the app-owned namespace can be written by the merchant and by any other installed app with write access to that resource. Tell THEME_EXTENSION_XSS, UNSAFE_INNERHTML, CROSS_SITE_SCRIPTING and TEXT_SETTING_HTML_SMUGGLING that a compromised app is a lower-trust writer, and stop CROSS_SITE_SCRIPTING from treating HTML the app stores in a shared namespace as author-controlled. Bump each of their versions, including the deterministic UNSAFE_INNERHTML version it shares.

Add the SHARED_NAMESPACE_METAFIELD_RENDER agent check, which reports shared-namespace values reaching HTML, JavaScript or URL output, classifying ownership by namespace prefix.
…or change

Treat a shared-namespace value passed through a strict allowlist sanitizer, adequate for its output context, as a non-finding in SHARED_NAMESPACE_METAFIELD_RENDER. Bump the changeset to minor, since the change adds an agent check.
@jplhomer
jplhomer force-pushed the jplhomer/app-security-shared-namespace-metafield branch from 213bb09 to 4f72908 Compare October 20, 2026 14:55

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

Couple of small notes, nothing blocking.

- `{{ app.metafield.namespace.key }}` — app metafield, could be
merchant-writable depending on permissions

Metafields and metaobjects outside the app-owned namespace (`app--…` or

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.

The app-owned list in this prompt does not contain $app.

This prompt is for Liquid. In Liquid, a theme app extension uses the $app prefix to read app-owned data, for example product.metafields["$app:custom"].my_key and shop.metaobjects["$app:custom"]. Refer to reserved prefixes.

The other three XSS prompts have $app in their lists. The new check has $app and $app:<sub-namespace>.

I did a smoke test with 3 agent runs. In all 3 runs, the agents identified the $app: reads as app-owned. But each run examined three checks together. If an agent examines only this check, this prompt does not give it the $app information.

Please use the same app-owned list in all five prompts. Add the Liquid form to the list. For example:

   Metafields and metaobjects outside the app-owned namespace (`$app`,
   `$app:<sub-namespace>`, `app--…`, or `app.metafields`; in Liquid, for
   example `product.metafields["$app:custom"]`) can be written by the
   merchant and by any other app installed on the shop with write access to
   that resource.

To keep the lists the same, add this line to the loop in the new checks.test.ts test:

      expect(check.prompt, id).toContain('$app')

On this head, the test fails for THEME_EXTENSION_XSS. After you change the text and run node embed-checks.mjs, the test passes.

'Investigates metafield and metaobject values from shared namespaces, which other installed apps can write, reaching HTML, JavaScript or URL output.',
fix: 'Move the field to an app-owned namespace with a declared definition, prefer typed fields such as rich text or URL, or escape the value for its context.',
guide: 'https://shopify.dev/docs/apps/build/metafields#metafield-ownership',
docsUrl: `${CHECK_DOCS_BASE_URL}/shared-namespace-metafield-render`,

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.

Noting: This page returns 404 now: https://shopify.dev/docs/apps/build/security/app-security-checks/shared-namespace-metafield-render. The pages for the other checks return 200.

shopify app security review shows this link in the box of each check with findings, and the agent instructions say that the docs_url explains the fix. The comment on DOCS_PAGE_SLUGS in catalog.test.ts also says to change the catalog and the docs together.

The PR lists the page as a post-release step. Please make sure the page is live when the CLI release that includes this check ships.

@jplhomer
jplhomer added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit 6b75e1f Oct 20, 2026
49 of 55 checks passed
@jplhomer
jplhomer deleted the jplhomer/app-security-shared-namespace-metafield branch October 20, 2026 18:10
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