boringllm/Icewall

GitHub: boringllm/Icewall

一款基于 LLM 与多 Agent 架构的大型代码仓库漏洞扫描器,通过构建代码图并追踪污点传播路径来发现漏洞并提供修复建议。

Stars: 0 | Forks: 0

Icewall

# 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多智能体, 代码安全审计, 代码知识图谱, 图数据库, 逆向工具, 错误基检测, 静态代码分析