fix(questions): close the paid-LLM abuse gap in question generation - #100
Open
tanvishdesai wants to merge 1 commit into
Open
fix(questions): close the paid-LLM abuse gap in question generation#100tanvishdesai wants to merge 1 commit into
tanvishdesai wants to merge 1 commit into
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
POST /questions/generatewas reachable by any teacher-role account with an arbitrarytranscriptstring — noroomId, 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.roomIdand verify the caller owns that room (checkRoomOwnership), tying generation to an actual lesson instead of any transcript from any teacher account./generate(40/15min, Redis-backed when available), separate from the generalapiLimiterwhich is sized for high-volume student polling, not for gating an expensive external API call.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.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 theroomIdrequirement)vite build— frontend builds clean🤖 Generated with Claude Code