When listing owners cannot close their own listings without contacting support, closed or defunct businesses remain in the directory indefinitely — polluting search results, misleading visitors, and generating dead-end clicks. GDPR Art. 17 (right to erasure) creates an obligation to honor removal requests promptly; routing that through a support ticket introduces unnecessary delay. CWE-285 applies when the authorization model lacks a scope the owner clearly should have: control over their own content.
Low because the failure is primarily a UX and data-quality gap — stale listings mislead users — rather than a direct security vulnerability.
Add a close action to the owner dashboard that sets status = 'closed' and soft-hides the listing. Never hard-delete: keep the row for audit history and allow the owner to reopen later:
// app/api/listings/[id]/close/route.ts
export async function POST(req: Request) {
const user = await requireAuth(req)
const listing = await db.listings.findUniqueOrThrow({ where: { id: params.listingId } })
if (listing.owner_id !== user.id) {
return Response.json({ error: 'Unauthorized' }, { status: 403 })
}
await db.listings.update({
where: { id: listing.id },
data: { status: 'closed', closed_at: new Date() }
})
return Response.json({ message: 'Listing closed' })
}
In app/dashboard/listings/[id]/page.tsx, surface a "Close Listing" button that calls this route. Filter status = 'closed' from all public listing queries the same way you filter 'pending' and 'rejected'.
ID: directory-submissions-moderation.spam-prevention.self-flag-closed
Severity: low
What to look for: Check if listing owners have a simple way to mark their own listing as closed/inactive without requesting moderator intervention. Look for an owner dashboard or account page with a "Close Listing" or "Mark as Closed" button.
Pass criteria: Enumerate all relevant code paths. Owners can directly close/archive their own listings. The listing is hidden from the public directory but remains in the database. Owners can reopen the listing later. with at least 1 verified instance.
Fail criteria: Owners cannot close their own listings without contacting support, or closing a listing hard-deletes it.
Skip (N/A) when: The project doesn't have an owner/account dashboard.
Detail on fail: "Owners can't close their own listings. A business that's shutting down has to contact support to remove their listing." or "Listings are hard-deleted on closure — no way to archive them."
Remediation: Add a self-close feature:
// app/dashboard/listings/[id]/page.tsx
export default async function ListingSettings({ params }) {
const listing = await db.listings.findUnique({
where: { id: params.id, owner_id: req.auth.userId }
})
return (
<div>
<h1>{listing.title}</h1>
<button
onClick={() => fetch(`/api/listings/${listing.id}/close`, { method: 'POST' })}
>
Close This Listing
</button>
</div>
)
}
// app/api/listings/[id]/close/route.ts
export async function POST(req: Request) {
const { listingId } = params
const ownerId = req.auth.userId
const listing = await db.listings.findUnique({ where: { id: listingId } })
if (listing.owner_id !== ownerId) {
return Response.json({ error: 'Unauthorized' }, { status: 403 })
}
await db.listings.update({
where: { id: listingId },
data: { status: 'closed', closed_at: new Date() }
})
return Response.json({ message: 'Listing closed' })
}