Every Post, One Id at a Time — JavaScript Bug Hunt

Modelled on the Parler scrape (January 2021): post IDs were sequential, the API required no authentication for reads, and there was no rate limit.

  • Language: JavaScript
  • Layer: Backend
  • Difficulty: Medium
  • Concepts: Security, Rate Limiting
  • Modelled on: Parler · 2021
  • Visible tests: an authenticated caller can read a post; an anonymous caller is refused; the budget stops enumeration
  • Reward: 50 XP for a complete fix

Briefing

Modelled on the Parler scrape (January 2021): post IDs were sequential, the API required no authentication for reads, and there was no rate limit. A small group enumerated the entire site — roughly 70 TB — in a couple of days.

feed.js serves any post id to anyone as fast as they can ask.

Fix fetchPost so unauthenticated callers are refused and each client is rate limited.

Bug report

BUG-PARLER · Priority: Critical · Reported by: platform security

fetchPost(store, postId, session, buckets, limit) must return { status, post }:

  • "unauthorized" when session is missing or has no clientId
  • "rate-limited" when this client has already made limit requests
  • "not-found" when the post does not exist (this still consumes a request)
  • "ok" with the post otherwise

buckets maps clientId -> requests made so far.

Observed: no session is required and no request is counted, so the whole archive can be walked by incrementing the id.

Logs

[feed] served post 0000000001..0000998211 to one client in 41 minutes
[feed] no session, no throttle, sequential ids

The code as shipped

src/feed/feed.js (editable)

exports.fetchPost = function (store, postId, session, buckets, limit) {
  var post = store[postId];
  if (!post) return { status: "not-found", post: null };
  return { status: "ok", post: post };
};

Read-only context: src/feed/POLICY.js.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.