Shared-namespace metafield rendered as active content
Investigates metafield and metaobject values from shared namespaces, which other installed apps can write, that reach HTML, JavaScript, or URL output, and shows how to fix them.
| Detail | Value |
|---|---|
| Check ID | SHARED_NAMESPACE_METAFIELD_RENDER |
| Severity | Medium |
| How it runs | Agent review |
Anchor to Why it mattersWhy it matters
Merchant-owned metafields and metaobjects can be written by the merchant and by every other app installed on the shop that has write access to that resource. If one of those apps is compromised, then an attacker can overwrite a value that your app renders. A value in a shared namespace is lower-trust input, even when your app is the only app that's meant to write it.
This check reports a shared-namespace value that reaches HTML, JavaScript, or URL output without escaping or serialization for that context. It also reports a value that's deliberately rendered as raw HTML, script, or a URL, such as widget markup that your app stores in reviews.widget_html. It covers Liquid in theme app extensions, Storefront API responses written to the DOM, Admin API responses rendered in the embedded app, and app proxy responses.
This check is reported alongside the cross-site scripting checks, such as THEME_EXTENSION_XSS and UNSAFE_INNERHTML, not instead of them. It's about who else can write the value. Those checks still require you to escape it.
Anchor to How ownership is decidedHow ownership is decided
Your coding agent decides ownership from the namespace or type prefix in your code. It doesn't read the definitions in shopify.app.toml.
- App-owned:
$appand$app:<sub-namespace>, which you read in Liquid with square brackets, such asproduct.metafields["$app"].<key>. Also a literalapp--namespace, and the metafields of the Liquidappobject,app.metafields. For metaobjects, a$app:type. - Shared: Every other namespace or type, such as
custom,reviews, orspecs. This includes a bareappnamespace in Liquid, such asproduct.metafields.app.<key>, which isn't translated to your app's namespace. It also includesshopify--namespaces and standard definitions: Shopify controls their structure, but merchants and apps can write their values. - Unresolved: A namespace that's built at runtime, such as a
namespacevariable passed to a query. Your agent names the value instead of guessing its ownership.
Any app-- prefix is treated as app-owned, even though it could belong to a different app.
An app-owned value isn't automatically safe. The merchant can still edit it when the definition allows it, and the cross-site scripting checks still require escaping.
Anchor to Vulnerable exampleVulnerable example
extensions/reviews/blocks/reviews-widget.liquid
Anchor to How to fixHow to 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.
extensions/reviews/blocks/reviews-widget.liquid
Declare the app-owned definition in shopify.app.toml. Use access.admin = "merchant_read" if merchants shouldn't edit the value, and access.storefront = "public_read" if the Storefront API needs it.
shopify.app.toml
Don’t
- Store HTML, script, or tracking code in a shared namespace, such as
customorreviews, and render it raw because your app wrote it. - Treat
shopify--namespaces or standard definitions as trusted. Merchants and other apps can write their values.
Do
- Store values that your app renders in an app-owned namespace, declared as
[<resource>.metafields.app.<key>]inshopify.app.toml, and read them in Liquid through the$appprefix, such asproduct.metafields["$app"].<key>. - Prefer typed fields over HTML strings, such as a
rich_text_fieldrendered withmetafield_tag, or aurlfield with a validated scheme. - Otherwise, escape or serialize the value with
jsonfor the context where it's output.
For more detail, see Metafield ownership.
- Store storefront settings where only your app can write them, the best practice that this check supports
shopify app security check, the command that runs this check- All app security checks