Storing a deletion flag that is never checked in the read path is a false sense of control — under DSA Art. 16 and CWE-284, content removal must actually prevent the content from being visible to users. Platforms where deleted=true content still appears in public feeds, search results, or API responses give bad actors exactly what they want: their content stays up regardless of moderation actions. Soft-delete patterns that skip the WHERE clause in even one public query defeat the entire enforcement model.
Critical because ineffective content removal means moderation actions have no real effect — abusive, illegal, or harmful content remains publicly visible after moderators act.
Implement a deleted_at soft-delete column and enforce its exclusion in every public-facing query. In src/lib/queries.ts:
export async function getPublicPosts() {
return db.posts.findMany({
where: {
deletedAt: null,
status: { not: 'removed' },
},
orderBy: { createdAt: 'desc' },
});
}
Audit every query in the codebase that reads posts, comments, or messages — confirm each includes deletedAt: null in its WHERE clause. Removed content may be retained in the database for records (moderation log, appeals), but must never surface in any public API response or UI render path.
ID: community-moderation-safety.report-enforcement.content-removal
Severity: critical
What to look for: Check if moderators have actions available to remove or hide content. Look for delete, hide, or flag-as-reviewed endpoints. Verify that removed content is actually removed from public view (not just marked as deleted but still visible).
Pass criteria: Moderators can delete, hide, or mark content as removed via at least 1 explicit action (button, endpoint, or command). Enumerate all public content queries and confirm each filters out removed items. Removed content is no longer visible to regular users (but may be retained for records).
Fail criteria: No moderator action to remove/hide content, or content is marked as deleted but still displays in public queries. A soft-delete flag that is never checked in the read path does not count as pass.
Skip (N/A) when: Never — content removal is essential.
Detail on fail: "Moderators can mark content as 'spam' but the content remains visible to users."
Remediation: Implement content removal actions that hide content from public view while optionally preserving it for moderation records. Add a deleted_at column and filter in all public queries at src/lib/queries.ts:
// Filter removed content in all public queries
const publicPosts = await db.posts.findMany({
where: { deletedAt: null, status: { not: 'removed' } }
});