The Token That Never Dies — JavaScript Bug Hunt

Security review flagged that Meridian API access tokens keep working long after they expire — and freshly issued tokens are sometimes rejected at 9 AM sharp.

  • Language: JavaScript
  • Layer: Backend
  • Difficulty: Medium
  • Modelled on: JWT deployments
  • Visible tests: a fresh token is accepted; a token expired an hour ago is rejected; clock skew works in the client's favour
  • Reward: 50 XP for a complete fix

Briefing

Security review flagged that Meridian API access tokens keep working long after they expire — and freshly issued tokens are sometimes rejected at 9 AM sharp.

Token decoding and the route guard are locked. verifyToken.js owns the expiry math.

Remember: JWT exp is in seconds since epoch (RFC 7519), and the service allows 60 seconds of clock skew in the client's favour.

Bug report

BUG-5521 · Priority: Critical (security) · Reported by: pentest

  • A token with exp = one hour AGO is still accepted.
  • Tokens minted by a server whose clock is 30s ahead get rejected, even though the spec grants 60s of skew tolerance.

Contract for verifyToken(token, nowMs): returns { valid: true, userId } or { valid: false, reason }.

Logs

[guard] ALLOW userId=u_812 exp=1767261600 now=1767265200000  <- expired 1h ago!
[guard] DENY  reason=expired exp=1767268830 now=1767268800000    <- only 30s ahead

The code as shipped

src/auth/verifyToken.js (editable)

var store = require("./tokenStore");

var CLOCK_SKEW_SECONDS = 60;

// exp is SECONDS since epoch (JWT spec). nowMs is milliseconds.
exports.verifyToken = function (token, nowMs) {
  var payload = store.decode(token);

  var deadline = payload.exp - CLOCK_SKEW_SECONDS;
  if (deadline < nowMs) {
    return { valid: false, reason: "expired" };
  }

  return { valid: true, userId: payload.userId };
};

Read-only context: src/auth/guard.js, src/auth/tokenStore.js.

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