wr0ld

基于 LangChain 与 LangGraph,花了一周 vibecoding 了一个渗透测试 Agent-v1.0

安全工具 / 工具 2026-09-17 5,958 字 预计 15 分钟

项目地址:https://github.com/wr0ld/security-agent

目录

一、前言

Security Agent 是之前用 vibecoding 从零写的一个渗透测试 Agent,技术栈是 LangChain + LangGraph。刚开始完全没有思路。后来读了 CyberStrikeAI 的思路,借鉴一部分。

前后写了一到两周,空余时间看看。很多第一眼像「调个参数」的问题,写下去才发现是结构问题:状态放哪一层、证据谁来回收、已确认的结论怎么才不重复验证。基础部分写完,效果还算明显——Pikachu 靶场的大部分漏洞能自己找到并落地成报告。当然比不上大佬的作品,这个项目更多还是当学习作品。目前只是 v1 基础版本,Skill、MCP 都还没适配,能用的是内置工具和 YAML 声明的命令行工具。

话说:这东西能当毕业设计吗?vibecoding 也要学吗?

二、技术架构

Security Agent 是基于 LangGraph 与 LangChain 的单 Agent Web 安全测试系统。核心不是固定扫描流水线,而是由持久化 State 驱动 规划 → 执行 → 证据回收 → 状态归并 → 知识沉淀 → 再规划 的完整闭环。

这部分面向架构评审与团队协作,重点说明系统分层、核心链路、关键工程处理和当前技术边界。

架构总览

Web 安全测试的后续动作依赖实时响应、认证状态和新证据,长任务还需要恢复、停止和过程观测。系统因此将模型决策、工具执行、状态持久化与 Web 连接拆成独立层,通过 State 和统一协议协作。

层级 核心职责
LangGraph 控制层 节点编排、条件路由、Action 循环、Fact Sync、恢复入口与终止判定
LangChain 模型层 Provider 适配、流式响应、原生 Tool Calling、结构化输出与 Prompt Cache
Tool Runtime 工具注册、Schema 可见性、运行开关、调用复检、进程执行与结果归一化
State / Storage Agent State、Checkpoint、Project Fact、Vulnerability、Artifact 与遥测持久化
Web Runtime 后台任务、SSE 订阅、断线续接、State 查询、运行控制与前端可观测性

图中实线表示主控制与数据链路,虚线表示不改变主路由的旁路能力,包括节点级 Checkpoint 和运行事件流。

核心术语

术语 项目内含义
Agent State Conversation 级决策黑板,保存当前控制状态、Action、摘要和索引
Action / Completed Action Planner 生成的工具执行指令,以及 Executor 写回的实际执行记录
Run Conversation 内一次独立执行实例,拥有自己的状态、Checkpoint 和输出目录
Checkpoint 节点完成后保存的可恢复 State 快照
Project Fact 从真实 Action 中沉淀、可跨 Conversation 复用的项目认知
Vulnerability 满足复现条件并进入交付链路的结构化漏洞记录
Artifact 工具输出、HTTP 响应和 Transcript 等完整原始证据
Resident / Dynamic Tool 始终提供完整 Schema 的常驻工具,以及在延迟加载启用时按需解锁的动态工具

运行

首次部署使用 setup.ps1 创建 .venv 并安装、校验依赖:

1
.\scripts\setup.ps1

启动 Web 服务:

1
.\.venv\Scripts\python.exe web_app.py

