Session lifecycle or replay control may be missing
Investigates stale sessions, replayable signed links, and missing invalidation or idempotency across lifecycle transitions.
| Detail | Value |
|---|---|
| Check ID | SESSION_LIFECYCLE_AND_REPLAY |
| Severity | High |
| How it runs | Agent review |
Anchor to Vulnerable exampleVulnerable example
app/routes/webhooks.app.uninstalled.tsx
import type { ActionFunctionArgs } from "react-router";
import { authenticate } from "../shopify.server";
export const action = async ({ request }: ActionFunctionArgs) => {
const { shop } = await authenticate.webhook(request);
// Shopify revokes the access token on uninstall, but the
// session rows stay behind. Code that treats a stored session
// as proof the shop still has the app installed keeps trusting
// a shop that removed it.
console.log(`Uninstalled: ${shop}`);
return new Response();
};
Anchor to How to fixHow to fix
Invalidate stale authority and enforce replay protection or re-authorization before sensitive actions.
app/routes/webhooks.app.uninstalled.tsx
import type { ActionFunctionArgs } from "react-router";
import { authenticate } from "../shopify.server";
import prisma from "../db.server";
export const action = async ({ request }: ActionFunctionArgs) => {
const { shop, session } = await authenticate.webhook(request);
// Delete the shop's sessions on uninstall. Do the same on
// logout, scope downgrade, and account disconnect.
if (session) await prisma.session.deleteMany({ where: { shop } });
return new Response();
};
shopify app security check, the command that runs this check- All app security checks
Was this page helpful?