Prompt-injection wrap: remaining AI routes
Applied the <UNTRUSTED_*> delimiter + INJECTION_GUARD pattern to:
- src/routes/sickVisit.js (chief complaint, transcript, dictation,
ROS, physical exam, diagnoses, style hints)
- src/routes/wellVisit.js (SSHADESS answers + full well-visit context)
- src/routes/chartReview.js (PMH + all visit content + labs)
- src/routes/hospitalCourse.js (all notes/H&P/ED + clarification
& update endpoints)
- src/routes/milestones.js (narrative + summary)
Each wraps patient-derived text in <UNTRUSTED_*>…</UNTRUSTED_*>
tags and appends the INJECTION_GUARD system instruction that tells
the model to treat wrapped content strictly as data. Operator-
supplied `additionalInstructions` stays unwrapped (trusted).
This commit is contained in:
parent
63f77aa9cf
commit
ea03db3d45
5 changed files with 60 additions and 90 deletions
|
|
@ -4,6 +4,7 @@ const { callAI } = require('../utils/ai');
|
|||
const PROMPTS = require('../utils/prompts');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
var logger = require('../utils/logger');
|
||||
var { wrapUserText, INJECTION_GUARD } = require('../utils/promptSafe');
|
||||
|
||||
// Generate chart review
|
||||
router.post('/generate-chart-review', authMiddleware, async (req, res) => {
|
||||
|
|
@ -21,7 +22,7 @@ router.post('/generate-chart-review', authMiddleware, async (req, res) => {
|
|||
|
||||
const today = new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
|
||||
let clinicalData = `Today's date: ${today}\nPatient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}`;
|
||||
if (pmh) clinicalData += `\nPMH: ${pmh}`;
|
||||
if (pmh) clinicalData += `\nPMH: ${wrapUserText('pmh', pmh)}`;
|
||||
|
||||
// Use the top-level review type to select the prompt —
|
||||
// the per-visit types only control data formatting, not the prompt.
|
||||
|
|
@ -37,29 +38,29 @@ router.post('/generate-chart-review', authMiddleware, async (req, res) => {
|
|||
// Include ALL visit data regardless of per-visit type —
|
||||
// an outpatient chart review should include subspecialty consults too
|
||||
(visits || []).forEach(v => {
|
||||
clinicalData += `\n\n=== OUTPATIENT VISIT (${v.date}) ===\n${v.content}`;
|
||||
if (v.labs && v.labs.trim()) clinicalData += `\n--- Labs from this visit (${v.date}) ---\n${v.labs}`;
|
||||
clinicalData += `\n\n=== OUTPATIENT VISIT (${v.date}) ===\n` + wrapUserText('visit', v.content || '');
|
||||
if (v.labs && v.labs.trim()) clinicalData += `\n--- Labs from this visit (${v.date}) ---\n` + wrapUserText('labs', v.labs);
|
||||
});
|
||||
(subspecialty || []).forEach(s => {
|
||||
clinicalData += `\n\n=== SUBSPECIALTY: ${(s.specialty || 'Subspecialty').toUpperCase()} — ${s.specialistName || 'Unknown'} (${s.date}) ===\n${s.content}`;
|
||||
if (s.labs && s.labs.trim()) clinicalData += `\n--- Labs from this visit (${s.date}) ---\n${s.labs}`;
|
||||
clinicalData += `\n\n=== SUBSPECIALTY: ${(s.specialty || 'Subspecialty').toUpperCase()} — ${s.specialistName || 'Unknown'} (${s.date}) ===\n` + wrapUserText('consult', s.content || '');
|
||||
if (s.labs && s.labs.trim()) clinicalData += `\n--- Labs from this visit (${s.date}) ---\n` + wrapUserText('labs', s.labs);
|
||||
});
|
||||
(edVisits || []).forEach(v => {
|
||||
clinicalData += `\n\n=== ED VISIT (${v.date}) ===\n${v.content}`;
|
||||
if (v.labs && v.labs.trim()) clinicalData += `\n--- Labs from this visit (${v.date}) ---\n${v.labs}`;
|
||||
clinicalData += `\n\n=== ED VISIT (${v.date}) ===\n` + wrapUserText('ed_visit', v.content || '');
|
||||
if (v.labs && v.labs.trim()) clinicalData += `\n--- Labs from this visit (${v.date}) ---\n` + wrapUserText('labs', v.labs);
|
||||
});
|
||||
|
||||
if (labs && labs.length > 0) {
|
||||
clinicalData += '\n\n=== ADDITIONAL LABS (not tied to a specific visit) ===';
|
||||
labs.forEach(l => { clinicalData += `\n${l.date ? l.date + ': ' : ''}${l.values}`; });
|
||||
labs.forEach(l => { clinicalData += `\n${l.date ? l.date + ': ' : ''}` + wrapUserText('labs', l.values || ''); });
|
||||
}
|
||||
|
||||
if (additionalInstructions) {
|
||||
prompt += `\n\nADDITIONAL INSTRUCTIONS:\n${additionalInstructions}`;
|
||||
prompt += `\n\nADDITIONAL INSTRUCTIONS (operator-supplied, trusted):\n${additionalInstructions}`;
|
||||
}
|
||||
|
||||
const result = await callAI([
|
||||
{ role: 'system', content: prompt },
|
||||
{ role: 'system', content: prompt + INJECTION_GUARD },
|
||||
{ role: 'user', content: clinicalData }
|
||||
], { model, maxTokens: 4000 });
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const { callAI } = require('../utils/ai');
|
|||
const PROMPTS = require('../utils/prompts');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
var logger = require('../utils/logger');
|
||||
var { wrapUserText, INJECTION_GUARD } = require('../utils/promptSafe');
|
||||
|
||||
// Generate hospital course
|
||||
router.post('/generate-hospital-course', authMiddleware, async (req, res) => {
|
||||
|
|
@ -51,41 +52,35 @@ router.post('/generate-hospital-course', authMiddleware, async (req, res) => {
|
|||
|
||||
// Build the complete clinical data
|
||||
let clinicalData = `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}`;
|
||||
if (pmh) clinicalData += `\nPMH: ${pmh}`;
|
||||
if (pmh) clinicalData += `\nPMH: ` + wrapUserText('pmh', pmh);
|
||||
if (setting) clinicalData += `\nSetting: ${setting}`;
|
||||
if (los) clinicalData += `\nLength of Stay: ${los} days`;
|
||||
clinicalData += `\nFormat: ${format}`;
|
||||
|
||||
if (edNote) {
|
||||
clinicalData += `\n\n=== ED NOTE (${edNote.date || 'date unknown'}) ===\n${edNote.content}`;
|
||||
if (edNote.labs) clinicalData += `\nED Labs: ${edNote.labs}`;
|
||||
clinicalData += `\n\n=== ED NOTE (${edNote.date || 'date unknown'}) ===\n` + wrapUserText('ed_note', edNote.content || '');
|
||||
if (edNote.labs) clinicalData += `\nED Labs: ` + wrapUserText('labs', edNote.labs);
|
||||
}
|
||||
if (hAndP) clinicalData += `\n\n=== H&P (${hAndP.date || 'date unknown'}) ===\n` + wrapUserText('h_and_p', hAndP.content || '');
|
||||
|
||||
if (hAndP) {
|
||||
clinicalData += `\n\n=== H&P (${hAndP.date || 'date unknown'}) ===\n${hAndP.content}`;
|
||||
}
|
||||
|
||||
// Sort notes by date
|
||||
const sortedNotes = [...notes].sort((a, b) => new Date(a.date) - new Date(b.date));
|
||||
sortedNotes.forEach((note, i) => {
|
||||
clinicalData += `\n\n=== ${(note.type || 'Progress Note').toUpperCase()} - ${note.date || `Note ${i + 1}`} ===\n${note.content}`;
|
||||
clinicalData += `\n\n=== ${(note.type || 'Progress Note').toUpperCase()} - ${note.date || `Note ${i + 1}`} ===\n` + wrapUserText('note', note.content || '');
|
||||
});
|
||||
|
||||
if (labs && labs.length > 0) {
|
||||
clinicalData += '\n\n=== LABS ===';
|
||||
labs.forEach(lab => {
|
||||
clinicalData += `\n${lab.date}: ${lab.values}`;
|
||||
});
|
||||
labs.forEach(lab => { clinicalData += `\n${lab.date}: ` + wrapUserText('labs', lab.values || ''); });
|
||||
}
|
||||
|
||||
if (additionalInstructions) {
|
||||
prompt += `\n\nADDITIONAL INSTRUCTIONS FROM PHYSICIAN:\n${additionalInstructions}`;
|
||||
prompt += `\n\nADDITIONAL INSTRUCTIONS FROM PHYSICIAN (operator-supplied, trusted):\n${additionalInstructions}`;
|
||||
}
|
||||
|
||||
if (physicianMemories) clinicalData += '\n\n[STYLE HINTS (low priority — only apply if relevant to the current note, never copy content from these examples)]\n' + physicianMemories + '\n[END STYLE HINTS]';
|
||||
if (physicianMemories) clinicalData += '\n\n[STYLE HINTS (low priority — only apply if relevant to the current note, never copy content from these examples)]\n'
|
||||
+ wrapUserText('style_hints', physicianMemories) + '\n[END STYLE HINTS]';
|
||||
|
||||
const result = await callAI([
|
||||
{ role: 'system', content: prompt },
|
||||
{ role: 'system', content: prompt + INJECTION_GUARD },
|
||||
{ role: 'user', content: clinicalData }
|
||||
], { model, maxTokens: 6000 });
|
||||
|
||||
|
|
@ -107,8 +102,8 @@ router.post('/hospital-course-clarify', authMiddleware, async (req, res) => {
|
|||
const { currentDraft, notes } = req.body;
|
||||
|
||||
const result = await callAI([
|
||||
{ role: 'system', content: PROMPTS.askClarification },
|
||||
{ role: 'user', content: `Current draft:\n${currentDraft}\n\nSource notes:\n${JSON.stringify(notes)}` }
|
||||
{ role: 'system', content: PROMPTS.askClarification + INJECTION_GUARD },
|
||||
{ role: 'user', content: `Current draft:\n` + wrapUserText('draft', currentDraft || '') + `\n\nSource notes:\n` + wrapUserText('notes', JSON.stringify(notes || [])) }
|
||||
], { model: req.body.model });
|
||||
|
||||
res.json({ success: true, questions: result.content, model: result.model });
|
||||
|
|
@ -123,15 +118,15 @@ router.post('/hospital-course-update', authMiddleware, async (req, res) => {
|
|||
const { currentCourse, updates, model, instructions } = req.body;
|
||||
if (!currentCourse) return res.status(400).json({ error: 'No current course' });
|
||||
|
||||
let userMsg = `CURRENT HOSPITAL COURSE:\n${currentCourse}\n\n`;
|
||||
if (updates) userMsg += `NEW UPDATES TO ADD:\n${updates}\n\n`;
|
||||
if (instructions) userMsg += `INSTRUCTIONS:\n${instructions}`;
|
||||
let userMsg = `CURRENT HOSPITAL COURSE:\n` + wrapUserText('course', currentCourse) + `\n\n`;
|
||||
if (updates) userMsg += `NEW UPDATES TO ADD:\n` + wrapUserText('updates', updates) + `\n\n`;
|
||||
if (instructions) userMsg += `INSTRUCTIONS:\n` + wrapUserText('instructions', instructions);
|
||||
|
||||
const result = await callAI([
|
||||
{ role: 'system', content: `You are updating a hospital course document with new information.
|
||||
Add the new information in the appropriate place. Maintain the same format and style.
|
||||
If adding discharge day information, add it at the end.
|
||||
${PROMPTS.refine.split('\n').slice(0, -1).join('\n')}` },
|
||||
${PROMPTS.refine.split('\n').slice(0, -1).join('\n')}` + INJECTION_GUARD },
|
||||
{ role: 'user', content: userMsg }
|
||||
], { model, maxTokens: 6000 });
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ const PROMPTS = require('../utils/prompts');
|
|||
const { authMiddleware } = require('../middleware/auth');
|
||||
const db = require('../db/database');
|
||||
var logger = require('../utils/logger');
|
||||
var { wrapUserText, INJECTION_GUARD } = require('../utils/promptSafe');
|
||||
|
||||
// Get all milestones for client (authenticated users)
|
||||
router.get('/milestones-data', authMiddleware, async (req, res) => {
|
||||
|
|
@ -59,8 +60,8 @@ router.post('/generate-milestone-narrative', authMiddleware, async (req, res) =>
|
|||
const prompt = (format === 'list') ? PROMPTS.milestoneList : PROMPTS.milestoneNarrative;
|
||||
|
||||
const result = await callAI([
|
||||
{ role: 'system', content: prompt },
|
||||
{ role: 'user', content: `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}\nAge Group: ${ageGroup}\n\n${milestoneText}\n\nAssessed: ${achieved.length + notAchieved.length} | Achieved: ${achieved.length} | Not achieved: ${notAchieved.length} | Not assessed (OMIT): ${notAssessed.length}` }
|
||||
{ role: 'system', content: prompt + INJECTION_GUARD },
|
||||
{ role: 'user', content: `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}\nAge Group: ${ageGroup}\n\n` + wrapUserText('milestones', milestoneText) + `\n\nAssessed: ${achieved.length + notAchieved.length} | Achieved: ${achieved.length} | Not achieved: ${notAchieved.length} | Not assessed (OMIT): ${notAssessed.length}` }
|
||||
], { model });
|
||||
|
||||
res.json({
|
||||
|
|
@ -82,8 +83,8 @@ router.post('/generate-milestone-summary', authMiddleware, async (req, res) => {
|
|||
if (!narrative) return res.status(400).json({ error: 'No narrative' });
|
||||
|
||||
const result = await callAI([
|
||||
{ role: 'system', content: PROMPTS.milestoneSummary },
|
||||
{ role: 'user', content: `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}\nAge Group: ${ageGroup}\n\nNARRATIVE:\n${narrative}` }
|
||||
{ role: 'system', content: PROMPTS.milestoneSummary + INJECTION_GUARD },
|
||||
{ role: 'user', content: `Patient: ${patientAge || 'Unknown'}, ${patientGender || 'Unknown'}\nAge Group: ${ageGroup}\n\nNARRATIVE:\n` + wrapUserText('narrative', narrative) }
|
||||
], { model, maxTokens: 500 });
|
||||
|
||||
res.json({ success: true, summary: result.content, model: result.model });
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ var { callAI } = require('../utils/ai');
|
|||
var PROMPTS = require('../utils/prompts');
|
||||
var { authMiddleware } = require('../middleware/auth');
|
||||
var logger = require('../utils/logger');
|
||||
var { wrapUserText, INJECTION_GUARD } = require('../utils/promptSafe');
|
||||
|
||||
// ── POST generate sick visit note ────────────────────────────────────────
|
||||
router.post('/sick-visit/note', authMiddleware, async function(req, res) {
|
||||
|
|
@ -28,32 +29,23 @@ router.post('/sick-visit/note', authMiddleware, async function(req, res) {
|
|||
// Assemble context
|
||||
var context = 'SICK VISIT\n';
|
||||
context += 'Patient: ' + (patientAge || 'Unknown age') + ', ' + (patientGender || 'Unknown gender') + '\n';
|
||||
context += 'Chief Complaint: ' + chiefComplaint + '\n\n';
|
||||
context += 'Chief Complaint: ' + wrapUserText('chief_complaint', chiefComplaint) + '\n\n';
|
||||
|
||||
if (transcript) {
|
||||
context += 'ENCOUNTER TRANSCRIPT/DICTATION:\n' + transcript + '\n\n';
|
||||
context += 'ENCOUNTER TRANSCRIPT/DICTATION:\n' + wrapUserText('transcript', transcript) + '\n\n';
|
||||
} else if (dictation) {
|
||||
context += 'PHYSICIAN DICTATION:\n' + dictation + '\n\n';
|
||||
context += 'PHYSICIAN DICTATION:\n' + wrapUserText('dictation', dictation) + '\n\n';
|
||||
}
|
||||
|
||||
if (ros) {
|
||||
context += ros + '\n\n';
|
||||
}
|
||||
|
||||
if (physicalExam) {
|
||||
context += physicalExam + '\n\n';
|
||||
}
|
||||
|
||||
if (diagnoses) {
|
||||
context += diagnoses + '\n\n';
|
||||
}
|
||||
|
||||
if (ros) context += wrapUserText('ros', ros) + '\n\n';
|
||||
if (physicalExam) context += wrapUserText('physical_exam', physicalExam) + '\n\n';
|
||||
if (diagnoses) context += wrapUserText('diagnoses', diagnoses) + '\n\n';
|
||||
if (physicianMemories) {
|
||||
context += '[STYLE HINTS (low priority — only apply if relevant to the current note, never copy content from these examples)]\n' + physicianMemories + '\n[END STYLE HINTS]\n\n';
|
||||
context += '[STYLE HINTS (low priority — only apply if relevant to the current note, never copy content from these examples)]\n'
|
||||
+ wrapUserText('style_hints', physicianMemories) + '\n[END STYLE HINTS]\n\n';
|
||||
}
|
||||
|
||||
var result = await callAI([
|
||||
{ role: 'system', content: PROMPTS.sickVisitNote },
|
||||
{ role: 'system', content: PROMPTS.sickVisitNote + INJECTION_GUARD },
|
||||
{ role: 'user', content: context }
|
||||
], { model });
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ var { callAI } = require('../utils/ai');
|
|||
var PROMPTS = require('../utils/prompts');
|
||||
var { authMiddleware } = require('../middleware/auth');
|
||||
var logger = require('../utils/logger');
|
||||
var { wrapUserText, INJECTION_GUARD } = require('../utils/promptSafe');
|
||||
|
||||
// Load growth reference and BMI classification data
|
||||
var scheduleData;
|
||||
|
|
@ -112,8 +113,8 @@ router.post('/well-visit/shadess', authMiddleware, async function(req, res) {
|
|||
}
|
||||
|
||||
var result = await callAI([
|
||||
{ role: 'system', content: PROMPTS.shadessAssessment },
|
||||
{ role: 'user', content: inputText }
|
||||
{ role: 'system', content: PROMPTS.shadessAssessment + INJECTION_GUARD },
|
||||
{ role: 'user', content: wrapUserText('sshadess_answers', inputText) }
|
||||
], { model });
|
||||
|
||||
var dur = Date.now() - start;
|
||||
|
|
@ -155,40 +156,20 @@ router.post('/well-visit/note', authMiddleware, async function(req, res) {
|
|||
var context = 'WELL CHILD VISIT\n';
|
||||
context += 'Patient: ' + (patientAge || visitAge) + ', ' + (patientGender || 'Unknown') + '\n\n';
|
||||
|
||||
if (vitals) {
|
||||
context += 'VITAL SIGNS:\n' + vitals + '\n\n';
|
||||
}
|
||||
if (measurements) {
|
||||
context += 'MEASUREMENTS/GROWTH:\n' + measurements + '\n\n';
|
||||
}
|
||||
if (parentConcerns) {
|
||||
context += 'PARENT/PATIENT CONCERNS:\n' + parentConcerns + '\n\n';
|
||||
}
|
||||
if (transcript) {
|
||||
context += 'ENCOUNTER TRANSCRIPT/DICTATION:\n' + transcript + '\n\n';
|
||||
} else if (dictation) {
|
||||
context += 'PHYSICIAN DICTATION:\n' + dictation + '\n\n';
|
||||
}
|
||||
if (screenings) {
|
||||
context += 'SCREENINGS COMPLETED:\n' + screenings + '\n\n';
|
||||
}
|
||||
if (vaccines) {
|
||||
context += 'IMMUNIZATIONS TODAY:\n' + vaccines + '\n\n';
|
||||
}
|
||||
if (shadessAssessment) {
|
||||
context += 'SSHADESS PSYCHOSOCIAL ASSESSMENT:\n' + shadessAssessment + '\n\n';
|
||||
}
|
||||
if (ros) {
|
||||
context += ros + '\n\n';
|
||||
}
|
||||
if (physicalExam) {
|
||||
context += physicalExam + '\n\n';
|
||||
}
|
||||
if (diagnoses) {
|
||||
context += diagnoses + '\n\n';
|
||||
}
|
||||
if (vitals) context += 'VITAL SIGNS:\n' + wrapUserText('vitals', vitals) + '\n\n';
|
||||
if (measurements) context += 'MEASUREMENTS/GROWTH:\n' + wrapUserText('measurements', measurements) + '\n\n';
|
||||
if (parentConcerns) context += 'PARENT/PATIENT CONCERNS:\n' + wrapUserText('concerns', parentConcerns) + '\n\n';
|
||||
if (transcript) context += 'ENCOUNTER TRANSCRIPT/DICTATION:\n' + wrapUserText('transcript', transcript) + '\n\n';
|
||||
else if (dictation) context += 'PHYSICIAN DICTATION:\n' + wrapUserText('dictation', dictation) + '\n\n';
|
||||
if (screenings) context += 'SCREENINGS COMPLETED:\n' + wrapUserText('screenings', screenings) + '\n\n';
|
||||
if (vaccines) context += 'IMMUNIZATIONS TODAY:\n' + wrapUserText('vaccines', vaccines) + '\n\n';
|
||||
if (shadessAssessment) context += 'SSHADESS PSYCHOSOCIAL ASSESSMENT:\n' + wrapUserText('sshadess', shadessAssessment) + '\n\n';
|
||||
if (ros) context += wrapUserText('ros', ros) + '\n\n';
|
||||
if (physicalExam) context += wrapUserText('physical_exam', physicalExam) + '\n\n';
|
||||
if (diagnoses) context += wrapUserText('diagnoses', diagnoses) + '\n\n';
|
||||
if (physicianMemories) {
|
||||
context += '[STYLE HINTS (low priority — only apply if relevant to the current note, never copy content from these examples)]\n' + physicianMemories + '\n[END STYLE HINTS]\n\n';
|
||||
context += '[STYLE HINTS (low priority — only apply if relevant to the current note, never copy content from these examples)]\n'
|
||||
+ wrapUserText('style_hints', physicianMemories) + '\n[END STYLE HINTS]\n\n';
|
||||
}
|
||||
|
||||
// Add growth reference and feeding guidance for this age
|
||||
|
|
@ -214,7 +195,7 @@ router.post('/well-visit/note', authMiddleware, async function(req, res) {
|
|||
var prompt = (noteStyle === 'short') ? PROMPTS.wellVisitShort : PROMPTS.wellVisitNote;
|
||||
|
||||
var result = await callAI([
|
||||
{ role: 'system', content: prompt },
|
||||
{ role: 'system', content: prompt + INJECTION_GUARD },
|
||||
{ role: 'user', content: context }
|
||||
], { model });
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue