Skip to main content

Config write trusts shop from request input

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

DetailValue
Check IDUNSCOPED_SHOP_CONFIG_WRITE
SeverityHigh
How it runsAgent review

app/routes/webhooks.settings.sync.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" },
],
},
},
);
};

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

For more detail, see Session tokens.

app/routes/app.settings.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",
},
],
},
},
);
};


Was this page helpful?