Skip to content

[Security]: Add rate limiting via express-rate-limit to the /api/shorten and /api/track endpoints — currently any unauthenticated caller can create unlimited short links and flood analytics #304

Description

@divyanshim27

Summary

The piik.me README's own "Security" section explicitly lists rate limiting under "Recommended Additions" with a code snippet showing it is not yet implemented. The /api/shorten endpoint and all /api/track/* endpoints are currently unprotected by any rate limit — an automated script can create thousands of short links per minute or artificially inflate any link's analytics counters.

Problem

  • POST /api/shorten has no request rate limit. A script can call it in a loop and exhaust the Firestore write quota, costing real money on the Firebase billing plan.
  • POST /api/track/impression/:shortCode and POST /api/track/share/:shortCode have no rate limit — a single IP can inflate any link's analytics by making thousands of requests, making the analytics dashboard meaningless.
  • GET /:shortCode (the redirect endpoint) has no rate limit — it is the most publicly exposed endpoint and the most likely to be abused.
  • The README's production checklist explicitly has an unchecked item: [ ] Implement rate limiting.
  • The memory.service.js in src/services/ implies some in-memory state management — if this is used for analytics, flooding it could cause memory exhaustion.

Proposed Solution

Install express-rate-limit and apply per-route limits in server.js:

npm install express-rate-limit
// server.js — add after existing middleware setup
const rateLimit = require('express-rate-limit');

// General API rate limit: 100 requests per 15 minutes per IP
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
  message: {
    error: 'Too many requests. Please try again in 15 minutes.',
  },
});

// Strict limit for link creation: 10 new short links per hour per IP
const createLimiter = rateLimit({
  windowMs: 60 * 60 * 1000,
  max: 10,
  message: {
    error: 'You have created too many links. Please try again in an hour.',
  },
});

// Analytics tracking: 30 requests per minute per IP (allows legitimate traffic bursts)
const trackingLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 30,
  message: {
    error: 'Too many tracking requests.',
  },
});

// Apply limits
app.use('/api/', apiLimiter);
app.use('/api/shorten', createLimiter);
app.use('/api/track/', trackingLimiter);

Additionally, add security headers using helmet (also listed as "Recommended" in the README):

npm install helmet
// server.js — add before any route definitions
const helmet = require('helmet');
app.use(helmet({
  contentSecurityPolicy: false, // disable CSP on API — set it on the frontend CDN instead
}));

Additional Notes

  • express-rate-limit uses in-memory storage by default — for a single-server Vercel deployment, this is sufficient. A Redis store (rate-limit-redis) can be added later if horizontal scaling is needed.
  • The Vercel deployment may impose its own rate limits, but these cannot be relied on for application-level protection.
  • Both express-rate-limit and helmet are the most downloaded npm security packages in their categories — they are well-maintained and require zero configuration beyond the above.
  • The 10 links/hour createLimiter applies to unauthenticated requests; authenticated users (Firebase Auth token present) could be given a higher limit in a follow-up.

Could you assign this issue to me?

Labels: security, enhancement, backend, GSSoC 2026

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions