Listing owners can self-flag a listing as closed without moderator action
Why it matters
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.
Severity rationale
Low because the failure is primarily a UX and data-quality gap — stale listings mislead users — rather than a direct security vulnerability.
Remediation
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'.
Detection
-
ID:
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' }) }
External references
- cwe · CWE-285 — Improper Authorization
- gdpr · Art. 17 — Right to Erasure — self-closure satisfies user's right to remove their own data/listing
Taxons
History
- 2026-04-18·v1.0.0·Initial import from directory-submissions-moderation·automated