File size: 4,133 Bytes
7f6dd09 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | import axios from "axios";
import config from "../config.js";
import supabase from "../lib/supabase.js";
import { getJob, updateJobStatus } from "./jobService.js";
const INJECTION_KEYWORDS = [
"ignore",
"override",
"set score",
"approve this",
"reject this",
"system prompt",
"you are now",
"disregard",
"forget previous",
"new instructions",
"jailbreak",
"ignore all",
"ignore previous",
];
function computeDecision(score) {
if (score >= 75) return "APPROVE";
if (score >= 55) return "CONDITIONAL";
return "REJECT";
}
async function processOfficerNotes(jobId, rawNotes, officerId) {
// Step A β Sanitize
let sanitizedNotes = rawNotes.trim();
if (sanitizedNotes.length > 2000) {
throw new Error("Notes too long. Maximum 2000 characters.");
}
// Strip any HTML tags
sanitizedNotes = sanitizedNotes.replace(/<[^>]*>/g, "");
// Step B β Injection detection
const lower = sanitizedNotes.toLowerCase();
const injectionFound = INJECTION_KEYWORDS.some((kw) => lower.includes(kw));
if (injectionFound) {
// Log to audit_log
await supabase.from("audit_log").insert({
job_id: jobId,
event_type: "INJECTION_ATTEMPT",
event_data: { officer_id: officerId, raw_text: rawNotes },
});
// Log to officer_notes
await supabase.from("officer_notes").insert({
job_id: jobId,
raw_text: rawNotes,
injection_detected: true,
score_delta: -50,
officer_id: officerId,
});
// Fetch current score so we can report before/after
const job = await getJob(jobId);
const currentScore = job?.result?.score_breakdown?.final_score ?? 0;
const currentDecision = job?.result?.score_breakdown?.decision ?? "REJECT";
const newScore = Math.max(0, currentScore - 50);
return {
injection_detected: true,
injection_message:
"Prompt injection attempt detected. Penalty: -50 points. Incident logged to compliance.",
score_before: currentScore,
score_delta: -50,
score_after: newScore,
decision_before: currentDecision,
decision_after: computeDecision(newScore),
adjustments: [],
interpretation: "Security violation detected.",
};
}
// Step C β Fetch current score from job
const job = await getJob(jobId);
if (!job) throw new Error("Job not found");
const currentScore = job.result?.score_breakdown?.final_score ?? 0;
const currentDecision = job.result?.score_breakdown?.decision ?? "REJECT";
// Step D β Call Python AI service
const aiRes = await axios.post(
`${config.aiServiceUrl}/api/v1/cam/officer-notes`,
{
job_id: jobId,
notes_text: sanitizedNotes,
officer_id: officerId,
},
{ timeout: 60000 },
);
const aiResponse = aiRes.data;
const scoreDelta = aiResponse.score_delta ?? 0;
// Step E β Update job result in Supabase
const newScore = Math.max(0, Math.min(100, currentScore + scoreDelta));
const newDecision = computeDecision(newScore);
const updatedResult = {
...job.result,
score_breakdown: {
...job.result.score_breakdown,
final_score: newScore,
decision: newDecision,
},
officer_notes_applied: true,
officer_score_delta: scoreDelta,
};
await updateJobStatus(jobId, "completed", updatedResult);
// Step F β Insert records
await supabase.from("officer_notes").insert({
job_id: jobId,
raw_text: sanitizedNotes,
injection_detected: false,
score_delta: scoreDelta,
officer_id: officerId,
});
await supabase.from("audit_log").insert({
job_id: jobId,
event_type: "OFFICER_NOTES_SUBMITTED",
event_data: {
officer_id: officerId,
score_delta: scoreDelta,
injection_detected: false,
},
});
// Step G β Return response
return {
injection_detected: false,
score_before: currentScore,
score_delta: scoreDelta,
score_after: newScore,
decision_before: currentDecision,
decision_after: newDecision,
adjustments: aiResponse.adjustments || [],
interpretation: aiResponse.interpretation || "",
};
}
export { processOfficerNotes };
|