Moderation workflow prevents unreviewed reviews from displaying
Why it matters
Without a status column defaulting to 'pending' and a filter on every public display query, your review page is an open broadcast channel — any submitted content, including spam, competitor attacks, and profanity, appears live to all shoppers immediately. CWE-284 (Improper Access Control) covers exactly this: the resource (a published review) is accessible to users who should not have triggered that state transition (published). Even one unfiltered display query is enough to leak pending reviews; attackers simply POST then GET before moderation runs.
Severity rationale
Critical because a missing status filter exposes unmoderated, potentially harmful content to all site visitors with no privilege required beyond submitting a review.
Remediation
Add a status column to the reviews table via migration and apply WHERE status = 'approved' to every public-facing query in lib/reviews.ts and api/reviews/route.ts.
-- Migration
ALTER TABLE reviews ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'pending';
CREATE INDEX idx_reviews_status ON reviews(status);
// lib/reviews.ts — the single place all display queries should live
export async function getApprovedReviews(productId: string) {
return db.reviews.findMany({
where: { product_id: productId, status: 'approved' },
orderBy: { created_at: 'desc' }
})
}
Search the codebase for every db.reviews.findMany and SELECT * FROM reviews call and verify each one includes the status filter. Related: ecommerce-reviews.moderation-trust.spam-detection for automated pre-screening before insert.
Detection
-
ID:
moderation-enforced -
Severity:
critical -
What to look for: List all review display queries and API routes. For each query, classify whether it filters by a
statusfield (e.g.,WHERE status = 'approved'). Count the ratio of filtered vs unfiltered queries. Also check the reviews database schema for astatuscolumn with at least 3 states (pending, approved, rejected). -
Pass criteria: The reviews database schema includes a
statuscolumn with at least 2 states (pending and approved). At least 100% of public-facing review display queries filter bystatus = 'approved'— no query returns unmoderated reviews to end users. New reviews default tostatus = 'pending'on insert. -
Fail criteria: Reviews are automatically displayed on submission without any status filtering, or the status field exists but at least 1 public display query omits the filter. Must not pass when a status column exists but the default value is 'approved' — that bypasses moderation entirely.
-
Skip (N/A) when: The project uses a fully external review service (Yotpo, Judge.me, Trustpilot) that handles moderation outside the codebase.
-
Detail on fail:
"Reviews table has status column but 2 of 3 display queries omit the status filter. Unmoderated reviews visible on product pages."or"No status column in reviews table. All reviews published immediately on submission." -
Cross-reference: Related to
ecommerce-reviews.moderation-trust.spam-detection(automated filtering complements manual moderation) andecommerce-reviews.moderation-trust.edit-protection(edits must re-enter moderation). -
Remediation: Add a status column via migration and filter all display queries in
lib/reviews.tsandapi/reviews/route.ts:-- Database: add status column ALTER TABLE reviews ADD COLUMN status VARCHAR DEFAULT 'pending'; CREATE INDEX idx_reviews_status ON reviews(status);// API: Only display approved reviews const approvedReviews = await db.reviews.findMany({ where: { product_id: productId, status: 'approved' }, orderBy: { created_at: 'desc' } }) // Admin: Add route to approve/reject // POST /api/admin/reviews/:id/approve // POST /api/admin/reviews/:id/reject
External references
- cwe · CWE-284 — Improper Access Control
Taxons
History
- 2026-04-18·v1.0.0·Initial import from ecommerce-reviews·automated