Repository navigation
Treat shared-namespace metafields as writable by other apps in app security checks - #8893
Conversation
There was a problem hiding this comment.
🟡 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.
jek
left a comment
There was a problem hiding this comment.
Tested on a local app & found violations
…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.
213bb09 to
4f72908
Compare
dmerand
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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`, |
There was a problem hiding this comment.
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.


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_SCRIPTINGstep 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 asreviews.widget_html, even though another app can replace it.Shopify docs confirm the ownership model:
$app(GraphQL) orapp(TOML).shopify--namespaces are Shopify-reserved, but their metafields are typically merchant-owned.$app:types and non-reserved types.$app:<sub-namespace>is stored asapp--{id}--<sub-namespace>, and TOML definitions are always declared under[<resource>.metafields.app.<key>].WHAT is this pull request doing?
THEME_EXTENSION_XSS,UNSAFE_INNERHTML,CROSS_SITE_SCRIPTINGandTEXT_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_SCRIPTINGstep 6 no longer dismisses this case.SHARED_NAMESPACE_METAFIELD_RENDERagent 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 readingshopify.app.toml. It leaves dynamic namespaces unresolved, and it treats anyapp--prefix as owned (a known gap, because the prefix could belong to another app). It reports alongside the XSS checks rather than replacing them.THEME_EXTENSION_XSS2 → 3,UNSAFE_INNERHTML3 → 4 (agent and deterministic, because the registry requires shared IDs to match),CROSS_SITE_SCRIPTING1 → 2,TEXT_SETTING_HTML_SMUGGLING2 → 3.It doesn't change the deterministic Liquid rule.
How to manually test your changes?
{{ product.metafields.reviews.widget_html }}from a block.shopify app security check.agent-checks.jsonlistsSHARED_NAMESPACE_METAFIELD_RENDERversion 1, plus the bumped versions of the four XSS checks.SHARED_NAMESPACE_METAFIELD_RENDERand 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
patchfor bug fixes ·minorfor new features ·majorfor breaking changes) and added a changeset withpnpm changeset add