A verified owner badge that is set client-side or derived from user-supplied input is cosmetic — any user can fake it by editing the DOM or intercepting the API response. Visitors rely on the badge to distinguish operator-managed listings from third-party submissions. When the badge is fabricated, trust signals collapse and the directory's credibility is undermined. CWE-285 and OWASP A01 (Broken Access Control) apply: authorization state must always originate from the server.
High because a client-side or user-controllable badge can be faked by any listing submitter, making the verified owner signal meaningless to visitors.
Render the badge exclusively from server-fetched data. Never pass a isVerified prop from client state or URL params — derive it from claimed_at in the database row:
// app/listings/[id]/page.tsx (server component)
const listing = await db.listings.findUniqueOrThrow({
where: { id: params.id },
select: { title: true, description: true, claimed_at: true }
})
return (
<article>
<h1>{listing.title}</h1>
{listing.claimed_at && (
<span className="badge-verified">Verified Owner</span>
)}
</article>
)
Never expose a claimed_at override via any public-facing API endpoint. The field should only be writable by the claim verification flow in app/api/listings/[id]/verify-claim/route.ts.
ID: directory-submissions-moderation.spam-prevention.verified-owner-badge
Severity: high
What to look for: Examine how listings display ownership. If a listing has been claimed, check that: (1) a "Verified Owner" or similar badge is shown, (2) the badge is set server-side based on the claimed_at or owner_id field, not client-side, (3) the badge cannot be faked by a non-owner.
Pass criteria: Enumerate all relevant code paths. Claimed listings display a server-rendered badge that indicates verified ownership. The badge is present only for listings where claimed_at is set and verified. with at least 1 verified instance.
Fail criteria: No badge for claimed listings, or badge is set client-side or based on user input rather than the database.
Skip (N/A) when: The project has no claim feature.
Detail on fail: "Claimed listings have no badge to show the owner is verified. Users can't tell if a listing is legitimate." or "Badge is shown based on client-side data — user can fake it by editing the page."
Remediation: Render verified badges server-side:
// components/ListingCard.tsx
export function ListingCard({ listing }) {
return (
<article>
<h3>{listing.title}</h3>
<p>{listing.description}</p>
{listing.claimed_at && (
<badge className="verified-owner">
✓ Verified Owner
</badge>
)}
</article>
)
}
// app/listings/[id]/page.tsx
export default async function ListingPage({ params }) {
const listing = await db.listings.findUnique({
where: { id: params.id },
select: {
title: true,
description: true,
claimed_at: true,
owner_id: true
}
})
return <ListingCard listing={listing} />
}