大模型安全测试garak工具搭建配置

本文转载自微信公众号「智佳网络安全」

原文标题:大模型安全测试garak工具搭建配置

内容版权归原作者及原公众号所有,转载仅为传播资讯;若有侵权请联系管理员删除。

基础环境配置

https://github.com/ggml-org/llama.cpp/releases

图片[1]-大模型安全测试garak工具搭建配置-阻击者联盟

下载对应cuda版本的exe和后面对应的DLLs 至于如何看cuda版本,命令nvidia-smi也行,去nvidia控制面板看也行

图片[2]-大模型安全测试garak工具搭建配置-阻击者联盟
modelscope download --model diodel/Qwen3.5-4B-Q4_K_M-GGUF --local_dir ./
图片[3]-大模型安全测试garak工具搭建配置-阻击者联盟
llama-server.exe -m D:\llama_cpp\llama-b10995-bin-win-cuda-13.4-x64\models\qwen3-5-4B-Q4_K_M.gguf -c 4096 --host 127.0.0.1 --port 9090 -ngl 99
图片[4]-大模型安全测试garak工具搭建配置-阻击者联盟
curl http://127.0.0.1:9090/v1/models

图片[5]-大模型安全测试garak工具搭建配置-阻击者联盟大模型就跑起来了

安装garak

conda create --name garak_env "python>=3.10,<=3.12"
conda activate garak_env
python -m pip install -U garak
图片[6]-大模型安全测试garak工具搭建配置-阻击者联盟
set OPENAI_API_KEY=sk-no-key-required
set OPENAI_BASE_URL=http://127.0.0.1:9090/v1
图片[7]-大模型安全测试garak工具搭建配置-阻击者联盟
garak --target_type openai --target_name qwen3-5-4B-Q4_K_M.gguf --probes encoding --parallel_attempts 1 --generations 1
图片[8]-大模型安全测试garak工具搭建配置-阻击者联盟
参数
缩写
默认值
作用与说明
--target_type -t / -m
无(必填)
指定生成器类型,如 openai、rest、huggingface、ollama
--target_name -n
无(必填)
目标模型或服务名称,如 qwen3-5-4B-Q4_K_M.gguf
--probes -p all
探针列表,可指定 encoding、dan、promptinject、 all 或 garak --list_probes(建议新版本使用–spec)
--probe_tags
无
无
按标签筛选探针,如 owasp:llm01
--detectors -d all
检测器列表,默认使用各探针推荐的检测器
--generations -g 5
每个提示生成几个响应。本地模型建议设为 1
--parallel_attempts
无
5
并行执行的探测尝试数。本地模型建议设为 1
--parallel_requests
无
1
同一提示的并行请求数,对不支持多生成的模型会被忽略
--config
无
无
指定 YAML/JSON 配置文件路径
--generator_option_file -G
无
指定生成器配置文件路径(用于自定义 REST 接口)
--detector_option_file
无
无
指定检测器配置文件路径(用于配置裁判模型)
--eval_threshold
无
0.5
判定为“成功攻击”的最低阈值
--timeout
无
视生成器而定
请求超时时间(秒),本地模型建议设大一些
--report_prefix
无
garak
报告文件的前缀名
--seed -s
随机
随机种子,用于结果可复现
--verbose -v 0
增加日志详细程度,可叠加使用

坑1: 跑完之后发现模型的返回是空

图片[9]-大模型安全测试garak工具搭建配置-阻击者联盟

经过检查发现是Qwen3.5自带思考,设置的窗口4096还没完成思考过程就截断了,导致garak没有收到完整的结果,也就默认判断为了通过

现在换成qwen2.5-3b-instruct-q4_k_m.gguf,这个没有思考过程,可以直接在content里返回模型输出结果

llama-server.exe -m ./models/qwen2.5-3b-instruct-q4_k_m.gguf -c 4096 --host 127.0.0.1 --port 9090 -ngl 99
图片[10]-大模型安全测试garak工具搭建配置-阻击者联盟
garak --target_type openai --target_name qwen2.5-3b-instruct-q4_k_m.gguf --spec probes.promptinject --parallel_attempts 1 --generations 1
图片[11]-大模型安全测试garak工具搭建配置-阻击者联盟

查看生成的jsonl结果文件,可以看到每个请求和响应

图片[12]-大模型安全测试garak工具搭建配置-阻击者联盟
图片[13]-大模型安全测试garak工具搭建配置-阻击者联盟

如果大模型提供接口和请求不是标准的格式,那么可能需要自己写对应的配置文件或Generator插件,相见下文,分别对应流式和非流式以及配置文件或插件处理方式

流式返回

方案一:使用RestGenerator配置文件

创建my_config.yaml

plugins:
generators:
rest:
RestGenerator:
name:"MyAgent"
uri:"http://xxxx:8080/api/chat/stream"
method:post
headers:
Content-Type:"application/json"
Accept:"text/event-stream"# 明确要求返回SSE流
User-Agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
req_template_json_object:
message:"$INPUT"
session_id:"garak_session_default"
user_orgid:"123456"
user_orgnm:"机构1"
# 注意:因为返回是 text/event-stream,此处无法用 response_json 提取字段
response_json:false

