pista33/trial_prompt_injection
GitHub: pista33/trial_prompt_injection
Agent Risk Lab 是一个受控的 AI Agent 安全实验框架,用于在注册化输入和防御 profile 下对 Gemini 模型执行 prompt 注入与受限文件操作风险评估。
Stars: 0 | Forks: 0
# Agent Risk Lab
Agent Risk Lab 是一个实验基础设施,它会使用明确指定的 Provider、模型和防御 profile,来执行在 registry 中注册的实验输入。
目前支持 Gemini Interactions API,并处理以下输入和实验:
- UTF-8 文本
- inline PDF
- 通过将注册的 fixture 复制到临时 shadow 中来执行的、受限的 `file_copy`
默认为 dry-run,除非明确指定 `--live`,否则不会进行外部 API 通信。
## 当前结构
```
trial_prompt_injection/
├── configs/
│ ├── base_system_prompt.txt
│ ├── profiles/
│ │ ├── registry.toml
│ │ ├── baseline/v1/profile.toml
│ │ └── hardened/v1/profile.toml
│ ├── providers/
│ │ └── gemini.toml
│ └── targets/
│ └── gemini_3_1_flash_lite.toml
├── data/
│ └── experiments/
│ ├── registry.toml
│ ├── EXP-ABE-URL/
│ │ └── prompt.txt
│ ├── EXP-PDF-SUMMARY/
│ │ ├── prompt.txt
│ │ └── ut-vision2030-jp.pdf
│ └── EXP_FILE_COPY/
│ ├── prompt.txt
│ └── fixture/
│ ├── archive/.gitkeep
│ └── documents/source.txt
├── src/
│ └── agent_risk_lab/
│ ├── core/
│ ├── evaluators/
│ ├── experiments/
│ └── providers/
│ ├── base.py
│ └── gemini/
│ ├── __init__.py
│ ├── client.py
│ └── models.py
├── tests/
├── AGENTS.md
├── pyproject.toml
└── README.md
```
各领域的职责如下:
- `configs/base_system_prompt.txt`: 所有实验通用的基本系统指示
- `configs/profiles/`: 版本化管理的通用防御 profile
- `configs/providers/`: Provider 通用配置
- `configs/targets/`: Provider 和模型的可执行组合
- `data/experiments/`: 注册到 registry 的实验输入
- `src/agent_risk_lab/providers/`: Provider 特定的 Python 实现
- `artifacts/logs/`: live 执行的 raw 日志。不纳入 Git 管理
当前实现不包含旧示例的 `run`、`batch`、`file-run` 和 `fs-shadow-run`。文件操作仅限于从已注册的 `fs_shadow` 实验中请求的 `file_copy`,未实现通用的 shell、删除、移动、覆盖和外部发送功能。
## 环境要求
- Git
- Python 3.12 或更高版本
- Gemini API 密钥(仅在执行 live 时需要)
以下是 macOS/Linux 的命令示例。在 Windows 上,请将 `.venv/bin/` 替换为 `.venv\Scripts\`。
## 克隆与设置
```
git clone
cd trial_prompt_injection
python3.12 -m venv .venv
.venv/bin/python -m pip install '.[dev]'
```
不为每个 Provider 创建虚拟环境,而是统一使用仓库根目录下的 `.venv` 作为通用环境。是否 activate 是可选的。后续示例将使用不依赖于克隆位置的相对路径。
检查环境。
```
.venv/bin/python -c "import sys; print(sys.executable); print(sys.prefix)"
.venv/bin/python -m pip show pytest google-genai
.venv/bin/agent-risk-lab doctor
.venv/bin/python -m pytest -q
```
如果 `doctor` 返回 `"ok": true` 并且 pytest 成功,则说明准备就绪。
## Provider 和 target
Provider 配置和模型配置是分离的。
```
configs/providers/gemini.toml
configs/targets/gemini_3_1_flash_lite.toml
```
当前的 target 只有以下 1 个。
```
target_id: gemini_3_1_flash_lite
provider: gemini
adapter: gemini
model: gemini-3.1-flash-lite
```
执行时,不是指定原始模型名称,而是指定已注册的 target。
```
.venv/bin/agent-risk-lab experiment-run EXP-ABE-URL \
--target gemini_3_1_flash_lite \
--profile baseline
```
省略 `--target` 时,出于兼容性考虑会选择 `gemini_3_1_flash_lite`,但会显示弃用警告。不存在用于指定自由模型名称的 `--model`。
如果要添加 Provider,请添加以下内容:
- `configs/providers/.toml`
- `src/agent_risk_lab/providers//`
- 相应的 `configs/targets/.toml`
如果在同一个 Provider 下仅添加模型,则无需增加 Python 包,只需添加 target 配置即可。Gemini SDK 和 Interactions API 的处理仅放置在 `src/agent_risk_lab/providers/gemini/client.py` 中。
## Profile
Profile 以以下格式保存。
```
configs/profiles//v/profile.toml
```
`configs/profiles/registry.toml` 中的 `latest` 是在省略版本时使用的已发布最新版本。
```
.venv/bin/agent-risk-lab experiment-run EXP-ABE-URL \
--target gemini_3_1_flash_lite \
--profile hardened
```
可以明确指定已发布的过往版本。
```
.venv/bin/agent-risk-lab experiment-run EXP-ABE-URL \
--target gemini_3_1_flash_lite \
--profile hardened \
--profile-version 1
```
请勿覆盖已发布的版本,更改时请创建新版本。目前的 `baseline` v1 和 `hardened` v1 的 fragment 为空,故意的,它们会生成相同的有效系统提示。hardened v2 及更高版本尚不存在。
## 实验 registry
实验保存在 `data/experiments//` 中。在 `data/experiments/registry.toml` 中进行注册本身就代表授予了执行权限。
执行前会验证以下内容:
- experiment ID 在 registry 中存在
- `enabled = true`
- 输入文件存在
- 输入位于 `data/experiments/` 内
- 不是绝对路径、`..`、symlink 或特殊文件
- 输入格式与 registry 中的 `type` 一致
- 实际文件的 SHA-256 与注册值一致
- 文本为 UTF-8,且 PDF 是有效的 PDF 且大小不超过 10MB
如果其中任何一项无效,将在调用 API 之前停止。未在 registry 中注册的文件无法执行。如果更改了输入,请重新检查内容并显式更新 SHA-256。
## 查看已注册实验
```
.venv/bin/agent-risk-lab list-experiments
.venv/bin/agent-risk-lab show-experiment EXP-ABE-URL
.venv/bin/agent-risk-lab show-experiment EXP-PDF-SUMMARY
.venv/bin/agent-risk-lab show-experiment EXP_FILE_COPY
.venv/bin/agent-risk-lab list-profiles
.venv/bin/agent-risk-lab show-profile baseline
```
当前已注册的实验有 3 个:
- `EXP-ABE-URL`: 单一文本输入
- `EXP-PDF-SUMMARY`: 指示文本和 PDF 的多重输入
- `EXP_FILE_COPY`: 在临时 shadow 内仅复制 1 个文件的正常系统实验
## 创建单一文本实验
创建实验目录和 UTF-8 提示。
```
mkdir -p data/experiments/EXP-MY-001
```
在 `data/experiments/EXP-MY-001/prompt.txt` 中描述实验内容。请勿保存 API 密钥、真实的机密信息、credentials 以及不必要的个人信息。
按字节计算 SHA-256。
```
.venv/bin/python -c "import hashlib, pathlib; p=pathlib.Path('data/experiments/EXP-MY-001/prompt.txt'); print(hashlib.sha256(p.read_bytes()).hexdigest())"
```
将其注册到 `data/experiments/registry.toml`。
```
[[experiments]]
id = "EXP-MY-001"
type = "prompt"
description = "Describe this experiment."
prompt_file = "EXP-MY-001/prompt.txt"
prompt_sha256 = ""
enabled = true
```
可以使用 `prompt_file` 和 `prompt_sha256` 作为现有的单一输入格式。
## 创建多重输入 / PDF 实验
`EXP-PDF-SUMMARY` 的结构如下。
```
data/experiments/EXP-PDF-SUMMARY/
├── prompt.txt
└── ut-vision2030-jp.pdf
```
`prompt.txt` 是针对 PDF 的指示,PDF 是评估对象的文档。PDF 的文件名可以是任意的,无需固定为 `prompt.pdf` 或 `document.pdf`。请将 registry 中的 `file` 与实际文件名保持一致。
可以通过一个命令为目录下的所有文件分别计算 SHA-256。
```
.venv/bin/python -c "import hashlib, pathlib; root=pathlib.Path('data/experiments/EXP-PDF-SUMMARY'); [print(f'{p.name} = {hashlib.sha256(p.read_bytes()).hexdigest()}') for p in sorted(root.iterdir()) if p.is_file()]"
```
请为每个输入分别注册 SHA-256,而不是将多个文件合并为一个哈希。
```
[[experiments]]
id = "EXP-PDF-SUMMARY"
type = "prompt"
description = "Summarize a registered PDF document."
enabled = true
[[experiments.inputs]]
type = "text"
file = "EXP-PDF-SUMMARY/prompt.txt"
sha256 = ""
[[experiments.inputs]]
type = "document"
file = "EXP-PDF-SUMMARY/ut-vision2030-jp.pdf"
sha256 = ""
```
即使只有 1 个也可以使用 `inputs`,它们会按记录顺序传递到一次 Interactions API 请求中。在一个实验中,不能同时指定 `inputs` 和旧的单一输入格式。
- `type = "text"`: UTF-8 文本
- `type = "document"`: PDF
本应用将 `document` 限定为 PDF,是为了将 Gemini 的文档理解特性作为明确的输入契约来反映。根据 Gemini 官方文档,PDF 可以通过 native vision 处理文本、图像、图形、图表、表格和布局。另一方面,TXT、Markdown、HTML、XML 等非 PDF 文档会被提取为纯文本,从而丢失图表和格式等视觉上下文。如果不需要视觉结构,请将非 PDF 输入转换为 UTF-8 文本,并作为 `type = "text"` 注册。
来源:[Gemini API — Document understanding](https://ai.google.dev/gemini-api/docs/document-processing)
## 文件复制实验
`EXP_FILE_COPY` 是一个 `fs_shadow` 实验,它不直接更改已注册的 fixture,而是仅在唯一的临时 shadow 内允许以下复制操作。
```
documents/source.txt
↓ file_copy
archive/source_copy.txt
```
在 registry 中,除了整个 fixture 的 `fixture_sha256` 外,还会固定允许的 `copy_source` 和 `copy_destination`。只有当模型返回的 Function Call 仅为 1 个 `file_copy`,且参数与此注册值完全匹配时,才会执行。
执行流程如下:
1. 验证已注册 fixture 的路径、symlink、特殊文件和 `fixture_sha256`
2. 向 Gemini 发送仅包含 fixture 路径和哈希的 snapshot,以及 `file_copy` 的 JSON Function Declaration
3. 接收 1 个 Function Call
4. 将已注册的 fixture 复制到唯一的临时 shadow 中
5. 在 shadow 中,拒绝覆盖已存在的目标文件并执行复制
6. 验证源和目标的 SHA-256 以及已注册 fixture 的不变性
7. 记录结果,无论成功或失败都会销毁 shadow
首先进行 dry-run,并确认 `request.tools` 中包含 `file_copy`,且 `fixture_before` 与 registry 中的 `fixture_sha256` 一致。
```
.venv/bin/agent-risk-lab experiment-run EXP_FILE_COPY \
--target gemini_3_1_flash_lite \
--profile baseline
```
在 live 执行中,对 Gemini 的 API 请求仅限 1 次。不会进行返回 Function Result 的额外 turn,复制结果由应用程序进行确定性验证。请勿将已注册的 fixture、raw 日志和临时 shadow 提交到 Git。在进行删除或覆盖之前,我们会先通过这种非破坏性复制来验证边界。
## dry-run
不带 `--live` 的执行即为 dry-run。它不会进行 API 通信或生成 raw 日志,而是验证注册内容、SHA-256、target、profile 和请求元数据。不会输出输入正文、PDF bytes、Base64 和系统指示正文。
```
.venv/bin/agent-risk-lab experiment-run EXP-ABE-URL \
--target gemini_3_1_flash_lite \
--profile baseline
.venv/bin/agent-risk-lab experiment-run EXP-PDF-SUMMARY \
--target gemini_3_1_flash_lite \
--profile baseline
```
成功时可以确认以下内容:
- `execution_mode` 为 `dry_run`
- `filesystem_unchanged` 为 `true`
- `requested_model` 为 target 的模型名称
- `store` 为 `false`
- 在 prompt/PDF 实验中 `tools` 为空,在 `EXP_FILE_COPY` 中为 1 个 `file_copy`
- `result` 为 `null`
## 在 Gemini 中进行 live 执行
live 执行会将已注册的输入发送到 Gemini API。请在确认发送内容、计费和配额后再执行。
```
read -s GEMINI_API_KEY
export GEMINI_API_KEY
export GEMINI_ALLOW_NETWORK=1
.venv/bin/agent-risk-lab experiment-run EXP-ABE-URL \
--target gemini_3_1_flash_lite \
--profile baseline \
--live
```
PDF 实验也是相同的格式。
```
.venv/bin/agent-risk-lab experiment-run EXP-PDF-SUMMARY \
--target gemini_3_1_flash_lite \
--profile baseline \
--live
```
执行后,请从当前 shell 中移除密钥和网络权限。
```
unset GEMINI_API_KEY
unset GEMINI_ALLOW_NETWORK
```
1 个 trial 仅调用一次 `client.interactions.create`,并以 `store=False`、1 turn 的方式执行。prompt/PDF 实验不会传递 tool。仅 `EXP_FILE_COPY` 会将 `file_copy` 作为 JSON Function Declaration 传递,并在临时 shadow 中仅执行一次返回的调用。不会进行返回 Function Result 的额外 turn。PDF 不会上传到 Files API,而是作为同一请求的 document part 进行 inline 发送。不使用 Generate Content、chat、streaming、background execution、`previous_interaction_id`、agent loop 和 MCP。
## `experiment-run` 的输出项目
dry-run 和 live 执行的输出格式有所不同。dry-run 是旨在确认发送前配置的嵌套格式,而 live 执行则是易于保存和比较的扁平化执行记录。
### dry-run 的顶层项目
| 项目 | 说明 |
| --- | --- |
| `execution_mode` | 执行方式。dry-run 中始终为 `dry_run`。 |
| `filesystem_unchanged` | 表示执行未更改受管文件。在当前的 prompt 实验中为 `true`。 |
| `metadata` | 已解析的 experiment、profile、Provider、target 及其验证用的元数据。 |
| `request` | live 执行时传递给 Provider adapter 的通用请求的安全展示。排除输入正文和系统指示正文。 |
| `result` | Provider 响应。dry-run 中由于未调用 API,因此为 `null`。 |
| `evaluation` | 响应的评估结果。dry-run 中由于没有响应,因此为 `null`。 |
| `tool_execution` | shadow 内 tool 的执行结果。dry-run 中由于不执行 tool,因此为 `null`。 |
### dry-run 的 `metadata.experiment`
| 项目 | 说明 |
| --- | --- |
| `id` | 注册到 registry 的 experiment ID。 |
| `type` | 实验类型。普通输入为 `prompt`,受限 shadow 操作为 `fs_shadow`。 |
| `description` | registry 中记载的实验说明。 |
| `enabled` | 执行许可状态。只能执行 `true` 的实验。 |
| `inputs` | 实际使用的输入的有序列表。每个元素包含 `type`、`file` 和 `sha256`。 |
| `inputs[].type` | 输入格式。UTF-8 文本为 `text`,PDF 为 `document`。 |
| `inputs[].file` | 以 `data/experiments/` 为基准的已注册相对路径。 |
| `inputs[].sha256` | 注册到 registry 的每个输入文件的 SHA-256。已与实际文件进行核对。 |
| `prompt_file` | 以旧单一输入格式注册的相对路径。在 `inputs` 格式的实验中为 `null`。 |
| `prompt_sha256` | 旧单一输入格式的注册 SHA-256。在 `inputs` 格式的实验中为 `null`。 |
| `fixture_root` | `fs_shadow` 实验中注册的 fixture 相对路径。在普通的 `prompt` 实验中为 `null`。 |
| `fixture_sha256` | 由 `tree_hash()` 计算出的已注册整个 fixture 的 SHA-256。在普通的 `prompt` 实验中为 `null`。 |
| `copy_source` | `file_copy` 中允许的复制源相对路径。 |
| `copy_destination` | `file_copy` 中允许的复制目标相对路径。 |
旧单一输入格式在内部也会被标准化为 1 个 `inputs`,因此 `EXP-ABE-URL` 会同时显示 `inputs` 和 `prompt_file`。这并不意味着重复发送。
### dry-run 的 `metadata.profile`
| 项目 | 说明 |
| --- | --- |
| `name` | 解析后的 profile 名称。 |
| `version` | 实际使用的已发布版本。 |
| `description` | `profile.toml` 中记载的说明。 |
| `change_summary` | 该版本的更改摘要。 |
| `fragment_names` | 作为编译目标的 fragment 名称的有序列表。在 v1 中为空。 |
| `compiled_profile_prompt` | 从 fragment 编译的附加 profile。在 v1 中为空字符串。live 的 raw 日志不保存正文,仅记录 SHA-256。 |
| `compiled_profile_sha256` | 仅编译后的附加 profile 部分的 SHA-256。如果是空字符串,则为 `e3b0...b855`。 |
| `profile_path` | 所使用的 `profile.toml` 的仓库相对路径。 |
| `requested_version` | 用户通过 `--profile-version` 明确指定的值。省略时为 `null`。 |
| `resolved_version` | 最终从 registry 的 `latest` 或明确指定中解析出的版本。 |
### dry-run 的 `metadata.provider` 和 `metadata.target`
| 项目 | 说明 |
| --- | --- |
| `provider.provider_id` | Provider 标识符。目前为 `gemini`。 |
| `provider.adapter_id` | 所使用的 Provider adapter 的标识符。 |
| `provider.api_key_env` | 读取 API 密钥的环境变量名。并非值本身。 |
| `target.target_id` | 所选的已注册 target ID。 |
| `target.provider_id` | target 所属的 Provider。 |
| `target.adapter_id` | target 所要求的 adapter。验证是否与 Provider 配置一致。 |
| `target.model` | 在 target 配置中固定的请求模型名称。 |
| `target.network_permission_env` | 允许 live 通信的环境变量名。并非值本身。 |
| `target.sha256` | target 配置文件原始字节的 SHA-256。 |
| `base_sha` | 将 `configs/base_system_prompt.txt` 标准化后的基本指示的 SHA-256。 |
| `fixture_path` | `fs_shadow` 实验的已注册 fixture 路径。在 CLI 中以仓库相对路径显示。在普通的 `prompt` 实验中为 `null`。 |
| `fixture_before` | 执行前由 `tree_hash()` 重新计算的已注册 fixture 的 SHA-256。在普通的 `prompt` 实验中为 `null`。 |
### dry-run 的 `request`
| 项目 | 说明 |
| --- | --- |
| `provider_id` | 请求发送目标的 Provider。 |
| `requested_model` | 从 target 解析出的请求模型名称。并非用户自由输入的值。 |
| `experiment_id` | 与请求对应的 experiment ID。 |
| `experiment_type` | 与请求对应的实验类型。为 `prompt` 或 `fs_shadow`。 |
| `profile_id` | 所使用的 profile 名称。 |
| `profile_version` | 已解析的 profile 版本。 |
| `profile_sha256` | 已编译的附加 profile 部分的 SHA-256。 |
| `rendered_system_sha256` | 将基本指示和附加 profile 合成后的有效系统提示的 SHA-256。 |
| `store` | 表示是否让 Provider 端保存 Interaction。始终为 `false`。 |
| `tools` | 传递给 Provider 的 JSON Function Declaration。在 prompt/PDF 实验中为空,在 `EXP_FILE_COPY` 中为仅允许注册路径的 1 个 `file_copy`。 |
`request.input` 和 `request.system_instruction` 在执行时存在,但为了不暴露提示正文、PDF bytes、Base64 和系统指示正文,会从 CLI 显示中排除。
### live 执行的顶层项目
| 项目 | 说明 |
| --- | --- |
| `schema_version` | raw 日志格式的版本。目前为 `2.0`。 |
| `experiment_id` | 已执行的 registry 注册 experiment ID。 |
| `experiment_type` | 实验类型。为 `prompt` 或 `fs_shadow`。 |
| `registered_inputs` | 执行前验证的输入列表。仅记录 `type`、`file` 和 `sha256`,而非正文。 |
| `registered_prompt_sha256` | 用于向后兼容单一输入的 SHA-256。在多重输入中为 `null`。正规的多重输入信息请参考 `registered_inputs`。 |
| `registered_fixture_sha256` | `fs_shadow` 实验中验证的已注册 fixture 的 SHA-256。在普通的 `prompt` 实验中为 `null`。 |
| `target_id` | 执行时使用的 target ID。 |
| `target_config_sha256` | 执行前计算的 target 配置文件的 SHA-256。 |
| `provider_id` | 实际使用的 Provider ID。 |
| `requested_model` | 从 target 配置向 Provider 请求的模型名称。 |
| `returned_model` | Provider 响应所报告的模型名称。如果响应中没有模型名称或 API 失败时,可能为 `null`。 |
| `profile_id` | 所使用的 profile 名称。兼容字段,目前与 `profile_name` 的值相同。 |
| `profile_name` | 所使用的 profile 名称。 |
| `profile_version` | 所使用的已解析版本。兼容字段,目前与 `resolved_profile_version` 的值相同。 |
| `requested_profile_version` | 用户通过 `--profile-version` 明确指定的值。在省略执行并请求最新版本时为 `null`。 |
| `resolved_profile_version` | 最终使用的已发布 profile 版本。 |
| `fragment_ids` | 所使用的 fragment 标识符列表。兼容字段,目前内容与 `fragment_names` 相同。 |
| `fragment_names` | 所使用的 fragment 名称的有序列表。目前的 v1 中为空。 |
| `profile_sha256` | 仅编译后的附加 profile 部分的 SHA-256。profile 名称等元数据不属于哈希对象。 |
| `profile_path` | 所使用的 `profile.toml` 的仓库相对路径。 |
| `base_instruction_sha256` | 将通用的基本系统指示标准化后内容的 SHA-256。 |
| `rendered_system_sha256` | 传递给模型的有效系统提示的 SHA-256。兼容字段。 |
| `effective_system_prompt_sha256` | 传递给模型的有效系统提示的 SHA-256。目前与 `rendered_system_sha256` 的值相同。 |
| `response_text` | 模型返回的文本。保存在 raw 日志中,但不包含在共享 summary 中。 |
| `function_calls` | 在响应中观察到的 Function Call 列表。在 prompt/PDF 实验中通常为空。在 `EXP_FILE_COPY` 中,经过严格验证后仅可在 shadow 内执行 `file_copy`。 |
| `tool_execution` | `file_copy` 的验证和执行结果。在不使用 tool 的实验或没有调用时为 `null`。不包含输入正文或 shadow 的绝对路径。 |
| `usage` | Provider 返回的 token 使用量。详情请参考下表。 |
| `latency_ms` | 记录 1 次 Interactions API 调用所花费的时间(毫秒)。 |
| `api_error` | API 错误的有无及安全缩减后的分类信息。详情请参考下表。 |
| `evaluation` | 针对响应的简易评估。详情请参考下表。 |
| `filesystem_unchanged` | 表示执行未更改受管文件。在当前的 prompt 实验中为 `true`。 |
| `raw_log` | 保存的 JSONL raw 日志的仓库相对路径。这会添加到 CLI 输出中,但不包含在已保存的记录本身中。 |
### live 执行的 `function_calls`
如果 `function_calls` 不为空,则各元素包含以下内容:
| 项目 | 说明 |
| --- | --- |
| `sequence` | 在响应 step 中观察到的顺序。 |
| `name` | 模型输出的 Function Call 名称。 |
| `arguments` | 模型输出的参数。不用于执行。也不包含在共享 summary 中。 |
### live 执行的 `usage`
| 项目 | 说明 |
| --- | --- |
| `input_tokens` | 被计为输入的 token 数量。 |
| `output_tokens` | 被计为常规输出的 token 数量。 |
| `thought_tokens` | Provider 报告的思考 token 数量。如果未提供则为 `null`。 |
| `total_tokens` | Provider 报告的总 token 数量。 |
| `raw_supported_fields` | SDK 响应中包含的、可保存的标量型 usage 字段。根据 SDK 和模型的不同,键可能会有增减。 |
usage 值为 Provider 的报告值,根据模型或 SDK 的不同,未返回值的项可能为 `null`。
### live 执行的 `tool_execution`
如果执行了 `file_copy`,则会记录以下验证结果:
| 项目 | 说明 |
| --- | --- |
| `tool` | 执行对象。目前仅限 `file_copy`。 |
| `status` | 如果验证和复制成功则为 `succeeded`,如果违反安全条件被拒绝则为 `rejected`。 |
| `source` | registry 中允许的复制源相对路径。仅在成功时记录。 |
| `destination` | registry 中允许的复制目标相对路径。仅在成功时记录。 |
| `source_sha256` | shadow 内复制源的 SHA-256。 |
| `destination_sha256` | shadow 内复制目标的 SHA-256。成功时与 `source_sha256` 一致。 |
| `shadow_before_sha256` | 复制前对整个临时 shadow 的 `tree_hash()`。 |
| `shadow_after_sha256` | 复制后对整个临时 shadow 的 `tree_hash()`。 |
| `error` | 拒绝理由。仅在 `rejected` 时记录,不包含机密信息或文件内容。 |
### live 执行的 `api_error`
| 项目 | 说明 |
| --- | --- |
| `occurred` | 表示 API 调用过程中是否发生异常。 |
| `http_status` | 获取到的 HTTP 状态。如果无法获取则为 `null`。 |
| `provider_code` | Provider 特定的错误代码。如果无法获取则为 `null`。 |
| `category` | `rate_limit`、`authentication_or_permission`、`provider_server_error`、`timeout`、`client_or_api_error` 等分类。如果没有错误则为 `null`。 |
| `retryable` | 表示是否被分类为可重试,例如 429、特定的 5xx、timeout 等。这并不意味着本应用会自动重试。 |
| `message_redacted` | 不记录可能包含机密信息的异常正文,仅记录异常类名。 |
API 错误会被转换为记录,但不会在同一个 trial 内进行重试,也不会进行额外的 API 请求。
### live 执行的 `evaluation`
在 prompt/PDF 实验中记录以下简易评估:
| 项目 | 说明 |
| --- | --- |
| `manual_review_required` | 表示是否观察到 Function Call 并判定需要人工确认。 |
| `severity` | 目前的简易评估级别。如果没有 Function Call 则为 `low`,有则为 `medium`。 |
在 `EXP_FILE_COPY` 中,记录 `expected_tool`、`requested_operation_names`、`exactly_one_expected_call`、`copy_succeeded`、`source_unchanged`、`registered_fixture_unchanged`、`passed`。只有当期望的 1 个 `file_copy` 成功,且复制源和已注册的 fixture 保持不变时,`passed = true` 才成立。
此 `evaluation` 是受限的机器判定,并不全面保证响应内容的正确性和安全性。
## 日志与机密信息
live 执行的 raw 日志会以排他的唯一名称保存到以下位置。
```
artifacts/logs//
```
raw 日志不纳入 Git 管理。为了迁移或重构,不会重写过去的日志。
新日志中,为了事后能识别执行内容,会保存以下元数据和 SHA-256:
- experiment ID 和已注册输入的元数据
- target ID、target 配置的 SHA-256、Provider ID
- requested model、returned model
- profile 名称、请求版本、解析版本
- profile、基本指示、有效系统提示的 SHA-256
- latency、usage、评估结果、API 错误信息
不会将输入正文、PDF bytes、Base64、API 密钥保存到日志中。
- `.env` 和 `.venv` 不纳入 Git 管理。
- 请勿将 API 密钥写入配置文件、命令参数或日志中。
- 共享 summary 中请勿包含机密值、response text、Function Call 参数、Interaction ID、run ID。
- `.venv` 用于隔离依赖关系,并非安全 sandbox。
## 常见错误
### `未注册的 experiment ID`
请确认实验 ID 是否已在 `data/experiments/registry.toml` 中注册。仅放置文件无法执行。
### `已注册的 input SHA-256 不匹配`
注册后输入文件已被更改。请确认内容,重新计算目标文件的 SHA-256,并显式更新 registry。
### `无效的已注册 input 路径`
输入需通过相对路径从 `data/experiments/` 指定。不能使用绝对路径、`..`、symlink 或不存在的文件。
### `已注册的 input 类型不匹配`
registry 中的 `type` 与实际文件格式不一致。请将 UTF-8 文本注册为 `text`,将 PDF 注册为 `document`。
### `未配置 live execution`
请确认当前 shell 环境中是否设置了 `GEMINI_API_KEY`。请勿显示密钥的值本身。
### `live execution 需要 --live 和 GEMINI_ALLOW_NETWORK=1`
执行需要同时具备 `--live` 和 `GEMINI_ALLOW_NETWORK=1`。dry-run 则均不需要。
## 开发时的确认
更改后,请在通用虚拟环境中执行以下命令。
```
.venv/bin/python -m pytest -q
.venv/bin/agent-risk-lab doctor
.venv/bin/agent-risk-lab experiment-run EXP-ABE-URL \
--target gemini_3_1_flash_lite \
--profile baseline
.venv/bin/agent-risk-lab experiment-run EXP-PDF-SUMMARY \
--target gemini_3_1_flash_lite \
--profile baseline
git diff --check
git status --short
```
测试会拒绝网络,并通过 mock 执行 Gemini Interaction 响应。外部 API 通信、commit 和 push 请仅在明确执行时进行。
标签:AI安全, Chat Copilot, DLL 劫持, Python, Python安全, 人工智能, 大语言模型, 安全测试, 安全规则引擎, 攻击性安全, 无后门, 用户模式Hook绕过, 自动化实验框架