Skip to content

[Enhancement]: Add per-user rate limiting and atomic idempotency enforcement on vote endpoints to prevent vote manipulation #80

Description

@prince-pokharna

enhancement, security, backend, GSSoC 2026

Summary

StackIt's voting system (POST /api/questions/:id/vote and POST /api/answers/:id/vote) is the backbone of the reputation system. Without rate limiting and atomic idempotency enforcement, a malicious or buggy client can rapidly fire repeated vote requests — inflating or deflating scores through network-level automation. The current implementation relies on application-level checks that are vulnerable to race conditions under concurrency.

Problem

  • No per-user, per-resource rate limit exists on vote endpoints.
  • Idempotency (the "already voted" check) is enforced only at the application layer — not atomically at the database level — making it vulnerable to race conditions under concurrent requests.
  • No cooldown between vote and unvote operations allows reputation farming via rapid vote cycling.
  • The endpoint returns 200 OK on duplicate vote attempts, providing no feedback to the frontend that the action was a no-op.

Impact

  • Malicious users can automate vote requests to unfairly boost their own answers or tank competitor answers.
  • Race conditions can result in duplicate vote records that corrupt the reputation calculation.

Proposed Solution

1. Add express-rate-limit middleware on all vote routes:

// server/middleware/voteLimiter.js
const rateLimit = require('express-rate-limit');

module.exports = rateLimit({
  windowMs: 60 * 1000,
  max: 10,
  keyGenerator: (req) => `vote:${req.user.id}`,
  standardHeaders: true,
  legacyHeaders: false,
  message: {
    error: 'Too many voting actions. Please wait before voting again.',
    retryAfter: 60,
  },
});

2. Add a unique compound index to the Vote model for atomic idempotency at the database level:

// server/models/Vote.js
VoteSchema.index(
  { user: 1, target: 1, targetType: 1 },
  { unique: true }
);

3. Return 409 Conflict (not a silent 200) when a duplicate vote is attempted, so the frontend can accurately update its UI state:

// In the vote controller
try {
  await Vote.create({ user: req.user.id, target: id, targetType: 'question', value });
} catch (err) {
  if (err.code === 11000) {
    return res.status(409).json({ error: 'You have already voted on this item.' });
  }
  throw err;
}

I am participating in GSSoC 2026. Could you please assign this issue to me?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions