Skip to content

fix: prevent prompt injection via systemInstruction separation#274

Open
akshara200829-lgtm wants to merge 2 commits into
Canopus-Labs:mainfrom
akshara200829-lgtm:fix/prompt-injection-system-instruction
Open

fix: prevent prompt injection via systemInstruction separation#274
akshara200829-lgtm wants to merge 2 commits into
Canopus-Labs:mainfrom
akshara200829-lgtm:fix/prompt-injection-system-instruction

Conversation

@akshara200829-lgtm

@akshara200829-lgtm akshara200829-lgtm commented Jun 21, 2026

Copy link
Copy Markdown

Related Issue

Closes #246

Summary

Fixes a structural prompt injection vulnerability in prompts.js and aiController.js, where user-supplied values (role, topic, experience, topicsToFocus, question) were interpolated directly into the same string that carried the model's output-format instructions. This allowed crafted input to override the expected response structure or attempt to leak the system prompt, and it bypassed validateAiPrompt/sanitizeAiPrompt entirely, since the exploit lived downstream of both, inside prompt construction itself.
Also fixes a crash in sanitizeAiPrompt.js (unconditional sanitization of fields that may not be present) and corrects a schema/route mismatch in aiPromptSchema.js.


Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature
  • ♻️ Refactoring
  • 📝 Documentation update
  • 🎨 UI/UX improvement
  • 🔥 Other(please describe) ______
  • Security

How Has This Been Tested?

Verified locally that prompts.js / aiController.js now build prompts as a static systemInstruction (no user data) plus a separate plain-data payload, for both generate-questions and generate-explanation routes.
Verified aiRoutes.js's /api/generate and /api/ai/generate routes now pass a systemInstruction into every candidate model in the fallback loop, hardening the open-ended-prompt route against persona override / instruction-leak attempts.
Reproduced and fixed the sanitizeAiPrompt.js crash: previously, requests omitting role/topic threw a TypeError on .replace(); each field now has an independent typeof === "string" guard before sanitizing.
Corrected aiPromptSchema.js to match the route's actual contract (role/topic are optional, not required) and added a structural character allow-list as defense-in-depth alongside the existing regex blacklist.
Confirmed end-to-end locally that a request flows through validateAiPrompt → sanitizeAiPrompt → reaches the Gemini API call correctly with systemInstruction attached (confirmed via direct SDK call bypassing Express). Hit a free-tier quota limit (limit: 0 for gemini-2.0-flash) specific to my personal API key when trying to observe the final model response, so live output wasn't directly inspected, but the full pipeline up to and including the outbound API call was verified working.


Screenshots (if applicable)

Add screenshots or videos to demonstrate the changes.


Checklist

  • My code follows the project's guidelines
  • I have tested my changes
  • I have updated documentation where necessary
  • I have linked the related issue
  • My changes do not introduce new warnings or errors

- Split static instructions from user-supplied data in prompts.js and
  aiController.js using Gemini's systemInstruction parameter, closing
  the structural injection vector described in the issue.
- Added systemInstruction to the /api/generate and /api/ai/generate
  routes in aiRoutes.js, since this route accepts open-ended prompts
  and can't use the same data/instruction split as structured routes.
- Fixed a crash in sanitizeAiPrompt.js where role/topic were sanitized
  unconditionally without checking they were present, causing a
  TypeError on requests that omit those fields.
- Corrected aiPromptSchema.js: role and topic are now optional (they
  are not part of the /api/generate route's actual contract), and
  added a structural character allow-list as defense-in-depth
  alongside the existing regex blacklist.
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.

[Security] Prompt Injection via String Interpolation in prompts.js — User-Controlled role/topic Escape Instruction Context

1 participant