模型和运行参数通过 Web 设置页配置,保存于本机 memory/*.json;config_examples/ 提供手工配置模板。memory/ 已被 Git 忽略,不提交密钥和机器级运行差异。

LangGraph 控制回环

固定扫描顺序无法适应不断变化的目标状态;一次生成过长计划又容易继续执行已经失效的假设。控制层因此采用”小批次决策、逐项执行、结果回写、重新规划”的循环,并由 route_next 统一决定下一跳。

节点 输入职责 输出职责
parse 新任务与初始上下文 初始化完整可执行 State
resume 最近 Checkpoint 与本轮新输入 恢复已发生事实并清理失效执行意图
planner 目标、Action 历史、摘要、事实/漏洞索引与可见工具 Tool Call 批次或结束判断
executor action_queue[0] Completed Action、Artifact 引用、错误与工具状态更新
fact_sync 尚未沉淀的 Action 结果 可跨 Conversation 复用的 Project Fact
route_next 当前完整 State planner、executor、fact_sync 或结束

Planner

Planner 使用 LangChain 原生 Tool Calling,不要求模型生成自定义计划 JSON。Tool Call 转换为 Action 前,后端执行确定性处理:

  • 只接受当前可见且启用的工具;
  • 过滤 Schema 未声明参数,补充默认值,检查必填项与枚举值;
  • 对名为 url、base_url 的参数执行基础格式处理和有效性检查;
  • 单轮 Action 上限由 max_actions_per_plan 控制,默认值为 5。

超出上限的 Action 不进入队列,其工具名与参数作为”未执行信息”反馈给下一轮 Planner,避免模型误认为已完成。

提高 max_actions_per_plan 可以减少 Planner 往返次数,但会扩大同一批次基于旧信息连续执行的范围;降低该值会增加规划频率,但每次决策能更快吸收最新工具结果。

Executor

Planner 生成的 Action 批次进入队列后,Executor 每次只消费一个 Action。调用前再次检查工具注册和启用状态,并建立 Project、Conversation、Run、Action 的归属上下文;执行结果统一封装后写入 completed_actions。队列清空后回到 Planner,使一次模型决策的盲执行窗口限制在一个小批次内。

Fact Sync

Fact Sync 在达到 Action 间隔、上下文压缩前、任务结束前和状态恢复后触发。它只处理同步游标之后的新 Action,并将稳定认知写入 Project Fact Store。控制回环由此不仅消费工具结果,还能把已验证认知带入后续 Run 和其他 Conversation。

State 与持久化模型

控制状态、长期知识、漏洞交付、原始证据和运行遥测具有不同生命周期。如果全部进入 State,不仅会放大 Checkpoint 和模型上下文,也会使恢复语义与数据归属混乱。系统因此按职责拆分数据源:

数据模型 Scope 权威存储 生命周期与用途
Agent State Conversation 当前内存状态 / Run Checkpoint 当前控制流、Action、摘要、错误和工具解锁状态
Run Checkpoint Conversation / Run Checkpoint 文件 节点级恢复点与最终运行状态
Project Fact Project SQLite 跨 Conversation 复用的目标、认证态、技术栈、入口和约束
Vulnerability Project SQLite 已确认漏洞、POC、复现步骤和关键证据
Artifact Project / Conversation / Run / Action 文件系统 完整工具输出、HTTP 响应和 Transcript
Process Event Conversation / Turn SQLite process_events 节点、工具、进度和执行时间线
LLM Usage Project / Conversation / Run SQLite llm_usage Token、缓存命中与模型调用耗时

Agent State 负责控制流,Project Fact 负责长期认知,Vulnerability 负责交付,Artifact 负责原始证据,Process Event 与 LLM Usage 负责运行观测。各层只交换必要索引和引用。

Artifact Scope 与不可覆盖证据

Executor 在工具调用期间建立包含 project_id、conversation_id 和 run_id 的 Artifact Context:

1
2
3
4
5
6
7
artifacts/projects/{project_id}/
├── project-scoped artifacts
└── conversations/{conversation_id}/
└── outputs/{run_id}/
├── actions/{action_id}/attempt_NNNN/
├── context_history/
└── tool_results/

project:// 与 conversation:// 分别绑定当前项目和会话。路径解析执行 canonical path 校验,拒绝越出 Scope 的相对路径;Fact 与 Vulnerability 工具的归属 ID 由 Executor 注入,不依赖模型传参维持隔离。

YAML Command Artifact 采用 Action / Attempt 两级追加式存储。不同 Action 的输出物理隔离;同一 Action 重试时分配新的 attempt_NNNN,已存在文件拒绝覆盖,从而保留每次执行的独立证据和时间顺序。该策略只约束自动产物,write_file、edit_file 仍保留显式修改语义。

SQLite 自检与迁移

数据库文件位于 artifacts/security_agent.db。启动时通过 schema_version 比较当前版本与程序支持版本,并顺序执行缺失 Migration;单个 Migration 使用 BEGIN IMMEDIATE 事务,失败整体回滚。数据库版本高于程序支持版本时拒绝写入,连接统一启用 Foreign Key、WAL 和 Busy Timeout。

该机制使 Conversation、Project Fact、Vulnerability、遥测等结构可以随程序版本原地升级,同时避免半迁移状态继续运行。

上下文工程(Context Engineering)

上下文压力主要来自 Tool Schema 数量、单次工具大结果和多轮 Action 历史。系统不使用一种摘要策略处理全部数据,而是在内容进入模型、写入 State 和保存 Checkpoint 的不同阶段分别控制规模。

Prompt 分层与缓存

Planner Prompt 按变化频率拆分为 System Prompt、Tool Catalog、Project Memory 和 Turn State。稳定前缀与高频变化状态分离:OpenAI 路径使用 Responses API Prompt Cache Key,Anthropic 路径对稳定内容块设置 cache_control,减少重复输入成本和缓存失效范围。

分阶段规模控制

机制 解决的问题 触发条件 State / Prompt 保留内容 完整内容位置 是否调用 LLM
Tool Schema 延迟加载 工具数量扩大 Prompt 启用业务工具达到阈值,默认 20 Resident 完整 Schema、Dynamic 名称索引及已解锁 Schema YAML Registry 否
Tool Result 卸载 单次结果过大 序列化结果超过字符阈值 元数据、头尾预览和 Artifact 引用 outputs/{run_id}/tool_results/ 否
Action 历史摘要 多轮历史累计过大 预计上下文达到上限的 80% 累计摘要 + 最近约 30,000 Token Action 原文 outputs/{run_id}/context_history/ 是
Checkpoint Action 裁剪 Checkpoint 重复保存已归档正文 Action 已完成摘要和 Fact Sync Action 基本字段 + 结果状态、数量和路径 Transcript / Tool Artifact 否

启用业务工具数量未达到阈值时,Planner 获得全部已启用工具 Schema;达到或超过阈值后,Dynamic Tool 先以名称索引提供给模型。阈值由 tool_search_min_tools 控制:降低该值会更早减少 Prompt 中的完整 Schema,但可能增加 Tool Search 次数;提高该值则相反。

tool_search 命中的工具写入 Conversation State 的 unlocked_tools,下一轮开始注入完整 Schema,后续 Run 也可恢复该集合。该字段减少重复检索,但长期累积会重新增加 Prompt 中的 Schema 规模。

历史摘要触发前先执行 Fact Sync,随后把将被压缩的 Action 完整写入 Transcript Artifact,再与既有摘要合并。下一轮只携带累计摘要和近期 Action 原文;需要核对细节时,模型可通过文件工具按路径和行号回读原始 Transcript。

Checkpoint Action 裁剪不重新总结内容,只从已有可靠副本且完成知识沉淀的旧 result JSON 中删除正文、重复预览等大字段。它与 Tool Result 卸载、Action 历史摘要分别处理单个结果、推理历史和持久化快照三个规模问题。

context_metrics 是唯一进入 State 的上下文指标快照,用于记录当前估算与压缩结果;该字段每轮覆盖。Token、耗时和缓存明细不随历史进入 Planner。

工具运行时(Tool Runtime)

命令行扫描器与 Python 内置工具具有不同的启动方式、参数结构和返回格式。如果每个工具都实现独立执行链,Executor 会与具体工具耦合,新增工具也会重复实现校验、超时、输出解析和 Artifact 管理。Tool Runtime 因此以统一协议隔离模型、控制层和真实执行代码。

三类协议对象

对象 生产方 → 消费方 内容
Tool Schema YAML Registry → LLM 工具名称、描述和参数约束
Tool Call LLM → Planner 工具名称、参数和 Provider Call ID
Tool Result Tool Runtime → Executor 统一的 status/data/error 执行结果

YAML 同时承载”给模型看的工具声明”和”给后端看的执行配方”,Tool Registry 将工具名称映射为统一 Python callable。

执行适配

生成 Tool Schema 时只提取名称、描述和参数约束;command、cwd、timeout、输出源和 Artifact 模板等后端细节不发送给模型。

工具类型 实际执行方式 统一处理
Command Tool 根据 YAML 渲染命令并启动子进程 默认值与枚举、Profile、超时/取消、流式输出、text/json/jsonl 解析
Builtin Tool 直接调用注册的 Python 函数 Scope 注入、运行状态复检和 Tool Result 封装

Executor 只依赖统一 callable 和 Tool Result,不感知底层工具类型。新增 CLI 工具主要通过 YAML 声明接入,无需改动主控制回环。

工具分类与运行开关

类型 Schema 可见性 运行开关
Resident 始终注入完整 Schema 强制启用,不允许前端关闭
Dynamic 按 Context Engineering 的阈值与会话解锁状态注入 支持前端启停

YAML enabled 定义版本库默认状态,memory/tool_settings.json 只保存当前机器相对默认值的 override。工具关闭后会从启用列表、Planner 可见集合、Tool Schema 和 Tool Search 结果中移除;Executor 调用前仍执行最终复检,避免历史 Action 绕过当前运行策略。

证据与知识闭环

工具输出既不等于可复用知识,也不等于已确认漏洞。系统将”当前决策依据””跨会话认知””漏洞交付”和”原始证据”分开处理,并用来源 Action 建立追溯关系。

输入 处理链路 最终用途
普通工具结果 Completed Action + Artifact 当前 Planner 决策与原始证据回读
尚未沉淀的 Action fact_sync → Project Fact Store 跨 Conversation 复用的稳定认知
满足复现条件的发现 record_vulnerability → Vulnerability Store 漏洞交付、去重与复现

Project Fact 保存来源 Conversation 和 Action。模型提出的每条 Fact 必须引用当前同步批次中的真实 Action ID,服务端复核后才允许写入,避免无证据结论进入项目记忆。

Vulnerability 保存入口、参数、请求方法、POC、复现步骤、响应证据、严重程度和来源 Action,并使用项目、漏洞类型、方法、标准化 URL 与参数生成稳定去重键;重复发现更新原记录并累计出现次数。

完整扫描结果保存在 Artifact,State 和业务表只保存推理索引、交付字段及证据引用。最终链路为:

1
2
3
工具证据 → Completed Action → Planner 判断
├→ Project Fact → 后续 Conversation / Run
└→ Vulnerability → 漏洞交付与复现

Checkpoint、恢复与停止

长时间扫描可能因用户停止、进程异常或服务重启中断。系统需要保留已验证证据,但不能机械重放中断时尚未执行的队列,否则可能重复触发扫描、写文件或其他带副作用操作。

节点级 Checkpoint

Web Runtime 在每个 LangGraph 节点完成后保存 Run Checkpoint,并通过临时文件替换保证原子写入。正常完成、用户停止和异常分别写入最终 Run 状态。

Checkpoint 保存清理后的 Agent State:

  • 剥离 Token、耗时等遥测字段;
  • 将 Artifact 绝对路径转换为项目/会话相对引用;
  • 按 Context Engineering 规则裁剪已完成知识沉淀的旧 Action。

每个 Run 复用同一 Checkpoint 文件进行节点级原子覆盖,避免重复保留整份状态历史。

State 级恢复

当前 resume 是 State 重建与重新规划,不是从中断节点机械续跑。恢复遵循”保留已经发生的事实,丢弃尚未执行的意图”:

  • 保留 Completed Action、上下文摘要、Tool Search 解锁结果以及摘要和 Fact Sync 游标;
  • 从 SQLite 重新加载最新 Project Fact 与 Vulnerability,避免继续使用 Checkpoint 中的过期项目索引;
  • 清空未执行队列、当前 Action 和瞬时结束状态,结合本轮新输入重新规划;
  • 历史 Action 尚未完成知识沉淀时,先执行 Fact Sync,再进入 Planner。

Checkpoint 不恢复外部工具进程、进行中的 HTTP/LLM 请求或 LangGraph 精确节点指针,恢复目标是重建决策依据。

取消信号与进程树停止

停止控制使用独立运行时控制面。每个活动 Run 注册内存 threading.Event,停止接口只设置 Event;取消信号不进入 Agent State、Checkpoint、SQLite 或 Planner Prompt。

Executor 只向声明支持取消的工具注入内部 _cancel_event,该参数不进入 Tool Schema。YAML Command Tool 与 search_files 已接入统一 Command Runner,执行循环通过 Event.wait(0.2) 阻塞等待,停止时立即唤醒;超时和用户取消复用同一进程树终止入口。

Windows 下优先通过 Job Object 终止父进程和全部派生进程,并以 taskkill /T /F 兜底;其他平台按进程组终止。已经产生的部分输出和 Artifact 保留,Tool Result 标记为 stopped,未执行队列被清空,最终 State 以停止状态写入 Checkpoint。

Web 运行时与可观测性

扫描任务通常长于一次浏览器连接,运行状态、控制台日志和性能数据的频率也完全不同。Web Runtime 将任务执行、SSE 订阅、高频输出和遥测持久化拆开,避免页面生命周期或日志流量影响 Agent。

通道 实现 职责
Agent 执行 独立后台线程 任务生命周期不依赖页面连接
状态事件 AgentEventStream + SSE 模型增量、节点状态、工具进度和结果推送
控制台日志 tool_console_tails 快照 隔离高频 stdout/stderr
运行遥测 process_events / llm_usage 执行时间线、Token、缓存命中和耗时
State 查询 项目黑板与 Run API 展示持久化后的真实 State 和业务数据

后台执行与 SSE 续接

SSE 只是 AgentEventStream 的订阅者,不承载 Agent 任务本身。页面刷新、关闭或网络断开只移除当前订阅,后台线程和 LangGraph 链路继续运行。

事件流为每条事件分配连续序号并保留最近一段内存历史。前端重连时提交最后收到的序号,服务端先补发遗漏事件,再恢复实时推送;轮次标识用于隔离同一 Conversation 中的不同任务,避免重复展示和跨轮混合。

高频工具输出隔离

外部工具可能持续产生大量 stdout/stderr。如果逐行写入 SSE 重放历史和 process_events,长任务会放大内存、数据库与页面恢复成本。当前在线页面仍可实时接收输出,但后台按 Action、stdout/stderr 分别维护最近 120 行、单流最多 64 KB 的滚动窗口,并以快照写入 tool_console_tails。

页面刷新后恢复最近控制台记录;工具正式结果仍进入 Tool Result 与 Artifact 链路。Console Tail 只负责运行过程展示,不替代原始证据。

遥测与 State 可见性

Node、Tool、Agent 耗时和过程事件写入 process_events;模型 Token、缓存命中和调用耗时写入 llm_usage。运行中通过 SSE 推送,页面刷新后从 SQLite 重建,不进入 Agent State、Checkpoint 或 Planner Prompt。

前端项目黑板直接查询持久化数据,可查看:

  • Project Fact 与 Vulnerability;
  • 项目下全部 Conversation 及其最新 State;
  • Conversation 历史 Run、运行状态、步骤数和 Action 数;
  • 指定 Run 的原始 Checkpoint JSON。

Run 状态覆盖 running、completed、stopped、error,并对历史遗留状态派生 interrupted、superseded 和 read_error。前端按项目、Conversation、Run 分级加载,避免一次性反序列化全部 Checkpoint。

当前技术边界与演进方向

以下方向用于说明现有边界,不代表已经实现:

领域 当前边界 可演进方向
取消覆盖 YAML Command Tool 与 search_files 可立即停止;等待中的 LLM 请求和未接入取消协议的 Builtin Tool 需等待返回或超时 Provider 请求取消、统一 Builtin 协作式取消接口
恢复粒度 只恢复 State,不恢复外部进程、进行中的 HTTP/LLM 请求或精确节点指针 Action 执行日志、租约与结果对账
副作用窗口 工具已产生副作用但 State 尚未提交时,重新规划可能重复执行 幂等键、Write-Ahead Action Journal
事件流 AgentEventStream 与重放缓冲位于进程内存,服务重启后实时事件不可恢复 持久化事件流与分布式订阅
数据库 SQLite 适合当前单实例部署 共享数据库、分布式任务队列和事件总线
Dynamic Tool 解锁 解锁集合随 Conversation State 持久化,长期会话可能持续累积 TTL、LRU 或阶段性回收策略

三、运行示例

登录与鉴权

登录控制台
登录控制台

主界面

登录后进入主界面:左侧可以新建、删除项目或者会话,同一个项目下的所有会话共享项目黑板(关键信息和漏洞报告);右侧显示当前注册的工具,分为常驻工具和动态工具两类,动态工具可以自定义开关。

主界面与工具面板
主界面与工具面板

靶场实测

本次测试目标是 Pikachu 靶场 http://192.168.3.1/pikachu/。

靶场渗透测试对话
靶场渗透测试对话

简单漏洞可以被很快发现,这一阶段总共耗时 4 分 57 秒,完成了阶段性验证并确认了 4 个漏洞:除了漏洞本身,Agent 还沉淀了两条补充事实:并据此给出下一步的推进建议。

阶段性漏洞汇总
阶段性漏洞汇总

过程可观测

任务执行过程是完全可展开的。任务执行流程面板中可以看到节点流程图(planner → executor → planner → fact_sync)、LLM Token 统计(调用次数、累计 Token、缓存读取量、缓存命中率与当前上下文占用)、关键事件列表和工具执行记录。

任务执行流程与令牌统计
任务执行流程与令牌统计

点击任意节点可以查看更详细的信息:执行序号、状态、耗时、处理事件数,节点处理过程中的 Token 使用与 Fact Sync 结果,以及节点开始和完成时的原始数据。

节点详情弹窗
节点详情弹窗

中断与续跑

长任务难免会因为各种情况中断。Agent 对各类终止场景都做了适配,不会卡住进程;已经确认并落地的漏洞记录与当前执行状态都会保留下来,前端页面刷新也不会影响流式输出的显示。

任务停止与状态保留
任务停止与状态保留

中断之后再次对话,可以接着上次的信息继续推进,不需要从头再来一遍。

多轮对话接着上次继续
多轮对话接着上次继续

项目黑板

项目黑板是整个项目的共享面板,分为项目事实、漏洞管理和会话 State 三个视图。

项目事实是同一个项目下不同会话共同维护的知识信息库。每条事实都有归属模块、状态与确认来源,例如「不安全文件下载模块暴露下载端点」被标记为 surface / confirmed,而「XXE 练习入口……」因为只完成了概述层面的判断,标记为 surface / observed。

项目黑板项目事实
项目黑板项目事实

漏洞管理里是找到并落地的漏洞。下面这次测试大概总共花了 20 分钟,落地了 10 个漏洞,覆盖 CRITICAL、HIGH、MEDIUM 三个级别。

漏洞管理列表
漏洞管理列表

点击任意一条可以看到对应的漏洞报告,包括 POC、复现步骤、验证证据(一路记录到具体的执行产物路径)、以及漏洞 ID、严重程度、CWE 和首次/最后发现时间等记录信息。

漏洞报告详情
漏洞报告详情

会话 State 保存的是最原始的信息。不同会话、不同对话的 State 都有保存并且能直接查看,也能切换查看该会话的历史 Run(运行状态、Action 数量、Checkpoint 体积),排查问题时相当方便。

会话状态与运行历史
会话状态与运行历史

模型与运行设置

设置页左侧是接入的 AI 模型,适配了两种协议,可以填写模型名称、Base URL 和 API Key,并支持一键检测连接、获取模型列表;系统提示词按 Planner 和 Fact Sync 分开维护,直接以 Markdown 文件的形式查看和编辑。

右侧是运行参数,包括上下文管理(最大上下文 Token、摘要触发比例、近期动作保留 Token)和 Agent 运行限制(LangGraph 最大递归步数、单轮最大 Action 数、Tool Search 启用阈值),都可以按需自定义修改。

模型与运行设置
模型与运行设置

Token 缓存

上下文成本也做了考虑:Prompt 按变化频率分层之后,稳定前缀可以命中部分缓存。在模型的调用记录里能直接看到每一次请求的缓存命中率,命中的部分会明显降低 Token 消耗和费用。

接口调用缓存命中
接口调用缓存命中

结语

以上就是这个项目的全部内容:一个基于 LangGraph 的渗透测试 Agent,从架构到落地跑通了一遍。前面「当前技术边界」里列出的那些问题都真实存在,距离真正能用的产品还有不少距离。