Skip to content

Security: integrity/proctoring flags are client-supplied — leaderboard penalty is bypassable #93

Description

@barunaniket

What happened?

The Arena's integrity/proctoring penalty is driven by a flag count that the browser computes and sends in the submit request, and the server trusts it. So the "more than 5 flags → base points only, out of the ranked top 10" rule is trivially bypassable: a solver who sends flags: 0 is never penalized, no matter how much they paste or tab-switch.

To be clear about what already works vs. what doesn't:

  • The penalty logic itself is already implemented and correctsrc/server/leaderboard.ts (scoreChallenge) gives anyone with more than FLAG_LIMIT (5) flags only BASE_POINTS and drops them from the ranked positions.
  • The flag count is already stored in submissions.flags.
  • The gap is the source of the number: src/app/api/submit/route.ts stores body.flags verbatim (flags: typeof body.flags === "number" ? Math.max(0, Math.round(body.flags)) : 0). The count originates in src/components/cp-arena/useIntegrityMonitor.ts on the client and is sent by ArenaWorkspace in the submit body. Because the server never establishes the count itself, the penalty can be zeroed out by the client.

Steps to reproduce

  1. Open today's Problem of the Day and solve it while deliberately triggering integrity flags (paste code, switch tabs, blur the window) — normally this would flag the submission.
  2. When submitting, edit the POST /api/submit request body (dev tools, or any HTTP client) and set "flags": 0 (or omit it).
  3. The submission is recorded with 0 flags: full speed-bounty points, still eligible for the ranked top 10, despite the violations.

Affected page / URL

/api/submit (Arena solve workspace). Source: src/app/api/submit/route.ts, src/server/leaderboard.ts, src/components/cp-arena/useIntegrityMonitor.ts, src/components/cp-arena/ArenaWorkspace.tsx.

Where did you see this?

Both — the trust boundary is the same locally and on the live site.

Suggested fix — record integrity events server-side

Move the count to somewhere the client can't overwrite:

  1. Add a server endpoint (e.g. POST /api/attempt/flag) that the workspace calls when useIntegrityMonitor detects an event, keyed to the user's attempts row for today's ranked problem. The server increments a per-attempt counter (and can persist a breakdown).
  2. At submit time, derive the flag count from that server-side record, not from the request body. Stop trusting body.flags (keep it, if at all, only as an explicitly-untrusted diagnostic).
  3. Caveat / expectations: browser-observed signals (paste, tab-switch, blur) can still be suppressed by a determined user who refrains from firing them, so this raises the bar rather than being a hard guarantee. Pair it with server-side sanity checks (e.g. implausibly fast solves) and manual review of flagged sessions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions