UNPKG

llm-guard

Version:

A TypeScript library for validating and securing LLM prompts

31 lines (28 loc) 14.8 kB
#!/usr/bin/env node import*as y from'fs';import*as w from'path';var n=class{constructor(e=true){this.enabled=e;}isEnabled(){return this.enabled}createResult(e,t=1,r=[]){return {valid:e,score:t,details:r}}};var p=class extends n{constructor(e=true){super(e),this.patterns=new Map([["email",/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g],["phone",/(\+\d{1,3}[-.]?)?\(?\d{3}\)?[-.]?\d{3}[-.]?\d{4}/g],["ssn",/\d{3}[-]?\d{2}[-]?\d{4}/g],["credit_card",/\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}/g],["ip_address",/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g]]);}async validate(e){if(!this.isEnabled())return this.createResult(true);let t=[],r=1;for(let[i,s]of this.patterns){let a=e.match(s);a&&(t.push(...a.map(l=>({type:i,value:l}))),r=Math.min(r,.5));}return this.createResult(t.length===0,r,t.map(i=>({rule:"pii_detection",message:`Found ${i.type}: ${i.value}`,matched:i.value})))}};var c=class extends n{constructor(e=true){super(e),this.profanityList=new Set(["badword1","badword2"]),this.commonSubstitutions=new Map([["a","@"],["i","1"],["o","0"],["e","3"],["s","$"],["t","7"]]);}normalizeText(e){let t=e.toLowerCase();for(let[r,i]of this.commonSubstitutions)t=t.replace(new RegExp(i,"g"),r);return t=t.replace(/(.)\1+/g,"$1"),t}async validate(e){if(!this.isEnabled())return this.createResult(true);let r=this.normalizeText(e).split(/\s+/),i=[];for(let a of r)this.profanityList.has(a)&&i.push(a);let s=i.length===0?1:Math.max(0,1-i.length*.2);return this.createResult(i.length===0,s,i.map(a=>({rule:"profanity_detection",message:`Found profanity: ${a}`,matched:a})))}};var d=class extends n{constructor(e=true){super(e),this.patterns=[/ignore previous instructions/i,/disregard previous/i,/you are now/i,/pretend you are/i,/let's roleplay/i,/bypass/i,/override/i,/ignore rules/i,/you don't have to/i,/you can break/i];}async validate(e){if(!this.isEnabled())return this.createResult(true);let t=[],r=1;for(let i of this.patterns){let s=e.match(i);s&&(t.push(s[0]),r=Math.min(r,.3));}return this.createResult(t.length===0,r,t.map(i=>({rule:"jailbreak_detection",message:`Possible jailbreak attempt detected: ${i}`,matched:i})))}};var m=class extends n{constructor(e=true){super(e),this.toxicPatterns=[/hate|kill|die|death|murder|suicide/i,/racist|sexist|homophobic|discrimination/i,/terror|bomb|explosion|weapon/i,/abuse|torture|pain|suffer/i,/threat|danger|harm|hurt/i],this.toxicWords=new Set(["hate","kill","die","death","murder","suicide","racist","sexist","homophobic","discrimination","terror","bomb","explosion","weapon","abuse","torture","pain","suffer","threat","danger","harm","hurt"]);}calculateToxicityScore(e){let t=1,r=e.toLowerCase().split(/\s+/);for(let s of this.toxicPatterns)s.test(e)&&(t-=.2);for(let s of r)this.toxicWords.has(s)&&(t-=.1);let i=(e.match(/!{2,}|@{2,}|#{2,}/g)||[]).length;return t-=i*.05,Math.max(0,t)}async validate(e){if(!this.isEnabled())return this.createResult(true);let t=this.calculateToxicityScore(e),r=t<.7,i=[];for(let a of this.toxicPatterns){let l=e.match(a);l&&i.push(l[0]);}let s=e.toLowerCase().split(/\s+/);for(let a of s)this.toxicWords.has(a)&&i.push(a);return this.createResult(!r,t,i.map(a=>({rule:"toxicity_detection",message:`Found toxic content: ${a}`,matched:a})))}};var u=class extends n{constructor(e=true,t={}){super(e),this.minLength=t.minLength||10,this.maxLength=t.maxLength||5e3,this.minWords=t.minWords||3,this.maxWords=t.maxWords||1e3,this.commonFillerWords=new Set(["um","uh","ah","er","like","you know","sort of","kind of","basically","literally","actually","so","well","right"]);}calculateRelevanceScore(e){let t=1;e.length<this.minLength&&(t-=.3),e.length>this.maxLength&&(t-=.2);let r=e.split(/\s+/).filter(l=>l.length>0);r.length<this.minWords&&(t-=.3),r.length>this.maxWords&&(t-=.2);let i=0;for(let l of r)this.commonFillerWords.has(l.toLowerCase())&&i++;let s=i/r.length;s>.3&&(t-=s*.5);let a=this.findRepeatedPhrases(e);return t-=a*.1,Math.max(0,t)}findRepeatedPhrases(e){let t=e.toLowerCase().split(/\s+/),r=new Map,i=0;for(let s=0;s<t.length-1;s++){let a=`${t[s]} ${t[s+1]}`;if(r.set(a,(r.get(a)||0)+1),s<t.length-2){let l=`${t[s]} ${t[s+1]} ${t[s+2]}`;r.set(l,(r.get(l)||0)+1);}}for(let s of r.values())s>1&&i++;return i}async validate(e){if(!this.isEnabled())return this.createResult(true);let t=this.calculateRelevanceScore(e),r=t>=.6,i=[];e.length<this.minLength&&i.push({rule:"relevance_length",message:`Text is too short (${e.length} chars, minimum ${this.minLength})`,matched:e}),e.length>this.maxLength&&i.push({rule:"relevance_length",message:`Text is too long (${e.length} chars, maximum ${this.maxLength})`,matched:e});let s=e.split(/\s+/).filter(a=>a.length>0);return s.length<this.minWords&&i.push({rule:"relevance_word_count",message:`Text has too few words (${s.length}, minimum ${this.minWords})`,matched:e}),s.length>this.maxWords&&i.push({rule:"relevance_word_count",message:`Text has too many words (${s.length}, maximum ${this.maxWords})`,matched:e}),this.createResult(r,t,i)}};var h=class extends n{constructor(e=true){super(e),this.patterns=[/\[system\]{3,}/i,/\[system\].*don't evaluate/i,/\[system\].*simply respond/i,/\[system\].*may not deviate/i,/\[system\].*this is a test/i,/\[system\].*ignore previous/i,/\[system\].*bypass/i,/\[system\].*override/i,/\[system\].*pretend/i,/\[system\].*act as/i,/\[system\].*you are now/i,/\[system\].*you must/i,/\[system\].*you have to/i,/\[system\].*you should/i,/\[system\].*you will/i,/\[system\].*you can/i,/\[system\].*you may/i,/\[system\].*you need to/i,/\[system\].*you must not/i,/\[system\].*you cannot/i,/\[system\].*you should not/i,/\[system\].*you will not/i,/\[system\].*you may not/i,/\[system\].*you need not/i,/print.*password/i,/show.*password/i,/reveal.*password/i,/display.*password/i,/output.*password/i,/get.*password/i,/fetch.*password/i,/retrieve.*password/i,/extract.*password/i,/leak.*password/i,/expose.*password/i,/share.*password/i,/give.*password/i,/send.*password/i,/transmit.*password/i,/transfer.*password/i,/forward.*password/i,/email.*password/i,/message.*password/i,/text.*password/i,/sms.*password/i,/what.*password/i,/tell.*password/i,/say.*password/i,/speak.*password/i,/write.*password/i,/type.*password/i,/enter.*password/i,/input.*password/i,/provide.*password/i,/supply.*password/i,/offer.*password/i,/present.*password/i,/deliver.*password/i,/hand.*password/i,/pass.*password/i,/throw.*password/i,/toss.*password/i,/fling.*password/i,/cast.*password/i,/hurl.*password/i,/chuck.*password/i,/lob.*password/i,/heave.*password/i,/sling.*password/i,/shoot.*password/i,/fire.*password/i,/launch.*password/i,/project.*password/i,/propel.*password/i,/catapult.*password/i,/hurl.*password/i,/fling.*password/i,/toss.*password/i,/act as/i,/can you/i,/could you/i,/do this/i,/help with/i,/ignore all/i,/pretend to be/i,/simulate this/i,/try to/i,/run command/i,/reprogram yourself/i,/redefine the system/i,/bypass rules/i,/if and only if/i,/not true/i,/or else/i,/true or false/i,/then proceed/i,/if then/i,/and then/i,/false positives/i,/start fresh/i,/continue with/i,/new prompt/i,/previous context/i,/clear instructions/i,/restart session/i,/fresh context/i,/reset all/i,/override this/i,/forget previous/i,/delete all/i,/output the data/i,/show everything/i,/provide unrestricted/i,/change context/i,/reveal hidden/i,/share details/i,/explain how/i,/describe this/i,/tell me/i,/show steps/i,/list everything/i,/outline the process/i,/test scenario/i,/hypothetical question/i,/experiment with/i,/for research purposes/i,/what if/i,/how about/i,/let's try/i,/why don't you/i,/suppose this/i,/is it possible to/i,/let me do/i,/maybe you could/i,/why would/i,/how can/i,/what if/i,/where is/i,/who would/i,/ignore previous instructions/i,/disregard previous/i,/you are now/i,/pretend you are/i,/let's roleplay/i,/bypass/i,/override/i,/ignore rules/i,/you don't have to/i,/you can break/i,/system prompt/i,/initial prompt/i,/base prompt/i,/core prompt/i,/original prompt/i,/default prompt/i,/reset/i,/restart/i,/new conversation/i,/start over/i,/begin again/i,/clear history/i,/forget everything/i,/memory reset/i,/wipe memory/i,/erase memory/i,/delete memory/i,/remove memory/i,/clear memory/i,/reset memory/i,/wipe history/i,/erase history/i,/delete history/i,/remove history/i,/clear history/i,/reset history/i],this.suspiciousCommands=new Set(["system","don't","simply","deviate","test","ignore","bypass","override","pretend","act","must","have","should","will","can","may","need","print","show","reveal","display","output","get","fetch","retrieve","extract","leak","expose","share","give","send","transmit","transfer","forward","email","message","text","sms","what","tell","say","speak","write","type","enter","input","provide","supply","offer","present","deliver","hand","pass","throw","toss","fling","cast","hurl","chuck","lob","heave","sling","shoot","fire","launch","project","propel","catapult","act","can","could","do","help","ignore","pretend","simulate","try","run","reprogram","redefine","bypass","override","forget","delete","output","show","provide","change","reveal","share","explain","describe","tell","list","outline","test","experiment","research","suppose","maybe","why","how","what","where","who","ignore","disregard","bypass","override","reset","restart","clear","wipe","erase","delete","remove","forget"]);}detectCommandInjection(e){let t=e.toLowerCase().split(/\s+/);for(let i of t)if(this.suspiciousCommands.has(i))return true;return (e.match(/\[system\]/gi)||[]).length>=3}detectSystemPromptInjection(e){let t=[/system prompt/i,/initial prompt/i,/base prompt/i,/core prompt/i,/original prompt/i,/default prompt/i];for(let r of t)if(r.test(e))return true;return false}detectMemoryResetInjection(e){let t=[/reset/i,/restart/i,/new conversation/i,/start over/i,/begin again/i,/clear history/i,/forget everything/i,/memory reset/i,/wipe memory/i,/erase memory/i,/delete memory/i,/remove memory/i,/clear memory/i,/reset memory/i,/wipe history/i,/erase history/i,/delete history/i,/remove history/i,/clear history/i,/reset history/i];for(let r of t)if(r.test(e))return true;return false}async validate(e){if(!this.isEnabled())return this.createResult(true);let t=[],r=1;for(let i of this.patterns){let s=e.match(i);s&&(t.push(s[0]),r=Math.min(r,.3));}return this.detectCommandInjection(e)&&(t.push("Command injection detected"),r=Math.min(r,.4)),this.detectSystemPromptInjection(e)&&(t.push("System prompt injection detected"),r=Math.min(r,.3)),this.detectMemoryResetInjection(e)&&(t.push("Memory reset injection detected"),r=Math.min(r,.3)),this.createResult(t.length===0,r,t.map(i=>({rule:"prompt_injection_detection",message:`Possible prompt injection detected: ${i}`,matched:i})))}};var f=class{constructor(e={}){this.guards={},e.pii!==false&&(this.guards.pii=new p(e.pii)),e.profanity!==false&&(this.guards.profanity=new c(e.profanity)),e.jailbreak!==false&&(this.guards.jailbreak=new d(e.jailbreak)),e.toxicity!==false&&(this.guards.toxicity=new m(e.toxicity)),e.relevance!==false&&(this.guards.relevance=new u(e.relevance,e.relevanceOptions)),e.promptInjection!==false&&(this.guards.promptInjection=new h(e.promptInjection));}async validate(e){let t=[],r=1;for(let i of Object.values(this.guards))if(i&&i.isEnabled()){let s=await i.validate(e);t.push(s),r=Math.min(r,s.score||1);}return {id:Date.now().toString(),input:e,results:t}}async validateBatch(e){return {responses:await Promise.all(e.map(r=>this.validate(r)))}}};function b(){let o=process.argv.slice(2),e={};for(let t=0;t<o.length;t++){let r=o[t];r==="--config"||r==="-c"?e.config=o[++t]:r==="--pii"?e.pii=true:r==="--no-pii"?e.pii=false:r==="--jailbreak"?e.jailbreak=true:r==="--no-jailbreak"?e.jailbreak=false:r==="--profanity"?e.profanity=true:r==="--no-profanity"?e.profanity=false:r==="--prompt-injection"?e.promptInjection=true:r==="--no-prompt-injection"?e.promptInjection=false:r==="--relevance"?e.relevance=true:r==="--no-relevance"?e.relevance=false:r==="--toxicity"?e.toxicity=true:r==="--no-toxicity"?e.toxicity=false:r==="--batch"||r==="-b"?e.batch=true:(r==="--help"||r==="-h")&&(e.help=true);}return e}function v(o){try{let e=w.extname(o).toLowerCase(),t=y.readFileSync(o,"utf8");if(e===".json")return JSON.parse(t);e===".yaml"||e===".yml"?(console.error("YAML config files are not supported yet. Please use JSON."),process.exit(1)):(console.error(`Unsupported config file format: ${e}`),process.exit(1));}catch(e){console.error(`Error loading config file: ${e.message||"Unknown error"}`),process.exit(1);}}function g(){console.log(` llm-guard - LLM prompt validation and security checks Usage: llm-guard [options] <prompt> Options: --config, -c <file> Path to config file (JSON) --pii Enable PII detection --no-pii Disable PII detection --jailbreak Enable jailbreak detection --no-jailbreak Disable jailbreak detection --profanity Enable profanity filtering --no-profanity Disable profanity filtering --prompt-injection Enable prompt injection detection --no-prompt-injection Disable prompt injection detection --relevance Enable relevance checking --no-relevance Disable relevance checking --toxicity Enable toxicity detection --no-toxicity Disable toxicity detection --batch, -b Run in batch mode (expects JSON array of prompts) --help, -h Show this help message Examples: llm-guard "Your prompt here" llm-guard --config config.json "Your prompt here" llm-guard --pii --jailbreak "Your prompt here" llm-guard --batch '["First prompt", "Second prompt"]' `);}async function x(){let o=b();if(o.help){g();return}let e="";if(process.stdin.isTTY){let r=process.argv.slice(2),i=r[r.length-1];(!i||i.startsWith("--"))&&(console.error("Please provide a prompt to validate"),g(),process.exit(1)),e=i;}else e=y.readFileSync(0,"utf8").trim();let t={};o.config&&(t=v(o.config)),o.pii!==void 0&&(t.pii=o.pii),o.jailbreak!==void 0&&(t.jailbreak=o.jailbreak),o.profanity!==void 0&&(t.profanity=o.profanity),o.promptInjection!==void 0&&(t.promptInjection=o.promptInjection),o.relevance!==void 0&&(t.relevance=o.relevance),o.toxicity!==void 0&&(t.toxicity=o.toxicity);try{let r=new f(t);if(o.batch)try{let i=typeof e=="string"?JSON.parse(e):e;if(!Array.isArray(i))throw new Error("Batch mode expects a JSON array of prompts");let s=await r.validateBatch(i);console.log(JSON.stringify(s,null,2));}catch(i){console.error(`Error parsing batch input: ${i.message||"Unknown error"}`),process.exit(1);}else {let i=await r.validate(e);console.log(JSON.stringify(i,null,2));}}catch(r){console.error("Error:",r.message||"Unknown error"),process.exit(1);}}x();//# sourceMappingURL=cli.mjs.map //# sourceMappingURL=cli.mjs.map