---
title: Config write trusts shop from request input
description: >-
  Detects settings or metafield writes scoped by request-controlled shop input
  without visible auth verification.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/security/app-security-checks/unscoped-shop-config-write
  md: >-
    https://shopify.dev/docs/apps/build/security/app-security-checks/unscoped-shop-config-write.md
---

# Config write trusts shop from request input

Detects settings or metafield writes scoped by request-controlled shop input without visible auth verification.

| Detail | Value |
| - | - |
| Check ID | `UNSCOPED_SHOP_CONFIG_WRITE` |
| Severity | High |
| How it runs | Agent review |

***

## Vulnerable example

## app/routes/webhooks.settings.sync.tsx

```tsx
import type { ActionFunctionArgs } from "react-router";


import { unauthenticated } from "../shopify.server";


export const action = async ({ request }: ActionFunctionArgs) => {
  const { shop, ownerId, value } = await request.json();


  // Both the shop and the metafield owner come straight from the
  // request body, so a caller can rewrite another merchant's
  // configuration.
  const { admin } = await unauthenticated.admin(shop);


  return admin.graphql(
    `#graphql
      mutation SetConfig($metafields: [MetafieldsSetInput!]!) {
        metafieldsSet(metafields: $metafields) {
          userErrors { message }
        }
      }`,
    {
      variables: {
        metafields: [
          { ownerId, namespace: "app", key: "config", value, type: "json" },
        ],
      },
    },
  );
};
```

***

## How to fix

Derive the target shop from an authenticated session or verified HMAC.

For more detail, see [Session tokens](https://shopify.dev/docs/apps/build/authentication-authorization/session-tokens).

## app/routes/app.settings.tsx

```tsx
import type { ActionFunctionArgs } from "react-router";


import { authenticate } from "../shopify.server";


export const action = async ({ request }: ActionFunctionArgs) => {
  // The session decides which shop is written to, not the payload.
  const { admin } = await authenticate.admin(request);


  const formData = await request.formData();
  const value = String(formData.get("config"));


  // Resolve the owner from the authenticated context too:
  // ownerId is required, and taking it from the request would
  // reintroduce the same bug one field over.
  const shopResponse = await admin.graphql(`{ shop { id } }`);
  const { data } = await shopResponse.json();


  return admin.graphql(
    `#graphql
      mutation SetConfig($metafields: [MetafieldsSetInput!]!) {
        metafieldsSet(metafields: $metafields) {
          userErrors { message }
        }
      }`,
    {
      variables: {
        metafields: [
          {
            ownerId: data.shop.id,
            namespace: "app",
            key: "config",
            value,
            type: "json",
          },
        ],
      },
    },
  );
};
```

***

## Related

* [`shopify app security check`](https://shopify.dev/docs/api/shopify-cli/app/app-security-check), the command that runs this check
* [All app security checks](https://shopify.dev/docs/apps/build/security/app-security-checks)

***
