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 |
Anchor to Vulnerable exampleVulnerable example
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" },
],
},
},
);
};
Anchor to How to fixHow to fix
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",
},
],
},
},
);
};
shopify app security check, the command that runs this check- All app security checks
Was this page helpful?