boringllm/Icewall
GitHub: boringllm/Icewall
一款基于 LLM 与多 Agent 架构的大型代码仓库漏洞扫描器,通过构建代码图并追踪污点传播路径来发现漏洞并提供修复建议。
Stars: 0 | Forks: 0
# Icewall
**由 LLM 驱动、用于大型仓库的多 agent 漏洞扫描器。**
Icewall 为仓库构建代码图,使用一组专门的 LLM agent 跨文件追踪不受信任的输入至危险的 sink,独立验证每个发现以减少误报,并提出(绝不直接应用)修复建议。它通过启发式 mock provider 实现开箱即用的完全离线运行,并通过 Anthropic 和任何兼容 OpenAI 的 endpoint 上的每 agent 分层机制扩展至真实模型。
## 为什么这样构建
Icewall 的设计遵循了 2025–2026 年基于 LLM 的安全领域的最前沿技术:
- **Agentic + 过程间分析胜过单次调用。** 真实的漏洞源于调用序列,因此追踪 source→sink 路径是核心任务(JitVul, *arXiv:2503.03586*)。
- **代码图是大型仓库的骨架**,而不是暴力破解上下文(RepoGraph, CGM)。tree-sitter 实现了无构建解析 —— 无需编译(参见 RepoAudit)。
- **独立的 validator agent 是准确性的支柱。** 独立的可行性检查和 guard 识别极大地减少了误报(QASecClaw: −88.6% FPs; LLM4PFA/AdaTaint)。
- **迭代式上下文扩展。** Tracer subagent 仅拉取其所需的函数,由代码图支持(Vulnhuntr 的做法,但成本更低)。
- **仅提供修复建议。** AI 补丁可能会引入新的 bug(*arXiv:2507.02976*),因此 Icewall 输出 diff 供人工审查。
## 架构
```
Ingest → tree-sitter code graph → pre-filter (taint signals)
→ TRIAGE (cheap model) → entry points
→ TRACER subagents (threaded) → source→sink paths ┐ dynamic
↑ request more context ── orchestrator ────────┘ parent↔child
→ ANALYZER (strong model, one prompt per CWE) → raw findings
→ VALIDATOR (strong model) → confirm / reject / adjust
→ REMEDIATOR → patch proposals
→ SARIF + Markdown + JSON report
```
| Agent | 职责 | 典型层级 |
|---|---|---|
| Triage | 分类攻击面 | fast (Haiku) |
| Tracer | 遍历 source→sink 图,请求上下文 | fast–mid |
| Analyzer | 每种 CWE 的判定 + 置信度 | strong (Sonnet + thinking) |
| Validator | 可行性、guard、去重 | strongest (Opus + thinking) |
| Remediator | 提出修复 diff | strong (Sonnet) |
| Summarizer | 按需压缩超大上下文 | fast (Haiku) |
并发使用两个有界线程池 —— **neural**(受速率/成本限制的 LLM 调用)和 **symbolic**(图相关工作),配备线程安全的去重发现存储,以及由 orchestrator 强制执行的每次运行 token/调用预算。
## 安装
```
pip install -e . # core (tree-sitter + mock provider, no keys needed)
pip install -e ".[all]" # + anthropic and openai SDKs
```
需要 Python ≥ 3.11。
## 快速开始
```
# 离线 — 无 API keys。使用 heuristic mock provider。
icewall scan examples/vulnerable_app --dry-run
# 仅检查 code graph(无 LLM 调用)
icewall graph examples/vulnerable_app
# 真实模型:创建并编辑 config,然后扫描
icewall init-config icewall.yaml
export ANTHROPIC_API_KEY=... # (PowerShell: $env:ANTHROPIC_API_KEY=...)
icewall scan ./my-repo -c icewall.yaml -f markdown -o report.md
```
输出格式:`markdown`(默认)、`sarif`(CI / GitHub 代码扫描)、`json`。进度和摘要表输出到 **stderr**;报告输出到 **stdout**(或 `--output FILE`),因此你可以通过管道传递它。当确认任何 high/critical 发现时,退出代码为 `1` —— 这在 CI 中非常方便。
有用的标志:`--min-severity {info,low,medium,high,critical}`, `--dry-run`。
## Web UI
更喜欢 UI 而不是 CLI?启动本地 Web 应用:
```
pip install -e ".[ui]" # fastapi + uvicorn
icewall ui # serves http://127.0.0.1:8765 and opens a browser
```
它是同一引擎之上的一个轻量级封装,为你提供:
- **设置 + 预设** —— 一个可编辑的配置表单(provider、每个 agent 的模型/层级、预算、并发、上下文、记忆、workshop、自定义定价)。每个 agent 都有一个完全 **感知 provider 的生成参数编辑器** —— OpenAI 和 Anthropic 公开了不同的调节项(`top_p`、`seed`、`stop`、`frequency_penalty`、`reasoning_effort`, … 与 `top_p`、`top_k`、`stop_sequences`, …),并为其他任何参数(`response_format`、`tools`、`metadata`)提供了原始 JSON 的备用方案。此处设置的任何内容都将原封不动地转发给模型的 API(配置键 `agents.
.params`)。将任何配置保存为命名的 **预设**,并在将来的扫描中加载它。已经有 `icewall.yaml` 文件了?将其作为即用型预设导入 —— 可以使用 `python run.py --import icewall.yaml`(或在“预设”选项卡的 *Import* 框中输入 `icewall.yaml` 路径)。在扫描表单上选择预设将按原样运行;表单的默认设置是离线 **mock** provider,因此在选择真实预设(或填写表单)之前,不会发生真实的 API 调用。
- **扫描强度** —— 扫描表单上的一键召回率/成本调节盘:**Fast**(高可疑度入口点,浅层追踪) → **Balanced**(默认) → **Thorough**(更多入口点,更深层次追踪) → **Exhaustive**(分诊每一个函数 —— 绕过 source/sink 预过滤 —— 并进行最深层次的追踪)。较高的强度会调查更多路径(减少漏报),但成本更高;选择 **Custom** 可在配置中驱动各个单独的调节项。CLI 中同样支持:`icewall scan --intensity thorough`。
- **实时扫描视图** —— 一个进度条,每张卡片显示一个 agent 当前的具体操作(triaging / tracing / analyzing / validating / summarizing),包含实时任务计数和 **实时每 agent 成本**,一个运行中的 **成本 / token / 调用** 读数,交互式渲染的 **代码图**(高亮显示 sink 和 source),以及流式传输的事件日志。点击 agent 可查看其完整的记录,然后点击任何任务可深入了解实际的 **LLM 交互** —— system prompt、用户输入、模型的推理/thinking(如果暴露)、回答以及 token 计数。
- **会话仪表板** —— 列出每一次扫描;打开其中一个可查看 **每个 agent 的成本** 明细、发现表、保存的代码图、agent 记忆,以及 markdown / SARIF / JSON 生成物的下载链接。
实时更新通过引擎事件总线上的 Server-Sent Events 进行流式传输,因此 UI 能准确反映 pipeline 正在执行的操作。标志:`--port`、`--host`、`--workshop-dir`、`--no-open`。
UI **完全离线** 运行 —— 图形库在本地集成,无需 CDN。Provider 设置包含一个 **验证 SSL 证书** 开关(取消勾选可跳过自签名 / MITM 代理 endpoint 的 TLS 验证 —— 不安全,配置键 `providers..verify_ssl: false`)。捕获的 LLM 交互将保存到会话的 `artifacts/traces.jsonl` 中;使用 `trace.enabled: false` 可禁用捕获。
## 配置 (`icewall.yaml`)
每个 agent 的模型分层是关键:将廉价的 token 花费在高频的分诊和追踪上,将强大的模型用于分析/验证/修复。Provider 可以混合使用 Anthropic 和任何兼容 OpenAI 的 `base_url`(本地模型、网关、vLLM)。
`icewall init-config` 会生成一个带有注释的起点配置(该仓库还附带了一个现成可编辑的 [`icewall.yaml`](icewall.yaml))。关键部分:`providers`、`agents`(角色 → provider/model/max_tokens/thinking_tokens/skills)、`concurrency`、`budget`、`scan`、`skills`。
**每个 agent 的 API 密钥。** 附带的配置为每个 agent 提供了自己的 provider 块,因此你可以为每个 agent 粘贴不同的密钥(和模型)。每个 provider 接受内联的 `api_key:`,或者最好是使用 `api_key_env:` 指定环境变量名称。想共享一个密钥?将每个 agent 指向单个 provider 即可。内联密钥是明文的 —— `icewall.yaml` 默认被 git 忽略;对于任何要提交的内容,仍然首选使用环境变量。
## 成本与进度
每次扫描都会根据当前 Anthropic 的标价报告按模型计算的 **估算成本**(`icewall/cost.py`),并提供每个模型的明细(调用次数、token、$)。CLI 会显示一个实时的 **进度条**,随着 pipeline 各个阶段的推进显示实时的成本读数,并且成本也会包含在 markdown/JSON 报告中。
Mock/dry-run 扫描是免费的。成本是基于 token 使用量的估算,而非实际计费。对于内置表格中没有的模型(自定义或 endpoint 模型),请在配置的 `pricing` 部分设置确切的费率,以使估算准确:
```
pricing:
my-model:
input: 0.60 # USD per 1M input tokens
output: 2.50 # USD per 1M output tokens
```
自定义费率会根据确切的模型 ID 覆盖内置表格;未列出的模型将退回到保守的默认值。
## Workshop(每次会话的结果)
每次扫描都会打开其专属的 **workshop** 文件夹,这样多次运行就不会互相覆盖:
```
.icewall/-/
session.json run metadata: target, config, stats, cost, status
artifacts/ report.md, report.sarif, report.json (always written)
memory/
master.md auto-maintained index of everything the agents learned
notes/.md one sub-note per fact (attack surface, paths, verdicts)
```
报告会自动保存在这里 —— `--output` 仅用于制作额外的副本。CLI 会在扫描完成时打印会话路径。
```
workshop:
enabled: true
root: .icewall
keep_last: 0 # keep only the N newest sessions (0 = keep all)
```
标志:`--workshop-dir DIR`, `--no-workshop`。
## 动态上下文管理
长追踪和深层的 source→sink 路径可能会超出模型窗口。当 agent 组装的上下文超过 `max_context_tokens` 时,**summarizer** 会将非锚点块压缩至 `summarize_to_tokens`,并保持入口点和 sink **原封不动**,从而确保追踪链完好无损。每次压缩都会记录到会话 memory 中,因此不会有任何内容被静默丢弃。如果没有配置 summarizer agent,将使用确定性的仅头部摘要(无需额外的模型调用)。
```
context:
enabled: true
max_context_tokens: 6000
summarize_to_tokens: 2000
```
## 会话 Memory
Agent 会在 **完成工作时** 写下笔记 —— triage 记录攻击面,tracer 记录每条 source→sink 路径,analyzer 记录候选项,validator 记录判定 —— 从而构建出 `master.md` 以及针对特定主题的子笔记。后续阶段会按文件/漏洞类别 **召回** 相关笔记(例如,validator 会看到 tracer 已经为该 sink 确定的内容),而不是重新推导它们。这是一种确定性的相关性召回,而 **不是** 由额外的 LLM 来决定加载什么:代码图已经按需提供针对性的上下文,因此 memory 的作用是跨阶段的事实共享和可审计的追踪(也是未来增量重新扫描的基础),而无需为每个决策支付模型调用费用。
```
memory:
enabled: true
share_across_stages: true # feed recalled notes into the validator
```
## Agent 技能
每个 agent 在启动时都会将 **skills**(详细的 markdown 知识模块)加载到其 system prompt 中,这样你就可以在不改动 Python 代码的情况下专门化或扩展 agent。内置技能位于 [`icewall/skills/library/`](icewall/skills/library):攻击面分诊、tracer 的污点传播规则、每种 CWE 的 analyzer 深度剖析(SQLi、命令注入、XSS、SSRF、路径遍历、反序列化)、用于 validator 的严苛误报检查清单以及安全修复模式。
技能是一个带有 YAML frontmatter 的 markdown 文件:
```
---
name: sql-injection-analysis
description: Deep guidance for confirming or dismissing SQL injection.
roles: [analyzer, validator] # or [all]; inferred from a role-named folder
priority: 8 # higher loads first
---
Confirm SQL injection only when attacker-controlled data is composed into a
query as *code* rather than passed as a *bound parameter*. ...
```
- **定向:** 技能通过其 `roles` frontmatter 附加到某个角色,或者如果它位于以角色命名的子文件夹(`.../analyzer/foo.md`)中,则会自动附加。
- **自定义技能:** 将 `icewall.yaml` 中的 `skills.dirs` 指向某个目录;那里的同名技能将覆盖内置技能。
- **固定:** 在 agent 上设置 `skills: [name, ...]` 以准确加载这些技能(而不是按角色自动加载)。通过 `skills.disabled` 禁用任何内置技能。
检查每个 agent 将加载的内容:
```
icewall skills # table of role -> skills
icewall skills --role analyzer
```
## 代码图
代码图是 Icewall 对仓库的映射,每一个 LLM 决策都锚定于此。tree-sitter **无需编译或安装任何东西** 即可解析每个文件,为每个函数/方法/类提取一个 **symbol**,并在它们之间建立三种类型的边:
| 边 | 含义 | 示例 |
|---|---|---|
| **call** | 一个 symbol 调用另一个 | `ping()` → `run_report()` |
| **import** | 一个文件从另一个模块引入定义 | `app.py` → `run_report` (来自 `utils`) |
| **inherit** | 一个类扩展另一个类 | `AdminHandler` → `Handler` |
正是这张图让 agent 能够 *跨文件* 追踪受污染的值 —— 通过 call、import 或继承的方法 —— 而不是仅仅从单个代码片段中进行猜测。(异构的 call/import/inherit 模式遵循 *LocAgent* 的图设计,arXiv:2503.09089。)
### 直接检查它(无需 LLM,无需密钥)
```
icewall graph examples/vulnerable_app
```
```
{'files': 3, 'symbols': 11, 'functions': 11,
'call_edges': 2, 'inherit_edges': 0, 'import_edges': 2}
Symbols
+-----------------------------------------------------------------------------+
| Kind | Location | Qualname | Calls |
|----------+--------------+-------------------------+-------------------------|
| function | app.py:19 | ping | get, run_report |
| function | app.py:26 | search | cursor, execute, ... |
| function | app.py:42 | download | get, join, send_file |
| function | app.py:50 | calc | eval, get, str |
| function | utils.py:5 | run_report | system |
| function | utils.py:11 | safe_lookup | cursor, execute, ... |
| function | server.js:14 | anonymous@14 | String, eval, send |
| ... | | | |
+-----------------------------------------------------------------------------+
```
这里的两条 `import_edges` 是 `app.py` 中的 `from utils import run_report, safe_lookup` —— 每一个都被解析为 `utils.py` 中的实际定义,而不仅仅是通过名称进行猜测。
### 一个实际示例:跨文件命令注入
将 `Calls` 列读作边。上表中两个 symbol 形成了一条文件视图永远无法捕获的路径:
```
app.py:19 ping() # Flask handler: reads request.args.get(...) ← SOURCE
└─ calls run_report() # edge resolved across files
utils.py:5 run_report() # calls os.system(cmd) ← SINK
```
一个用户可控的值在 `app.py` 中进入,但到达了 `utils.py` 中的 `os.system`。以下是图如何驱动 pipeline 走过这条路径的:
1. **预过滤** 将 `ping` 标记为污点相关(它读取 `request.args`,一个 *source*),并将 `run_report` 标记为污点相关(它调用了 `os.system`,一个 *sink*)。这些信号来自 `icewall/detectors/patterns.py`。
2. **Triage** 确认 `ping` 是真实的攻击面(一个 HTTP 入口点)。
3. **Tracer** 从 `ping` 开始,沿着 **call 边** 走到 `run_report`,并且 —— 因为 sink 位于另一个文件中 —— 它通过图(`neighborhood()`)向 orchestrator 请求该函数的源代码,从而组装出完整的 `ping → run_report → os.system` 路径。
4. **Analyzer** 发出带置信度的针对特定 CWE 的判定(命令注入)。
5. **Validator** 独立检查可行性和 guard,然后确认或拒绝 —— 这就是减少误报的支柱。
正是这张图使得第 3 步在不将整个仓库倾倒给模型的情况下成为可能:tracer 沿着边 **仅** 拉取它需要的那一个函数。
### Import 边 —— 精确的跨文件解析
仅仅依靠 Call 边会将一个名称链接到 *每一个* 使用该名称声明的 symbol(成本低,但会过度近似)。**Import 边** 增加了精确度:Icewall 解析每个文件的 `import` / `from … import` (Python) 和 `import … from '…'` (JS/TS) 语句,将模块字符串解析为仓库中的实际文件,并记录一条从引入文件到其所拉取定义的边。
```
from icewall.graph import build_graph
g = build_graph("examples/vulnerable_app")
g.imported_symbols("app.py") # → [run_report, safe_lookup] (the real defs in utils.py)
g.importing_files(run_report.id) # → ['app.py'] (reverse edge)
g.imports("app.py")[0].target_file # → 'utils.py' (module resolved to a file)
```
外部模块(标准库 `os`、第三方库 `flask`)无法解析为仓库文件,因此它们不会产生边 —— 图始终保持专注于 *你的* 代码。相对引入(`from .helpers import x`, `import './utils.js'`)会根据引入文件所在的目录进行解析,包括 `__init__.py` / `index.js` 包入口点。
### Inherit 边 —— 通过类层次结构追踪污点
**Inherit 边** 将子类连接到它扩展的父类,因此通过继承或重写的方法流动的污点不再是不可见的:
```
# base.py: class Handler: def handle(self, x): ...
# app.py: class AdminHandler(Handler): ... (导入 Handler)
g.bases(admin.id) # → [Handler] (Python bases, resolved across files)
g.subclasses(handler.id) # → [AdminHandler] (reverse edge)
```
它们同样适用于 Python(`class Foo(Base)`)和 JS/TS(`class Foo extends Base`)的提取;TypeScript 的 `implements` 子句特意 **不** 被视为继承。至关重要的是,`neighborhood()` 现在默认会遵循 inherit 边,因此当 tracer 检查子类或重写方法时,它扩展的基类会自动被拉入上下文。
### 图如何在 UI 中渲染
在扫描期间,同一张图会被流式传输到浏览器(`graph_data` 事件),并由本地集成的 Cytoscape.js 绘制 —— **无需 CDN,完全离线工作**。`icewall/graph/view.py` 将 `CodeGraph` 转换为节点/边并为它们着色:
- 🔴 **sink** —— symbol 包含危险调用(`os.system`、`eval`、`cursor.execute`, …)
- 🟢 **source** —— symbol 读取不受信任的输入(`request.args`、`req.query`, …)
- 🔵 **function** —— 其他一切
边带有一个 `kind`(`call` 或 `inherit`),因此这两种 symbol 到 symbol 的关系是可以区分的;import 边(指向文件而不是 symbol)会在 payload 的 `import_edges` 计数中进行汇总。大型仓库会被限制为连接最多、最相关的污点 symbol(`cap`,默认为 300),以保持视图清晰,并且每个会话中保存的 `graph.json` 允许你稍后从会话仪表板重新打开确切的图。
### 代码图会导致遗漏漏洞吗?
是的,原则上会 —— 这就是为什么存在扫描 **强度**,以及为什么图带有 import 和 inherit 边的原因。有两个图的限制值得注意:
- **基于名称的边解析是过度近似的,但也可能出现连接不足。** 纯粹通过动态调度(在 runtime 查找的值、反射、存储在字典中的 callback)进行的调用可能不会留下静态边,因此 tracer 不会遵循它。**Import 边** 通过将名称绑定到它来源的特定模块来缩小跨文件调用的这一差距,而 **inherit 边** 可以恢复通过继承/重写方法流动的污点。
- **source/sink 预过滤器可能会跳过** Icewall 无法识别为模式的 sink(围绕危险调用的自定义包装器)的函数。
**Exhaustive** 强度直接解决了第二个问题:它绕过了预过滤器,因此 *每一个* 函数都会被分诊,但成本更高。过度近似的边是故意的 —— 它们引导 LLM 找到候选路径,而 analyzer/validator 会确认真实的可达性,而不是盲目信任边。
## 语言
通过 tree-sitter 支持 Python、JavaScript、TypeScript(包括 TSX)。添加一种语言只需在 `icewall/graph/languages.py` 中定义一个 `LanguageSpec` —— 声明其函数、类、调用和 import 节点类型,以及它如何拼写继承(Python 的 `superclass_field`,`extends` 风格语法的 `heritage_nodes`)。
## 外部扫描器
**v1 不需要任何外部工具** —— 引擎仅使用 LLM + tree-sitter。一个可选的 `Sensor` 接口(`icewall/sensors/`)是一个有文档记录的切入点,Semgrep (v1.1) 和其他工具将在这里 *播种* 候选项以集中 LLM 的精力;它是附加的,绝不是硬性依赖。
## 开发
```
pip install pytest
python -m pytest tests/ -q # 75 offline tests, no API keys
```
## 项目布局
```
icewall/
providers/ anthropic, openai-compat, mock, base interface
graph/ tree-sitter parsing, code graph, builder, queries
agents/ triage, tracer, analyzer, validator, remediator, summarizer
orchestration/ thread pools, budget, context broker + manager, finding store
detectors/ taint source/sink/sanitizer patterns
skills/ markdown expertise loaded into agents at spawn (+ library/)
sensors/ optional external-scanner seam (Semgrep stub, v1.1)
report/ SARIF + markdown
ui/ FastAPI app + static SPA (settings/presets, live view, dashboard)
workshop.py per-session working folder (artifacts + memory)
memory.py session memory: master.md index + relevance recall
engine.py the orchestrator pipeline
cli.py typer CLI (scan, graph, skills, ui, init-config, …)
examples/vulnerable_app/ deliberately-vulnerable sample repo
tests/
```
## 注意事项
- 基于名称的 *call* 解析是过度近似的(用于引导上下文;由 LLM 确认可达性)。*Import* 边的解析更加精确 —— 模块字符串被映射到实际的仓库文件 —— 但仍然没有完整的跨模块类型解析,因此通过包重新导出或动态构建路径引入的名称可能无法解析。
- 修复建议是 **供人工审查的提案**,绝不自动应用。
- mock provider 是一个用于离线/开发/测试的简易模式匹配器 —— 真正的精确度来自于配置真实的模型。标签:AI安全, Chat Copilot, LLM多智能体, 代码安全审计, 代码知识图谱, 图数据库, 逆向工具, 错误基检测, 静态代码分析