执行

garak --target_type rest -G my_config.yaml --spec probes.encoding --parallel_attempts 1

方案二:编写自定义Generator插件

新建my_generator.py

import json
import requests
from garak.generators.base import Generator

class MyGenerator(Generator):
"""专门用于对接MyAgent自定义接口的生成器"""

# 声明支持的生成数量
supports_multiple_generations = False

# 默认参数,可在配置文件中覆盖
DEFAULT_PARAMS = Generator.DEFAULT_PARAMS | {
"uri": "http://xxxxx:8080/api/chat/stream",
"headers": {
"Content-Type": "application/json",
"Accept": "text/event-stream",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
}

def _call_model(self, prompt, generations_this_call=1):
# 构造请求体,替换 message 字段
payload = {
"message": prompt,
"session_id": "garak_session_123",
"user_orgid": "123456",
"user_orgnm": "机构1"
}

try:
# 发送请求,开启流式读取
response = requests.post(
self.uri,
headers=self.headers,
json=payload,
stream=True,
timeout=60
)
response.raise_for_status()

# 解析 SSE 流,寻找 type 为 message 的 data
final_message = ""
for line in response.iter_lines():
if line:
decoded_line = line.decode('utf-8')
if decoded_line.startswith("data:"):
json_str = decoded_line[5:].strip() # 去掉 "data:" 前缀
try:
data_obj = json.loads(json_str)
if data_obj.get("type") == "message":
final_message = data_obj.get("data", "")
# 因为只有一条真正的 message,找到后可以 break(或者继续读取直到流结束)
break
except json.JSONDecodeError:
continue

# 如果没找到 message 类型的回复,返回兜底内容
ifnot final_message:
final_message = "ERROR: No 'message' type found in SSE response."

return [final_message]

except Exception as e:
return [f"ERROR: {str(e)}"]

把这个文件放到garak安装目录下,比如:D:\env\anaconda3\envs\garak_env\Lib\site-packages\garak\generators\my_generator.py

复制过去后,运行garak --list_generators,如果看到my_generator.MyGenerator,说明加载成功。

执行命令

garak --target_type my_generator --target_name qwen-agent --spec probes.promptinject --parallel_attempts 1 --generations 1

非流式返回

方案一:使用 RestGenerator 配置文件

创建my_json_config.yaml

plugins:
generators:
rest:
RestGenerator:
name:"MyAgent普通接口"
uri:"http://xxxxx:8080/api/chat/stream"# 换成实际接口地址
method:post
headers:
Content-Type:"application/json"
Accept:"application/json"
User-Agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
req_template_json_object:
message:"$INPUT"
session_id:"garak_session_default"
user_orgid:"123456"
user_orgnm:"机构1"
response_json:true
# 重点:根据你的实际返回结构改写 JSONPath
# 假设返回的是 {"data": {"content": "..."}}
response_json_field:"$.data.content"

执行命令和流式一样:

garak --target_type rest -G my_json_config.yaml --spec probes.promptinject --parallel_attempts 1

方案二:编写自定义 Generator 插件

新建my_json_generator.py

import json
import requests
from garak.generators.base import Generator

class MyJsonGenerator(Generator):
"""用于对接返回普通 JSON 的MyAgent接口"""

supports_multiple_generations = False

DEFAULT_PARAMS = Generator.DEFAULT_PARAMS | {
"uri": "http://xxxx:8080/api/chat/stream",
"headers": {
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
}

def _call_model(self, prompt, generations_this_call=1):
# 构造请求体,替换 message 字段
payload = {
"message": prompt,
"session_id": "garak_session_123",
"user_orgid": "123456",
"user_orgnm": "机构1"
}

try:
# 1. 发送请求,注意去掉了 stream=True
response = requests.post(
self.uri,
headers=self.headers,
json=payload,
timeout=60
)
response.raise_for_status()

# 2. 直接将整个响应解析为 JSON
resp_json = response.json()

# 3. 根据返回结构提取需要的字段
final_message = ""

# 假设普通 JSON 返回是类似 {"code":200, "data": [{"type":"thought", ...}, {"type":"message", "data":"..."}]}
data_list = resp_json.get("data", [])
if isinstance(data_list, list):
for item in data_list:
if item.get("type") == "message":
final_message = item.get("data", "")
break# 找到就跳出循环
elif isinstance(data_list, dict):
# 如果是 {"data": {"type": "message", "data": "..."}}
if data_list.get("type") == "message":
final_message = data_list.get("data", "")

# 兜底:如果没找到对应字段,返回整个 JSON 字符串
ifnot final_message:
final_message = json.dumps(resp_json, ensure_ascii=False)

return [final_message]

except Exception as e:
return [f"ERROR: {str(e)}"]

同样复制到garak安装目录下,执行:

garak --target_type my_json_generator --target_name qwen-agent --spec probes.promptinject --parallel_attempts 1

— 阅读原文请访问微信公众号「智佳网络安全」 · 原文链接 —

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像-阻击者联盟
欢迎您留下宝贵的见解!
提交
头像-阻击者联盟

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容