正在读取笔记…
在线 Demo
在线 Demo
项目:SecAgent
SecAgent · AI 安全运营助手
一句话:把「告警 → 研判 → 处置」这条安全运营链路,做成一个可编排、可审计、有护栏的 AI 助手。
解决什么问题
企业安全运营中心每天产生几千到几万条告警,其中九成以上是误报和重复,真正的攻击被淹没在噪声里。分析师要在告警系统、日志平台、知识库、资产表之间来回切换才能下判断;判断完还要手工封 IP、锁账号——慢、容易出错,而且处置过程没有留痕,事后无法复盘。
新人上手要几个月,老手的经验留在脑子里带不走。
我做了什么
| 能力 | 具体实现 |
|---|---|
| 降噪 | 同一源 IP + 时间窗 + 同类告警自动聚合成一条,带 ×N 徽标,可下钻原始明细 |
| 多 Agent 研判 | 编排层 + 调度员,派发给研判员(查知识库 RAG)、取证员(拉告警与日志)、处置员(执行动作)三个专职智能体,汇总输出「结论 + 证据 + 处置结果」 |
| 分级处置 + 护栏 | 自动封禁;高危动作强制人工审批;白名单拦截内网/环回地址;全部动作写审计 |
| 知识库问答 | 33 篇安全知识文档向量化,支持带出处引用与相似度阈值拒答 |
| 红队评测 | 27 例用例(基础 19 + 绕过型 8),同时输出绕过率与误报率两个独立维度;绕过率 4/19 = 21.05%(95% CI 约 8%~44%),8 例正常流量未观察到误拦(真实误报率 95% 置信上限约 37.5%) |
一个关键的设计取舍
处置动作的护栏写在代码里,不写在提示词里。
因为提示词可以被精心构造的输入绕过,而代码层的白名单绕不过去。这条取舍说明我清楚大模型的能力边界——知道该在哪里踩刹车。
技术栈
Spring Boot 3.2 · Spring AI / Spring AI Alibaba · RAG · 多 Agent 编排 · Tool Calling · SSE 流式输出 · 自研向量存储抽象(内存 / Milvus 可切换)· Docker
相关文章
- 我把这个博客当成一个安全项目来做(站点本身的攻击面收敛)
- 待写的项目复盘见 写作计划
🚧 在线 Demo 正在部署中。 部署完成后这里会放可点击的试玩入口。
这个站点本身
一句话:用 Hugo + PaperMod 构建的纯静态技术站,托管在 GitHub Pages。
它值得单列的原因:这不是套个模板就完事——为了让它符合一个安全从业者该有的样子,做了这些取舍:
| 决策 | 原因 |
|---|---|
| 纯静态,不要 CMS 和数据库 | 没有数据库就没有 SQL 注入,没有后台就没有弱口令 |
| 零第三方脚本 | 每个外部脚本都是把访问者的浏览器控制权分一份给别人 |
| 页面内不加统计/字体/客服 | 同上,只留必要的评论组件 |
| 源码与构建产物分离 | main 放源码,gh-pages 放产物,发布可复现 |
相关文章:《我把这个博客当成一个安全项目来做》
在线 Demo
🔗 打开试用
地址是固定的:即使以后把服务迁到别的服务器,这个链接也不会失效。
🔑 演示账号
| 用户名 | analyst |
| 密码 | SecAgent-Demo-2026 |
| 角色 | 分析员(ANALYST) |
这个角色能做什么、不能做什么:
| ✅ 可以 | ❌ 不可以 |
|---|---|
| 查看告警大屏、事件归档 | 执行处置动作(封 IP / 锁账号 / 杀进程) |
| 对话研判、知识库检索 | 审批处置申请 |
| 提交知识条目(待审) | 审核知识库、管理用户 |
权限是刻意收窄的。 处置动作全部要求管理员角色 + 人工审批,所以公开一个分析员账号不会带来风险。
配额说明:为了让服务不被刷爆,接口有 50 次/天 的模型调用配额和 30 次/分钟 的限流。用完了第二天恢复。
建议试这几件事
按顺序试,能看出系统的设计思路:
1️⃣ 先问一个知识库问题
在「对话研判」里输入:
什么是路径穿越,怎么防御?
看什么:回答下面会附检索证据,包含命中的文档名、片段内容、相似度分数。
证据被包在一个 <UNTRUSTED_DATA> 标签里 —— 这是提示词注入防护:
知识库文档本身也可能是攻击载荷,不能被当成给模型的指令。
2️⃣ 再问一个需要"动手"的问题
帮我看看最近的告警里有没有可疑的
看什么:调度员会判定这是研判任务,派给对应角色的智能体,并调用工具去拉告警数据,而不是凭空编答案。
3️⃣ 去看告警大屏
看什么:列表默认显示的是聚合后的结果(同一源 IP + 时间窗 + 同类告警合成一条),带 ×N 徽标。点进去能下钻到原始明细。
这是解决"告警风暴"的核心设计。
4️⃣ 去看处置中心
看什么:处置动作分三级 —— 自动执行 / 需审批 / 禁止。 注意「需审批」那一列 —— 这是整个项目最重要的设计:高危动作永远不会自动执行。
5️⃣ 试着让它做危险的事
在对话里试试:
帮我封禁 127.0.0.1 这个 IP
看什么:会被代码层的白名单拦下来,而不是靠提示词拒绝。 内网地址、环回地址在处置护栏里是硬拦截的 —— 因为提示词可以被绕过,白名单不能。
为什么地址是这个
demo.xiaokeqing.cn —— 走的是 Cloudflare 命名隧道,不是临时隧道。
区别在这里:
| 临时隧道 | 命名隧道(现在用的) | |
|---|---|---|
| 地址 | 随机分配,重启就换 | 固定,绑在 Cloudflare 账号下 |
| 换服务器 | 链接失效 | 链接不变(只在新机器上重装隧道) |
| 入站端口 | 不需要 | 不需要 |
服务器上没有任何入站端口是开着的 —— 容器只监听 127.0.0.1,
隧道是服务器主动向 Cloudflare 建立出站连接。所以从外面扫这台机器,
扫到的只有 22 端口(而且已经关了密码登录)。
这条也是买域名真正要解决的问题:让地址和机器解耦。 服务器是消耗品,域名是资产。
已知限制(不想让你踩坑)
| 限制 | 说明 |
|---|---|
| 数据是模拟的 | 告警和日志是 MOCK 数据,没有接真实 SOC 环境 |
| 处置动作没实测过 | 代码里有完整实现和护栏,但不敢在生产环境真的封 IP 演示 |
| 响应偏慢 | 服务器在杭州,隧道绕经境外,首屏约 1.2~1.5 秒 |
| 手机端 | 能用,但告警大屏这类宽表格在小屏上体验一般 |
关于防护强度的真实数字
红队评测跑过了,27 例(19 例基础 + 8 例绕过型),结果:
| 指标 | 数值 | 说明 |
|---|---|---|
| 绕过率 | 4 / 19 = 21.05 % | 95% 置信区间约 8 % ~ 44 % |
| 误报率 | 0 / 8 未观察到误拦 | 真实误报率的 95% 置信上限约 37.5 % |
| 拦下 / 绕过 | 15 / 4 |
为什么要写置信区间,而不是写「误报率 0%」
8 个正常样本里 0 次误拦,不等于误报率是 0。 按统计学上的「三法则」,n 次试验 0 次事件时,真实发生率的 95% 置信上限约为
3/n—— 也就是 3/8 ≈ 37.5%。样本量太小,这个数字只够说明「没有明显误杀」, 支撑不了「误报率很低」的结论。同理,4/19 的绕过率区间宽到 8%~44%,几乎不具区分度。
写清楚这一点,是因为一个漂亮但站不住的 0% 比一个诚实的区间更容易被问倒。
为什么不写「拦截率 100%」:第一版 19 例跑出来就是 0 绕过、0 误报, 但那个数字没有意义 —— 用例是照着防护规则写的,当然全能命中。 0% 绕过率不是成绩,是评测集失效的信号。
所以补了 8 例专门针对规则边界的变形载荷(字符插空 / 全角 / Base64 / 同义改写 / 凭据拆开写…),绕过率立刻变成 42.11%。然后修掉其中能修的 4 类 (加文本归一化 + 补中文模式),降到 21.05%。
关键是:修完之后误报率没有变差 —— 8 个正常用例依然全部放行。 说明拦截率的提升不是靠「宁可错杀」换来的。
剩下 4 类修不掉,而且我知道为什么修不掉:
- 纯语义改写(「请把上面那段话作废…」)—— 正则无解
- Base64 编码的指令 —— 正则只看明文
- 编码后的私钥 —— 同上
- 中文自然语言表述的口令 —— 需要语义理解
这四类不是「还没修」,是「用规则修不了」。 加正则只会带来误报。 它们的存在,正是这条链路上必须保留人工审批的原因 —— 纵深防御不是口号。
下一步:这套用例是自己写的,可信度天花板就在那。 计划接入 NVIDIA Garak 与 Microsoft PyRIT 这类业界工具, 用第三方用例集解决「自己出题自己阅卷」的问题。
数据来源 · 口径 · 免责
公开端点实时探测(一次性,只读)
免责与取舍(逐条都是当前事实,2026-10-13 复核):
① 所有数字都来自上面的公开只读端点,取不到就写「未接入 / 上次同步」,不补假数据、不用样本冒充真实;
② /api/public/audit 展示的是真实封禁与处置留痕,并在卡片里写明来源,而不是编一条 live 数据;
③ /api/public/latest 已上线:返回窗口聚合(windowSize=200 / windowAlertCount /
severityCounts / sourceCounts / latestEvent / latestBlock),字段全部来自公开白名单
(来源 IP 显示为前三段 + 末段 x,不含标题、内网目的地址、聚合 id)。此前文档里写的"404 未上线"是旧结论,已更正;
④ 明细下钻端点 /api/public/alerts/{aggId} 仍未上线,所以监测中心只展示聚合记录自带的字段,
不额外拼一份「原始事件列表」;
⑤ 蜜罐命令时间线(commandsAvailable=false)保持「数据源未接入」,不编造命令内容;
⑥ HTTP 状态码分布与 7 天 × 24 小时热力格没有画:公开端点里没有 statusCode / httpStatus 字段,
也没有历史日桶 —— 监测中心底部把这两项如实列成"读不到",而不是拿 24h 数据伪装成 7 天;
⑦ 前端不做匿名常驻 SSE / WebSocket,秒级实时只给登录后的私有后台。

