Private Account/Friend Request Flow
Why it matters
When a user marks their profile private, they are expressing a consent boundary — covered under GDPR Art. 25 and CCPA §1798.120. An instant-follow model that ignores the private flag grants content access without approval, defeating the user's explicit privacy setting. Under CWE-284, this is a failed authorization check: the platform makes the access control decision on behalf of the user rather than routing it through the user's own approval queue. The fix requires a FollowRequest table to hold pending state across the approval workflow.
Severity rationale
Low because the gap only surfaces when private accounts exist and is not a data exfiltration vector, but it directly violates the user's stated privacy preference.
Remediation
Add a FollowRequest model with a status enum to your Prisma schema, then branch your follow logic on the target account's is_private flag before deciding whether to create an instant follow or a pending request.
// lib/follow.ts
export async function requestFollow(followerId: string, followedId: string) {
const target = await db.user.findUniqueOrThrow({ where: { id: followedId } })
if (!target.is_private) {
return db.follow.create({ data: { followerId, followedId } })
}
return db.followRequest.create({
data: { followerId, followedId, status: 'pending' }
})
}
export async function approveFollowRequest(requestId: string, approverId: string) {
const req = await db.followRequest.findUniqueOrThrow({ where: { id: requestId } })
if (req.followedId !== approverId) throw new Error('Unauthorized')
await db.$transaction([
db.follow.create({ data: { followerId: req.followerId, followedId: req.followedId } }),
db.followRequest.update({ where: { id: requestId }, data: { status: 'accepted' } })
])
}
Detection
-
ID:
friend-request-approval -
Severity:
low -
What to look for: Enumerate all relevant files and If profile privacy controls exist (
is_privatefield), check for a friend request flow. Look for aFollowRequestorConnectionRequesttable with astatusfield (pending, accepted, rejected). Check for UI elements allowing users to "Accept" or "Decline" follow requests. Verify that content is blocked until the follow request is approved for private account users. -
Pass criteria: At least 1 conforming pattern must exist. If
is_privateis supported, follow requests use a pending status. Users can approve or decline. Content from private accounts is blocked until approval. -
Fail criteria: Private accounts exist but follow requests are instant/no approval step, or pending requests are not displayed to the recipient.
-
Skip (N/A) when: No profile privacy feature, or all profiles are public.
-
Detail on fail:
"is_private field exists but follow requests are instant — private account follow flow not enforced"or"No FollowRequest table; pending follows not tracked" -
Remediation: Implement follow request workflow:
// Prisma schema model FollowRequest { id String @id @default(cuid()) followerId String followedId String status String @default("pending") // pending, accepted, rejected createdAt DateTime @default(now()) updatedAt DateTime @updatedAt follower User @relation(name: "sent_requests", fields: [followerId], references: [id], onDelete: Cascade) followed User @relation(name: "received_requests", fields: [followedId], references: [id], onDelete: Cascade) @@unique([followerId, followedId]) } // Follow logic for private accounts export async function followUser(followerId: string, followedId: string) { const followedUser = await db.user.findUnique({ where: { id: followedId } }) if (followedUser.is_private) { // Create pending request return db.followRequest.create({ data: { followerId, followedId, status: 'pending' } }) } else { // Instant follow for public accounts return db.follow.create({ data: { followerId, followedId } }) } } // Accept follow request export async function acceptFollowRequest(requestId: string) { const req = await db.followRequest.findUnique({ where: { id: requestId } }) await db.follow.create({ data: { followerId: req.followerId, followedId: req.followedId } }) await db.followRequest.update({ where: { id: requestId }, data: { status: 'accepted' } }) }
External references
- cwe · CWE-284 — Improper Access Control
- gdpr · Art. 25 — Data Protection by Design and by Default
- ccpa · §1798.120 — Right to opt out of sale or sharing of personal information
Taxons
History
- 2026-04-18·v1.0.0·Initial import from community-social-engagement·automated