Pre-shipment cancellation allowed and functional
Why it matters
Missing pre-shipment cancellation is a direct OWASP A01 (Broken Access Control) and CWE-285 gap: a customer has a legitimate right to cancel an order they just placed, and if no endpoint exists to exercise that right, they lose money or are forced to contact support for a routine action. Beyond customer experience, an absent cancellation flow means that if a payment is captured for an order that should not have been fulfilled — duplicate order, payment error, customer mistake — there is no programmatic path to unwind it. The handler must also guard against cancelling already-shipped orders through the same path, which could incorrectly trigger a refund for a package in transit.
Severity rationale
High because the absence of a guarded cancellation endpoint forces customers to contact support for a routine action and creates no programmatic path to unwind incorrectly captured payments.
Remediation
Implement a cancellation route at app/api/orders/[id]/cancel/route.ts with explicit status validation before allowing the transition.
// app/api/orders/[id]/cancel/route.ts
export async function POST(req: Request, { params }: { params: { id: string } }) {
const session = await getServerSession()
if (!session?.user) return new Response('Unauthorized', { status: 401 })
const order = await db.orders.findUnique({ where: { id: params.id, userId: session.user.id } })
if (!order) return new Response('Not found', { status: 404 })
const CANCELLABLE = ['pending', 'confirmed']
if (!CANCELLABLE.includes(order.status)) {
return Response.json(
{ error: `Cannot cancel an order with status '${order.status}'.` },
{ status: 409 }
)
}
await transitionOrder(params.id, 'cancelled', session.user.id, 'Customer requested cancellation')
return Response.json({ success: true })
}
Detection
-
ID:
preshipment-cancellation -
Severity:
high -
What to look for: Find the order cancellation endpoint or handler — count every route or function that can set an order to
cancelledstatus. This is typically a POST to/api/orders/[id]/cancelor a PATCH that sets status tocancelled. For each cancellation path, enumerate which order statuses it accepts. Verify that orders inpendingorconfirmedstatus (at least 2 pre-shipment statuses) can successfully be cancelled. Check whether the handler validates that the order is in a pre-shipment state before allowing cancellation. Quote the exact guard clause if one exists. -
Pass criteria: A cancellation endpoint or action exists and successfully transitions pre-shipment orders (
pending,confirmed) tocancelledstatus. The handler validates the order's current status against at least 2 allowed pre-shipment statuses. Attempting to cancel an already-shipped or delivered order is rejected with an appropriate error (HTTP 409 or similar) or redirected to a different flow. A cancellation handler with no status guard does not count as pass. -
Fail criteria: No cancellation endpoint exists (0 cancel handlers found). Or the endpoint exists but does not function — it throws unhandled errors, returns incorrect status codes, or silently fails without changing the order status. Or the endpoint allows cancellation of already-shipped orders through the standard customer path with no guard.
-
Skip (N/A) when: The project has no order cancellation feature — all cancellations are handled manually by admin staff via direct database access or an external platform. No cancel route or handler exists.
-
Detail on fail: Describe the specific gap. Example:
"No customer-facing cancellation endpoint found. 0 cancel handlers exist. The only way to cancel an order is for an admin to change the status directly in the database."or"A cancel endpoint exists at DELETE /api/orders/[id] but it accepts any order status, including shipped orders, with 0 status guards." -
Remediation: Implement a cancellation handler at
app/api/orders/[id]/cancel/route.tswith explicit pre-shipment state validation:// app/api/orders/[id]/cancel/route.ts export async function POST(req: Request, { params }: { params: { id: string } }) { const session = await getServerSession() if (!session?.user) return new Response('Unauthorized', { status: 401 }) const order = await db.orders.findUnique({ where: { id: params.id, userId: session.user.id }, }) if (!order) return new Response('Not found', { status: 404 }) const CANCELLABLE_STATUSES = ['pending', 'confirmed'] if (!CANCELLABLE_STATUSES.includes(order.status)) { return Response.json( { error: `Cannot cancel an order with status '${order.status}'.` + ` Contact support for post-shipment cancellation.` }, { status: 409 } ) } await transitionOrder(params.id, 'cancelled', session.user.id, 'Customer requested cancellation') return Response.json({ success: true }) }
External references
- cwe · CWE-285 — Improper Authorization
- cwe · CWE-841 — Improper Enforcement of Behavioral Workflow
- owasp:2021 · A01 — Broken Access Control
- iso-25010:2011 · functional-correctness — Functional Correctness
Taxons
History
- 2026-04-18·v1.0.0·Initial import from ecommerce-order-management·automated