Profile-level privacy settings are a coarse control — users frequently want to share a public post while keeping their overall profile private, or archive a sensitive post while keeping their profile public. Without per-post visibility, every privacy decision is all-or-nothing. More critically, feed and search APIs that check profile visibility but not post visibility will surface restricted posts to anyone who queries the endpoint with a known user ID, violating GDPR Art. 25's data minimization principle (OWASP A01:2021).
Medium because the exposure is limited to posts on profiles that are public but contain individually-restricted content, but the bypass is trivially achieved through a direct API call.
Add a visibility column to the posts table and filter on it in every feed, timeline, and search query — not just in the profile access check. In src/app/api/posts/route.ts or your ORM query layer:
ALTER TABLE posts ADD COLUMN visibility VARCHAR(20)
CHECK (visibility IN ('public', 'friends_only', 'private')) DEFAULT 'public';
Update feed queries to apply both profile and post visibility:
const posts = await db.post.findMany({
where: {
AND: [
{ visibility: 'public' },
{ author: { profile_visibility: { not: 'private' } } }
]
}
});
Search indices must also be filtered — re-indexing after adding the column is required if you use Elasticsearch or Algolia.
ID: community-privacy-controls.visibility.post-visibility-independent
Severity: medium
What to look for: Enumerate every relevant item. Check the database schema for visibility/privacy fields on posts or content items. Verify that individual posts can have different visibility than the profile default. Examine API endpoints that return posts (feed, user timeline, search) to confirm they check post visibility, not just profile visibility.
Pass criteria: At least 1 of the following conditions is met. Posts have independent visibility settings (can be public while profile is private, or vice versa). API endpoints check post-level visibility before returning. Posts with restricted visibility are not included in feed/search results unless the requester has permission.
Fail criteria: Posts inherit profile visibility with no independent control. Visibility enforced only on frontend. Private posts appear in search results or API responses without visibility check.
Skip (N/A) when: Never — post-level visibility control is standard.
Detail on fail: Describe the limitation. Example: "Posts inherit profile visibility; cannot make one post private while keeping profile public." or "Listing API returns private posts if called with correct user ID, no post visibility check."
Remediation: Add post-level visibility and enforce in queries:
ALTER TABLE posts ADD COLUMN visibility VARCHAR(20)
CHECK (visibility IN ('public', 'friends_only', 'private')) DEFAULT 'public';
Update feed queries to check both:
async function getUserFeed(userId: string) {
const posts = await db.post.findMany({
where: {
AND: [
{ visibility: 'public' },
{
OR: [
{ authorId: userId },
{
author: {
followers: {
some: { followerId: userId }
}
}
}
]
}
]
},
orderBy: { createdAt: 'desc' }
});
return posts;
}