Platforms without email verification allow spam accounts to be created and operational in seconds. CWE-306 (Missing Authentication for Critical Function) applies: posting is a critical function that should require verified identity. Without a verification gate, a single attacker can automate account creation and bypass per-account rate limits by rotating through throwaway emails. Email verification also enforces a basic contact point for ban notifications, password resets, and compliance communications — platforms without it lose the ability to reach users for any purpose.
High because unverified accounts eliminate the primary friction against spam account creation, allowing attackers to scale abuse by simply creating new accounts when banned.
Gate all content-creation endpoints behind an email verification check. Add a emailVerified boolean to your users table and enforce it at the API layer:
app.post('/api/posts', authenticate, async (req, res) => {
const user = await db.users.findOneById(req.user.id);
if (!user.emailVerified) {
return res.status(403).json({ error: 'Please verify your email before posting.' });
}
// ... create post
});
On signup, generate a time-limited token (24-hour expiry), send it to the provided email, and set emailVerified = true only when the token is consumed. A verification email that is sent but never enforced as a gate does not satisfy this check.
ID: community-moderation-safety.spam-prevention.email-verification
Severity: high
What to look for: Check if new user accounts require email verification before posting. Look for email verification flows, token generation, and verification checks before content submission.
Pass criteria: New accounts must verify their email before posting content. Unverified accounts cannot submit posts or comments — at least 100% of post-creation endpoints must enforce this gate. Enumerate all post-creation endpoints and confirm each checks the email verification flag before allowing submission.
Fail criteria: Accounts can post immediately upon creation without email verification. A verification email sent but never enforced as a gate does not count as pass.
Skip (N/A) when: Platform allows anonymous posting with no user accounts required.
Detail on fail: "New users can post immediately. No email verification required. Spam accounts are trivial to create."
Remediation: Require email verification before new accounts can post content:
// Create account with unverified email
app.post('/api/auth/signup', async (req, res) => {
const { email, password } = req.body;
const user = await db.users.create({
email,
passwordHash: await hash(password),
emailVerified: false
});
// Generate and send verification token
const token = generateToken();
await db.verificationTokens.create({
userId: user.id,
token,
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000)
});
await sendEmail(
email,
`Verify your email: ${process.env.APP_URL}/verify?token=${token}`
);
res.status(201).json({ message: 'Check your email to verify your account' });
});
// Verify email endpoint
app.post('/api/auth/verify', async (req, res) => {
const { token } = req.body;
const verification = await db.verificationTokens.findOne({ token });
if (!verification || verification.expiresAt < new Date()) {
return res.status(400).json({ error: 'Invalid or expired token' });
}
await db.users.updateOne(
{ _id: verification.userId },
{ emailVerified: true }
);
await db.verificationTokens.deleteOne({ _id: verification._id });
res.status(200).json({ success: true });
});
// Check verification before posting
app.post('/api/posts', authenticate, async (req, res) => {
const user = await db.users.findOne({ _id: req.user.id });
if (!user.emailVerified) {
return res.status(403).json({ error: 'Please verify your email to post' });
}
// ... create post
});