Skip to content

fix(questions): close the paid-LLM abuse gap in question generation - #100

Open
tanvishdesai wants to merge 1 commit into
vicharanashala:mainfrom
tanvishdesai:fix/question-generation-abuse
Open

fix(questions): close the paid-LLM abuse gap in question generation#100
tanvishdesai wants to merge 1 commit into
vicharanashala:mainfrom
tanvishdesai:fix/question-generation-abuse

Conversation

@tanvishdesai

Copy link
Copy Markdown

Summary

POST /questions/generate was reachable by any teacher-role account with an arbitrary transcript string — no roomId, no dedicated rate limit, and no length caps — turning a "generate quiz questions" endpoint into a de facto free-form LLM proxy on the org's paid keys. The transcript was also spliced verbatim into the prompt with no framing against prompt injection, and the model's returned question/option/explanation text had no length cap.

  • Require roomId and verify the caller owns that room (checkRoomOwnership), tying generation to an actual lesson instead of any transcript from any teacher account.
  • Add a dedicated per-user rate limiter on /generate (40/15min, Redis-backed when available), separate from the general apiLimiter which is sized for high-volume student polling, not for gating an expensive external API call.
  • Cap transcript input length (MAX_TRANSCRIPT_CHARS, env-overridable) and fence it with explicit <<<BEGIN/END SESSION CONTENT>>> delimiters plus an instruction to treat it as inert data, not commands — narrows the prompt-injection surface without changing legitimate generation quality.
  • Truncate returned question/option/explanation text so a hijacked or malfunctioning response can't balloon into unbounded stored/rendered text.

Intentionally out of scope: the student→teacher self-role-escalation via PUT /auth/role — left as-is per explicit instruction.

Test plan

  • npx jest — all 124 backend tests pass (6 new, covering the length guards, injection framing, and the roomId requirement)
  • vite build — frontend builds clean
  • Manual smoke test: generate questions from the room UI as a real teacher and confirm the existing flow still works end-to-end

🤖 Generated with Claude Code

POST /questions/generate was reachable by any teacher-role account with an
arbitrary transcript string, no roomId, and no rate limit beyond the generic
API-wide cap — turning a "generate quiz questions" endpoint into a de facto
free-form LLM proxy on the org's paid keys. The transcript was also spliced
verbatim into the prompt with no framing against prompt injection, and the
model's question/option/explanation output had no length cap.

- Require roomId and verify the caller owns that room (checkRoomOwnership),
  tying generation to an actual lesson instead of any transcript from any
  teacher account.
- Add a dedicated per-user rate limiter on /generate (40/15min), separate
  from the general apiLimiter which is sized for student polling, not for
  gating an expensive external API call.
- Cap transcript input length and fence it with explicit delimiters plus an
  instruction to treat it as inert data, not commands — narrows the
  prompt-injection surface without changing legitimate generation quality.
- Truncate returned question/option/explanation text so a hijacked or
  malfunctioning response can't balloon into unbounded stored/rendered text.

Intentionally out of scope: the student->teacher self-role-escalation via
PUT /auth/role, per explicit instruction to leave that as-is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant