62 lines
2.8 KiB
JavaScript
62 lines
2.8 KiB
JavaScript
/**
|
|||
|
|
* Text that tries to talk to the model rather than describe something.
|
||
|
|
*
|
||
|
|
* Memory is the one place where text from outside can settle permanently. The
|
||
|
|
* agent browses a page, decides a line on it is worth keeping, and saves it —
|
||
|
|
* from then on it rides into every session in every project. An injection
|
||
|
|
* anywhere else lives for one conversation; here it lives until someone
|
||
|
|
* notices.
|
||
|
|
*
|
||
|
|
* Patterns, not a model: this runs on every write and every index build, and a
|
||
|
|
* classifier there would cost more than the whole feature. That buys only the
|
||
|
|
* blunt cases, which is the honest expectation — it raises the floor rather
|
||
|
|
* than closing the door.
|
||
|
|
*
|
||
|
|
* A match never deletes anything. The entry is stored, kept out of what the
|
||
|
|
* model is shown, and flagged for the user, because a silently dropped entry
|
||
|
|
* hides the attempt from the only party who can judge it.
|
||
|
|
*/
|
||
|
|
|
||
|
|
const PATTERNS = [
|
||
|
|
// Trying to displace instructions already in play.
|
||
|
|
/\bignore\s+(?:all\s+|any\s+)?(?:previous|prior|earlier|above)\s+(?:instructions?|prompts?|rules?|context)\b/i,
|
||
|
|
/\bdisregard\s+(?:all\s+|any\s+)?(?:previous|prior|earlier|above)\s+(?:instructions?|prompts?|rules?)\b/i,
|
||
|
|
/\bforget\s+(?:everything|all)\s+(?:you|above|before)\b/i,
|
||
|
|
/\boverrid(?:e|ing)\s+(?:your\s+)?(?:system\s+)?(?:prompt|instructions?)\b/i,
|
||
|
|
|
||
|
|
// Trying to reassign who the model is.
|
||
|
|
/\byou\s+are\s+now\s+(?:a|an|the)\b/i,
|
||
|
|
/\bfrom\s+now\s+on[,\s]+(?:you|act|behave|respond)\b/i,
|
||
|
|
/\bact\s+as\s+(?:if\s+you\s+are\s+)?(?:a|an|the)\s+\w+\s+with\s+no\s+(?:restrictions?|limits?|rules?)\b/i,
|
||
|
|
|
||
|
|
// Forging turn structure so the text reads as a different speaker.
|
||
|
|
/^\s*(?:system|assistant|developer)\s*:/im,
|
||
|
|
/<\|(?:im_start|im_end|system|endoftext)\|>/i,
|
||
|
|
/\[\/?(?:INST|SYS)\]/,
|
||
|
|
|
||
|
|
// Aimed at the guardrails themselves.
|
||
|
|
/\b(?:bypass|disable|turn\s+off)\s+(?:all\s+)?(?:safety|security|guardrails?|filters?|restrictions?)\b/i,
|
||
|
|
/\bdeveloper\s+mode\s+(?:enabled|on|activated)\b/i,
|
||
|
|
|
||
|
|
// Asking for what the model was told, or for credentials to travel.
|
||
|
|
/\b(?:print|reveal|repeat|output|show)\s+(?:me\s+)?(?:your|the)\s+(?:system\s+prompt|instructions|initial\s+prompt)\b/i,
|
||
|
|
/\b(?:send|post|upload|exfiltrate)\s+(?:the\s+|your\s+)?(?:api\s+key|token|credentials?|secrets?|env)\b/i,
|
||
|
|
];
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The first pattern this text trips, or null. The name is returned rather than
|
||
|
|
* a boolean so the panel can tell the user what was matched instead of leaving
|
||
|
|
* them with an unexplained warning.
|
||
|
|
*/
|
||
|
|
export const findThreatPattern = (value) => {
|
||
|
|
if (typeof value !== 'string' || value.length === 0) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
const match = PATTERNS.find((pattern) => pattern.test(value));
|
||
|
|
return match ? match.source.slice(0, 80) : null;
|
||
|
|
};
|
||
|
|
|
||
|
|
export const looksLikeInjection = (...values) => (
|
||
|
|
values.some((value) => findThreatPattern(value) !== null)
|
||
|
|
);
|