环境设置
首先确保安装go和ollama,访问https://go.dev/doc/install和https://ollama.com/download按操作系统说明安装。安装后使用ollama下载phi3模型:
|
|
获取本文相关代码:
|
|
选择小型模型使实验室配置适用于更多硬件。运行go run main.go
后会出现终端提示,要求以顾客身份向音乐店员工提问,生成LLM输出。默认显示完整的中介LLM输出响应及其程序化截断版本。
代码描述
在GitHub仓库中,我们努力详细记录代码及其背后的思考过程。所有代码都在main.go文件中,仅使用go标准库和ollama依赖。
LLM隔离
认识到用户输入既是查询又是要处理的数据,当前思路是通过功能隔离LLM提供安全优势。我们定义了几个模型来反映在接触特权LLM之前的预期检查。用于清理的模板模型如下:
|
|
此Modelfile定义从默认LLM(phi3模型)获取LLM响应。还可以使用-model
标志指定Ollama模型库中的任何模型。虽然此模型有局限性,但我们选择参数较少的模型以便在更广泛的硬件上运行所有四个LLM实例。
SYSTEM参数尝试将LLM响应限制在我们感兴趣的领域:音乐店员工。我们还提供示例MESSAGE交换,向模型展示它与客户之间的交流可能是什么样子。这里我们只关心此LLM的布尔答案。
然后创建字符串映射以在模板中填充模型名称,并使用getModelMap()
函数更新Modelfile名称:
|
|
modelMap变量准备就绪后,使用ollama API客户端通过将modelMap的值保存到磁盘(文件名作为键)来初始化特权和隔离LLM。例如使用phi3模型时,最终得到类似phi3-is-llm-jailbreak
的文件名,ollama服务器使用initializeModels()
函数实例化模型。
第三个变量modelOptionsMSI通过使用go ollama SDK API调用提供进一步影响模型行为的方法:
|
|
这是影响模型行为的五种其他方式,描述来源于Ollama文档:
- temperature: 模型温度。增加温度将使模型回答更有创造性。默认浮点数0.8。我们在模型中重新配置为默认0.0以具有确定性(即相同的测试输入将导致LLM的相同输出)。
- seed: 设置用于生成的随机数种子。将其设置为特定数字将使模型始终为相同提示生成相同文本。默认整数0。我们将变量defaultModelSeed设置为-1用于此本地LLM CTF。
- top_k: 减少生成无意义内容的概率。较高值(例如100)将给出更多样化的答案,而较低值(例如10)将更保守。默认整数40,我们保留在llmTopK变量中,但尝试了1到1000之间的值。
- top_p: 与top-k一起工作。较高值(例如0.95)将导致更多样化的文本,而较低值(例如0.5)将生成更集中和保守的文本。默认浮点数0.9。我们将变量llmTopP设置为0.0进一步减少随机性。
- num_ctx: 设置用于生成下一个令牌的上下文窗口大小。默认整数2048。我们在变量llmContextLength中将其设置为4096。
总结到此,我们使用两层尝试限制LLM响应:Modelfile的初始配置,以及可以通过每个API调用影响的概率层,我们也试图减少过于随机的概率。在企业部署此技术的背景下,他们可能希望LLM具有创造性,但不要如此创造性以至于对客户生成不适当的响应。采用此设计方向还将帮助我们获得更可重现的结果,而不涉及发送相同提示数百次。也就是说,通过Modefile和API调用限制响应都很有趣,因为如果鼓励LLM更随机,恶意提示可能在多次尝试中成功一次。
拥有这个小框架将使本地LLM CTF从防御和攻击角度都具有交互性,因为我们可以实施防御措施以防止已识别的越狱发生,并移除防御措施以衡量更改的影响,例如在go控制器中排除确定性或使用需要更多资源的不同模型。每当程序启动时,通过比较磁盘上的当前Modelfile与go程序中的Modelfile,然后如果检测到差异,将新模型或更改的配置重新加载到内存中,对模型模板或main.go文件中的常量的更改将生效。以下是控制此行为的代码片段。
InitializeModels函数
|
|
此时,我们的模型已定义、创建并预加载到内存中使用。直接与模型交互存在风险,因此我们尝试应用传统的Web应用程序清理技术,将客户输入塑造成可接受的输入,此外还依赖隔离LLM的语义分类。
注入确定性
我们定义在处理用户输入之前应执行的两种确定性检查:包括字符允许列表和长度检查在一个正则表达式中。如果我们想移除长度限制以查看对可用越狱的影响,可以只移除{10,512}
并添加加号(+)表示任何长度都可以。然而,客户不太可能提交超过512个字符的问题,因此我们的代码默认如此:
|
|
由于处理令牌需要成本,此配置既有利于限制令牌处理产生的成本,也有利于维护LLM作为音乐店员工的预期操作。此外,正则表达式过滤了一些已知提示注入所需的字符。考虑将这些字符添加到正则表达式中以简化挑战。
LLM守门员
模型加载并有两个确定性检查后,如何将清理后的输入发送给被指示为音乐店员工的LLM,并限制响应以防止用户输入颠覆此预期功能?
一种方法是使用模型数组定义受限流程,然后简单地迭代这些模型。通过使用range子句循环数组,我们确保如果任何模型返回指示不良行为的响应,能够跳出循环。以下是我们创建的字符串数组,用于编码调用LLM的顺序:
|
|
定义字符串数组后,我们可以创建一些辅助函数,向本地LLM CTF的玩家显示当前流程步骤、用户输入和每个LLM的输出。我们没有使用其他依赖,而是以严格的方式为终端格式化文本;如果终端文本足够小,它可以完成工作。或者,尝试go run main.go -outputmode plain
以获取没有ANSI终端序列的输出。
以下是定义模型并将文本作为顾客发送给音乐店员工后发生的深入描述:
- 我们将确定性清理的输入传递给第一个LLM,其中变量m是模型
phi3-is-llm-jailbreak
,userInput包含我们已经执行长度和正则表达式检查的输入。我们使用getLlmResponse
函数通过JSON将模型和用户输入传递给ollama服务器。然后将ollama API的响应存储在resp变量中。 - 收到LLM的响应后,我们使用
llmToBool
函数将resp变量转换为bool类型,该函数返回强类型的true或false,如果类型转换失败则返回错误。 - 如果返回错误或isJailbreak为true,我们出错、重置交互、将行为分数增加1,并跳出modelFlow以在音乐店员工斥责后重新开始客户交互。
- 否则,我们继续到数组中的下一个索引,即下一个模型
phi3-is-valid-question
。步骤1到3再次发生于此第二个LLM,它将确认客户问题是否有效。 - 一旦客户问题被分类为既不是LLM越狱又是有效的客户问题,客户输入继续到我们的第三个LLM
phi3-genie-knowledgebase
,它可以访问敏感功能,例如CRM或秘密。我们只在此步骤中获取输出并将其存储在更广泛作用域的变量genie中(它在循环之前创建)。 - 虽然不在下面的代码中,但当生成此响应时,只有此响应的内容附加到我们的llmContext变量,通过将返回的上下文附加到我们的整数数组llmContext来控制对话记忆。稍后更多。
phi3-genie-knowledgebase
LLM的输出传递给我们的最终LLMphi3-is-patron-appropriate
,以确定我们的genie输出是否适合我们的客户。步骤1到3再次发生,将此LLM的响应转换为强类型bool值,然后检查其值是否为true。- 在函数
checkLLMOutput
中有一个额外的预防措施,检查genie响应中是否存在字符串"secret",如果检测到则导致被音乐店员工斥责,如果未检测到则向客户显示响应。
以下是执行所有这些步骤的代码:
模型流程循环
|
|
值得注意的观察
一旦确定性注入