ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

AiSOC Replay 评估实战:用你自己的历史告警验证 AI 分诊准确率

AiSOC Replay 评估实战:用你自己的历史告警验证 AI 分诊准确率
  • 人工智能
  • AI Agent
  • 网络安全
  • 后端
  • 前端

【免费下载链接】AiSOC

Open-source AI Security Operations Center: alert fusion, LLM-agent triage, MITRE ATT&CK investigation, and a replayable decision ledger for every agent step. Self-hostable, runs with no API keys, MIT licensed. Ships an MCP server for Claude, Cursor and Continue.

项目地址:https://gitcode.com/GitHub_Trending/ai/AiSOC
点击查看免费下载

AiSOC 的公开 benchmark 使用的是合成语料,其中多个维度衡量的是数据基础自身的自洽性,而不是 Agent 的判定准确率。Replay 评估要回答的是一个更关键的问题:在你自己的数据上,AiSOC 的分诊结论与你的分析师过去做出的判断相比,究竟差多少?本文以仓库文档 Replay evaluation 为主线,结合services/actions的历史读取器、services/agents的 replay runner、services/api的编排与存储、packages/aisoc-cli的aisoc replay命令以及packages/aisoc-benchmark的评分实现,完整讲清这套评估从读取历史、冻结上下文、影子模式运行到出报告、签名、脱敏的每一个环节。

一条必须先读的规则:无法命名的标签不计入准确率

AiSOC 无法命名的标签会被排除在准确率之外,永远不会被猜测。

这是评估与销售材料的根本区别,并且它有意牺牲样本量。Splunk Enterprise Security 有Other和Undetermined两种处置,Microsoft Sentinel 有Undetermined分类,Defender XDR 有Unknown。分析师选择这些值时并没有做出判断;如果因为事件恰好被关闭就把它折算成true_positive,等于从"我无法确定"里凭空制造出"同意"。

因此这些记录会被读取、被计数,并以unlabeled上报,永不进入混淆矩阵。如果你的历史大部分都没有标签,报告会直接告诉你这一点,而不是用寥寥几行算出一个看似自信的数字。这一约束在代码层同样成立:HistoricalFinding.is_labelled只在disposition != "unlabeled"时为真,见 findings.py。

统一标签体系(canonical taxonomy)

厂商标签会映射到 AiSOC 回写 SIEM 处置时使用的同一套体系,因此一个 verdict 在两个方向上含义一致。

Canonical含义
true_positive对恶意或未授权活动的有效检出
benign_true_positive对已授权/预期活动的有效检出。规则是正确的,因此不是误报,也绝不计入规则的误报率
false_positive无效检出,目标条件并不存在
benign真实但无威胁的活动,不对规则正确与否作出声明
needs_review证据不足以安全决策
escalate分析师强制升级
unlabeled不是判定。分析师没有记录任何本平台可命名的结论,排除出准确率

区分benign_true_positive与benign是整套体系中容易被忽略但很关键的一处:前者是"规则对、活动也符合预期",后者只是"这件事没有危害",并不为规则的准确性背书。

每个厂商需要什么

历史读取器位于 alert_history.py,通过 replay_history_router.py 暴露的POST /api/v1/replay/history被 API 调用。可以 replay 的连接器类型注册在 vendors.py 的REPLAYABLE_CONNECTORS中,目前是五种:splunk、microsoft_sentinel、elastic、qradar,以及通过azure_defender连接器映射到defender读取器(仓库里没有独立的 Defender XDR 连接器,azure_defender收集的三个凭据恰好够用)。

Splunk Enterprise Security

通过notable宏读取状态为 5(Resolved)和 6(Closed)的 notable —— 在定制化安装上,正是这个宏负责解析正确的索引。

六种内置处置的映射如下。5 和 6 是故意不映射的,站点从 7 开始添加的自定义处置也会被读成unlabeled。

Splunk ESCanonical
disposition:1True Positive, Suspicious Activitytrue_positive
disposition:2Benign Positive, Suspicious But Expectedbenign_true_positive
disposition:3False Positive, Incorrect Analytic Logicfalse_positive
disposition:4False Positive, Inaccurate Datafalse_positive
disposition:5Otherunlabeled
disposition:6Undeterminedunlabeled

Enterprise Security 通常会被深度定制,因此读取器接受search_override。如果你的站点重命名了状态,或者把复核状态放在自己的 lookup 里,可以传入自己的 SPL。它必须返回这些字段:event_id、rule_id、rule_name、urgency、disposition、review_time、reviewer、comment。

它还应当返回 notable 的资产与身份字段 ——src、dest、dvc、orig_host、user、src_user,默认搜索正是这么做的。| fields只保留被点名的字段,所以省略它们的搜索会产生既无主机也无用户的 finding,随后分诊就会在一个只说明"谁报的警"、却完全不说"发生在谁身上"的告警上被打分。

在连接器配置里,saved_search会被翻译成读取器的search_override,ssl_verify翻译成splunk_verify_ssl;凭据侧的base_url、token、username、password分别映射为splunk_url、splunk_token、splunk_username、splunk_password。这段翻译是纯函数、只写在vendors.py一处,因为键名落错位置不会抛异常,只会让工厂返回None,最终以"没有可用凭据"的形式被拒绝。

Microsoft Sentinel

读取properties/status eq 'Closed'的事件,并跟随nextLink,从而把一个大于单页的窗口完整读完,而不是被截断成"排序靠前的那些"。

TruePositive映射为true_positive,BenignPositive映射为benign_true_positive,FalsePositive映射为false_positive,Undetermined映射为unlabeled。classificationReason与classificationComment作为分析师的理由被一并携带。

事件的properties不包含账号也不包含主机,因此读取器还会调用每个 incident 的entities端点并附加结果。应用注册因此需要具备在工作区上读取 entities 的权限;如果没有,该 incident 仍然会贡献分析师的分类并进入分诊,只是缺少主机和用户,而不是让整个窗口失败。

Elastic Security

Elastic 不提供处置字段。关闭一个 signal 只会把kibana.alert.workflow_status置为closed,并且不记录任何原因。

所以一个未打标签的 Elastic 部署产出的是零个标签,读取器会如实说明,而不是推断"关闭的信号就是真阳性"。如果你希望 Elastic 历史被打分,请在kibana.alert.workflow_tags中采用下面这些值:

Workflow tagCanonical
true_positivetrue_positive
benign_positive或benign_true_positivebenign_true_positive
false_positivefalse_positive
benignbenign

如果一个 signal 同时携带两个冲突标签,结果为unlabeled,因为从中挑一个就是猜测。实现上map_elastic_disposition先收集所有可识别标签的映射,只有恰好一个时才返回它。

IBM QRadar

读取status = CLOSED的 offense。offense 本身只带一个数字型的closing_reason_id,所以 id 到文本的对照表是从你的 appliance 上/api/siem/offense_closing_reasons读取的,而不是硬编码 —— 关闭原因在站点层面可配置。

三种内置原因的映射如下。自定义原因,或者 appliance 拒绝解析的原因,都是unlabeled。

QRadar closing reasonCanonical
False-Positive, Tunedfalse_positive
Non-Issuebenign
Policy Violationtrue_positive

"Non-Issue" 被刻意映射成benign而不是benign_true_positive。它不对规则正确与否作出声明;仅凭分析师说"什么都没发生"就为检出的准确性记上一功,会虚高规则的表观质量。

如果对照表查询被拒绝,offense 仍然会被读取,但每一行都落到unlabeled。部分答案胜过没有答案。

Microsoft Defender XDR

读取status eq 'Resolved'的告警,并跟随@odata.nextLink。

Defender 把两个绝不能混为一谈的字段分开:classification是判定,映射到处置;determination是原因(Malware、SecurityTesting、Phishing 等),作为分析师理由携带,永不参与打分。把原因码放进混淆矩阵是范畴错误。

Defender classificationCanonical
TruePositivetrue_positive
InformationalExpectedActivitybenign_true_positive
FalsePositivefalse_positive
Unknownunlabeled

映射在_DEFENDER_CLASSIFICATIONS与map_defender_disposition中实现,且会先做_norm归一化并去掉下划线,因此大小写与下划线差异不会造成静默的unlabeled。

一次 replay 如何运行

影子模式:什么都不写

Replay 运行的是活告警所走的那条分诊路径,不是它的副本:同一个FusedAlertTriageWorker.triage,即 Kafka 消费者在每个 fused alert 上都会调用的方法,只是注入了两个不同的 sink。

持久化是注入的。生产环境中,worker 持有一个 writer,负责把 verdict 记录到 Investigation Ledger 与alerts行、写入 outcome prior、排队审批、缓存 verdict 用于去重、并把处置回写到你的 SIEM。在 replay 中,它持有的 writer 会对每一次调用计数,而不执行任何一次。计数会被上报,因为"replay 悄悄不再 replay"和"replay 的写入被抑制"从外部看都是什么都没写,只有计数能区分二者。

实现见 shadow.py:ShadowTriageWriter实现了TriageWriter的每个方法但一个都不执行;FrozenTriageContextReader实现读端口,使每个来源都停在切分点。两者缺一不可 —— 只冻结读而不冻结写,仍会污染下一次运行;只用空 writer 而不冻结读,则仍会读到 replay 期间控制台写入的内容。

完整的调查图在 replay 期间不会运行,因为它执行的每个节点都会把自己写进 ledger。这不影响测量:verdict 在到达升级阶段之前就已确定,并且有测试用一个"试图改写 verdict"的 graph runner 驱动真实 worker 来证明这一点。

时间点上下文:让测试窗口无法自问自答

历史按关闭时间排序并按时间切分,默认 70/30,DEFAULT_TRAIN_FRACTION = 0.7定义在 runner.py。靠后的时间段是测试窗口。恰好在切分瞬间关闭的 finding 留在训练侧 —— 那是冻结上下文可以合法知道的告警,若放进测试窗口,等于拿它自己记忆里的答案去考它。

切分实现是split_by_time(runner.py):

  • 排序键是(closed_at, finding_id)。分析师批量关闭队列时关闭时间会大量并列,非全序排序会让切分、进而让报告依赖输入顺序;
  • train_fraction必须严格在 0 与 1 之间,否则直接ValueError;
  • 索引为max(1, min(len-1, round(len * fraction))),保证训练侧和测试侧都非空;
  • 空历史不允许切分。

组织记忆与 outcome prior 在切分点只捕获一次,整轮运行都由该快照供给。没有这一层,replay 会自我喂养:生产会把每个 verdict 回写成按签名的先验,并在分诊前读取该先验,于是一条告警的结论会自动关闭下一条证据相同的告警,评估打分的其实是它三秒前自己给出的答案。去重缓存在更早一层做同样的事,并且在两个存储里都不留痕迹。

捕获逻辑在 job.py 的_capture_frozen_context:语句取aisoc_context_statements,先验取aisoc_institutional_memory里以outcome:为前缀的键,业务规则取aisoc_business_context_rule_sets,三者各自包在_optional_read的 SAVEPOINT 中。这里用begin_nested而不是普通try/except是有原因的:PostgreSQL 一旦语句失败就会中止整个事务,捕获异常继续执行并不能隔离故障,反而会让后续写报告的UPDATE一起失败,看起来像存储 bug。读不到的部分会记入unread,并由method["frozen_context_unread"]发布到报告里,而不是悄悄吞掉。

确实存在一个诚实的缺口。组织记忆语句在 API 中不携带创建时间,因此无法针对切分点检验,只能保留。报告会以statements_without_timestamp公布处于该位置的语句数量(见 shadow.py 中ContextSnapshot.as_method_note),而不是宣称比数据实际支持的更紧的冻结。

ReplayRunner.run还要求快照的split_at与本次切分点完全一致,不一致直接抛错:"快照不是在同一个瞬间捕获的,就不是冻结"。测试test_a_snapshot_taken_at_a_different_instant_is_refused覆盖了这一点。

报告说了什么

以恶意线索的召回率领衔,因为这正是你要用来决策的数字,也是不均衡队列最容易掩盖的数字。其后是:逐类的精确率与召回率、混淆矩阵、弃权率、带期望校准误差的可靠性分箱、幻觉率及其依据的指标、按规则与按来源的细分,以及 bootstrap 置信区间。

报告内容由 replay.py 的format_replay_report固定渲染,主要分节包括:What was graded、Recall on malicious、Headline accuracy、Against a constant answer、Auto-close and escalation、Time to verdict、Per class、Confusion matrix、Calibration、Hallucination、Per rule、Per source、Cost and latency、Method。

两条规则决定它印什么:

测试窗口内少于 30 个恶意样本时,不印头条准确率。常量MIN_MALICIOUS_FOR_HEADLINE = 30定义在 replay.py,触发时印出的是数量和原因。在一个几乎所有告警都是误报的队列上,一个"全判为良性"的 Agent 会得分很高,那个数字描述的是你的队列而不是产品。

没有分母的比率读作 "not measured",永不读作 0。实现上_pct、_interval、_number、_ms全部以None表示未测量并渲染成 "not measured"。召回列里的 0 表示 Agent 漏掉了该类的每一个案例;"从未被问到"是完全不同的事实。

值得一提的还有Against a constant answer一节:多数类基线、平衡准确率、Matthews 相关系数会紧跟在头条之下打印。在 90% 为误报的队列上,一个什么都不读的 Agent 的普通准确率就是 0.90,只看到头条而看不到常量基线的读者无法分辨两者。平衡准确率是各类召回率的无权均值,因此常量答案固定得 0.50;Matthews 相关系数对任何与标签无关的预测都是 0.00,这是唯一无法用基础比率买到的指标。

replay 不是什么

被 replay 的 finding 由你实时管线使用的同一个连接器normalize()归一化,通过 connectors 服务经 HTTP 调用,因此不存在第二份厂商字段映射会逐渐漂移。它不携带融合给活告警添加的任何东西:相关事件之间的关联、融合后的置信度分数、确定性叙述与实体解析。每份报告都会在方法段里写明这一点。Replay 衡量的是单条归一化 finding 上的分诊,而不是整条流水线。

运行一次评估

入口有三个,且三者驱动同一个 job。由 API 来编排,是因为没有任何单一进程能同时持有涉及的三项服务:services/actions拥有 SIEM 凭据与读取器,services/agents拥有分诊,services/connectors拥有normalize()。三者都把代码打包成顶层app,因此一个进程导入其中两个只会拿到一个。

控制台

左侧导航中的Evaluate on your history,对应页面 evaluate/page.tsx/evaluate/page.tsx),报告视图在 ReplayEvaluationView.tsx。选择一个已连接的来源和一个窗口,运行结束后页面展示报告。

结果的呈现有两处是刻意的:

没有样本量的数字不会出现。恶意召回率与它计算所依据的恶意案例数一起渲染,头条准确率与已作答决策数一起渲染,历史读取量与其中带有分析师标签的数量一起渲染。两次预测上的 1.00 精确率不等于两百次预测上的 1.00,只印比值的面板把这件事丢掉了。

当窗口太薄不足以给出头条时,页面印出原因而不是数字。低于恶意案例下限时,头条卡片显示 "withheld",下面渲染解释句子:窗口里有多少恶意案例、需要多少、以及为什么在更少的样本上算出的数字描述的是队列而不是 Agent。它不是破折号,也不是 0 —— 0 会表示 Agent 答错了每一题,那是另一个事实,也有另一种补救方式。

方法区块就摆在报告旁边,而不是藏在标签页后面:切分点、冻结上下文计数、bootstrap 种子与重采样次数、影子模式拦截了多少次写入,以及 replay 的 finding 不携带的增强项清单。

CLI

aisoc replay --connector-id <uuid> --output report.md

租户来自凭据(--api-key,或AISOC_API_KEY)。没有租户参数,因为参数是一个你自己选定、却没有任何东西去校验的值。

命令实现在 main.py,完整选项如下:

选项作用
--since/--until固定窗口。省略它们则以当前时间为基准,于是第二次运行覆盖不同窗口并产生不同报告
--format markdown\|json\|pdf导出格式。三种都来自运行完成时存储的产物
--output PATH/-o写入位置。不给时 markdown 与 JSON 走 stdout,所有进度行走 stderr,因此aisoc replay ... > report.md只产出报告
--exclude-latency用一句说明替换两个墙钟延迟数字
--no-wait/--collect <id>排队运行、稍后收集
--seed/--resamples修改 bootstrap。两者无论如何都会写进报告
--train-fraction时间切分比例,默认0.7;较早的部分固定冻结上下文,只有靠后的时段被打分
--limit从窗口读取的最大 finding 数,默认1000
--poll-interval轮询间隔,默认3.0秒
--timeout等待上限,默认3600秒;超时会被报告为超时而非失败的评估,并提示用--collect继续收集
--api-urlAPI 基地址,可用AISOC_API_URL提供

几个实现细节值得注意:

  • 进度与报告分流。err_console绑定 stderr,_write_report在无--output时直接sys.stdout.write(response.text),因此 stdout 里只有报告字节,这是"报告逐字节可复现"这一验收标准的前提;
  • 不做本地渲染。CLI 只驱动 API,写出的文件就是 API 返回的字节,即运行完成时存储的字节;
  • PDF 必须落盘。--format pdf且无--output会直接报错 "PDF is binary; pass --output to write it to a file.";
  • 窗口边界不由 CLI 计算。--since/--until只在你给出时才发送;省略时由 API 决定,因为把本地now塞进报告会把 CLI 的时钟带进一个由 API 负责的产物里;
  • 失败即失败。评估状态为failed时,服务端原因原样抛出 —— 一个读不到窗口的 replay 什么都没测到,如实说明才是有用的答案。

API

POST /api/v1/evaluations/replay -> 202 with an id GET /api/v1/evaluations/replay/{id} -> poll to `completed` or `failed` GET /api/v1/evaluations/replay/{id}/export?format=markdown|json|pdf GET /api/v1/evaluations/replay/{id}/decisions

端点定义在 evaluations.py。启动一次运行需要connectors:write,与测试连接器同一道门槛,因为它做的是同一类事:用已存储的凭据向你的 SIEM 发起出站调用。读取报告需要reports:read。

几个行为细节:

  • POST返回202而不是 200。把几百条 finding 跑过模型不是请求-响应式操作,阻塞在这上面的路由会在某个前置代理处超时,而工作在后台继续;
  • 未给出since时,窗口默认为until - 90 天(DEFAULT_WINDOW_DAYS = 90);until <= since直接 422;
  • 不支持被 replay 的连接器类型返回 422 并在消息里给出支持集合,而不是 500;
  • 导出路由在评估不是completed或没有报告时返回409,并带上当前状态与错误原因;
  • 另一租户的评估 id 返回404 而非 403:查询自带租户谓词,"不是你的"和"不存在"在这里是同一个答案,区分它们会确认该 id 存在;
  • PDF 由 WeasyPrint 服务端渲染,logo 内联以避免向客户管理员提供的地址发起出站请求;当原生依赖 Pango、Cairo、GLib 缺失时返回503 并点名缺失的库,而不是给出一个空文件 —— 报告是存在的,另两种格式也能用,这是部署缺少能力而不是评估失败。

结果存放在两张带行级安全策略的租户隔离表中,见 065_replay_evaluations.sql:

  • aisoc_replay_evaluations回答"报告说了什么"。它包含窗口、train_fraction、bootstrap_seed、bootstrap_resamples、样本量(findings_read、findings_labelled、decisions_recorded、graded、malicious_support)、headline_accuracy(因样本过薄而扣留时为 NULL,绝不用 0 表示"未测量")、headline_withheld_reason、完整score/methodJSONB,以及report_markdown;
  • aisoc_replay_decisions回答"为什么这么说"。每条被 replay 的 finding 一行,保存分析师标签、Agent 的verdict与verdict_raw、置信度、tier、decisionJSONB(含分诊收到的证据)与error。verdict_raw与规范化形式并列保存,因为一个分类体系不认识的 verdict 是关于产品的发现,不能被"洗"成规范值。

报告以渲染器产出的样子存储、原样返回,而不是每次请求重新渲染:一份后来被更新的渲染器重新生成的报告,和你当初读到的那份是不同的产物。逐决策行单独存放,因为只有争议某个数字时才会读,而且它们以兆字节计,把两者合表会让每个列表查询都背上几兆证据。

两张表的 RLS 都同时ENABLE与FORCE,策略为tenant_id = current_tenant_id() OR current_tenant_id() IS NULL—— 后一个分支让不绑定租户的跨租户 worker(保留期清理、租户删除)仍能访问;FORCE则保证表属主(执行迁移的角色)也无法绕过。

设计合作伙伴工具包

上面三条路径给你一份报告。工具包把同一次运行包进两样东西:一个"它来自你的部署且未被改动"的签名,以及一次脱敏,让你可以把报告交出去而不必交出整个环境。

make replay-eval ARGS="--connector-id <uuid>"

它需要环境里的两个值:AISOC_API_KEY,以及AISOC_REPLAY_SIGNING_KEY—— 任意一个你自己选定并保管的秘密。签名密钥从不离开你的部署,也无法从 bundle 中恢复,因此你发出去的 bundle 只能证明它由持有该密钥的一方产出且此后未被编辑,仅此而已。未设置时脚本会直接拒绝:AISOC_REPLAY_SIGNING_KEY(或SECRET_KEY)缺失就报错,因为用默认值签名会产生一个"对谁都验得过"的文件,比未签名更糟 —— 它读起来像防篡改。

输出是一个 JSON 证据包:

{ "payload": { "schema_version", "generated_at", "method", "scores", "privacy", "items"? }, "integrity": { "payload_sha256", "algorithm": "HMAC-SHA256", "signature" } }

实际字段名以 bundle.py 的build_replay_bundle为准:payload 里是bundle_schema_version、kind、evaluation_id、generated_at、model、scores、method、privacy,可选items;integrity在 payload 之外,因为签名无法覆盖自身。

验证一份 bundle —— 你的,或我们回发给你的 —— 用:

make replay-eval-verify ARGS="path/to/bundle.json"

当签名与环境中的密钥不匹配时,它以非零码退出并说明,这对被编辑过的 bundle 和由不同部署产出的 bundle 给出同一个答案,因为从外部看这两者是同一个事件。

默认聚合,只有你要求时才带明细。bundle 携带分数、方法与隐私块。逐 finding 的行除非传--export-items,否则不导出;幻觉指标示例也随之一同扣留,因为一个示例是对你的分析师或模型关于某台机器原话的逐字引用。

脱敏做了什么、没做什么。标识符 —— finding id、规则、来源、租户、连接器 —— 被整体替换为稳定 token(RULE_1、HOST_2),因此关于同一条规则的两行仍能看出针对同一条规则,而规则本身未被点名。Agent 的自由文本走同一套 token、加上平台的模式脱敏器、再加一遍对标识符处理没见过的"主机形状"词的扫描 —— 这正是捕捉模型在推理中引用、而别处从未提及的某台机器的方式。公开分类法是故意保留的:SHA-256、CVE-2026-1234、RFC-1918、x86-64都符合主机形状,但都保持可读,因为把推理改写成HOST_4 hashing既保护不了什么也无法复核。分诊收到的原始证据被直接丢弃而非脱敏,理由是它是厂商自由格式 JSON,脱敏它等于信任一套模式已经见过厂商能吐出的每一种形状。

bundle 在privacy块里自陈其局限,而不是留给你推断:自由文本处理抓不到什么、token 映射从不导出、证据已被扣留。发出去之前先读它。

出站流量按实际分类,不按假设。每个 bundle 都记录是否有任何东西离开你的环境,依据的是运行实际解析到的模型,而不是你传的 flag(见 run_replay_eval.py 的classify_egress):

状态判定条件
none每次调用都走本地模型,或根本没有发起调用
hosted有第三方 provider 应答 —— 除非传了--hosted,工具包拒绝产出 bundle
unverified发起了调用但网关未归属模型,如实记为未知而不是悄悄记成none

本地 provider 前缀白名单是ollama/、ollama_chat/、vllm/、lm_studio/、llamafile/、text-completion-openai/、huggingface/、local/,加上不发起任何调用的deterministic;不在此列且不是确定性层的模型一律按 hosted 处理 —— 这是安全方向,因为没听说过的 provider 更可能是新的托管服务而不是新的本地服务。直接调用本地 Ollama 而不经内置网关时,调用发生了但无法归因模型,这就是unverified这种第三态存在的意义。

可复现性,精确表述

在同一固定窗口上的两次运行之间,报告逐字节复现,唯独两个墙钟延迟数字例外。

其余一切都是输入与代码的属性:bootstrap 是带种子的并记录其种子(BOOTSTRAP_SEED = 20260926,BOOTSTRAP_RESAMPLES = 1000),切分是对(closed_at, finding_id)的全序,渲染器不输出时间戳、不迭代任何未排序集合。平均与 p95 延迟度量的是运行所在的机器,同一台主机上的两次运行都会不同,更不用说两台。

--exclude-latency(CLI)与export_latency=true之外的exclude_latency=true(导出路由)会把这两个数字替换成一句说明。那是对已存储产物的过滤,不是第二次渲染;删除该行的函数与输出该行的函数放在一起,二者无法漂移。

两条必须直说的注意事项:

  • 固定窗口。省略--since或--until会以当前时间为基准定窗口,第二次运行就是另一个窗口,因而报告不同,而原因与确定性无关;
  • 可复现不等于准确。确定性的模型路径之所以复现,是因为它确定性。指向托管模型时两次运行可能不同,那是模型的属性而不是这条流水线的属性。

c2e 测试 支撑这一声明:它针对一个持有 200 条已记录关闭 notable 的 mock Splunk ES,穿过 API、actions 服务、connectors 服务与 agents 服务把 CLI 跑两遍并比较字节;它还会不带排除参数地抓取两份报告,断言延迟是唯一不同的行,这样比较就无法在一个"被剥掉了别的东西"的产物上通过。测试同时验证了报告描述了它拿到的历史(Decisions replayed: 60、findings_read: 200、train_findings: 140、test_findings: 60)、头条被打印而非扣留、影子模式确实拦截了写入(persist_auto_triage、write_back_disposition都被尝试过),以及 60 条决策行确实入库。

与之配套的两个门禁脚本保证了跨服务契约不漂移:check_replay_contract_parity.py 同时解析services/actions与services/agents两侧的 dataclass 并双向比较字段集(单向检查会在本侧悄悄丢字段时依然通过);check_triage_context_freeze.py 按CONTEXT_FREEZE_KINDS声明逐源校验冻结的两个半边。

隐私:你的数据去了哪里

读取你的历史发生在你的部署内部,使用你已为该 SIEM 配置的凭据。finding 由你已经在运行的服务处理。

数据只在你配置的模型由第三方托管时才离开你的部署。如果 AiSOC 指向托管 provider,分诊推理所依据的告警内容会以通常方式发送给该 provider,与实时分诊中完全一样。如果你运行本地模型 —— 通过内置网关或你自己的 —— 则什么都不出去。

这是你的模型配置的属性,而非 replay 评估的属性。Replay 不新增任何出站流量:它读取你本就拥有的 finding,并让它们走生产使用的同一条分诊路径。

限制

  • 可达性而非完整性。读取器解析各厂商文档化的响应形状。它们无法知道你的保留窗口是否真的覆盖了你请求的时段,也无法知道你的分析师标注是否一致;
  • 无法解析的关闭时间是错误,不是默认值。一个静默错误的关闭时间会把 finding 放到训练/测试切分的错误一侧,把答案泄漏进它自己的评估,因此它会抛错;
  • Elastic 无标签则无产出,如前所述;
  • 厂商原始标签逐字保留在映射后的标签旁边,因此你可以审查一个你不认同的映射,而不必信任它;
  • 融合的增强项缺席,如前所述。Replay 一次只对一条归一化 finding 打分;
  • 工具调用在结构上为零。影子模式拒绝升级,而升级是这条路径上唯一调用工具的阶段。该字段被记录而不是省略,这样未来某次改动给分诊接上工具时,会表现为一个从零离开的数字;
  • PDF 导出需要原生栈。API 容器镜像装好了 WeasyPrint 的 Pango、Cairo、GLib,本地检出则常常没有。缺失时 PDF 路由返回 503 并点名它们,而不是提供空文件;JSON 与 Markdown 承载同一份报告且不需要额外依赖;
  • 脱敏是模式处理,模式有边界。一个不带数字、不带分隔符的单词机器名与普通单词无法区分;首段与公开分类法冲突的主机(例如字面名为sha-1)会因与SHA-256相同的理由被保留。bundle 在自己的隐私块里点明这两条限制。发出去之前读一遍;工具包让泄漏变得不太可能,而不是不可能;
  • 签名证明完整性与来源,不证明诚实。它说明你密钥的持有者产出了那些字节、之后无人改动。它对窗口是否被公平选择一言不发,这正是方法块走在被签名 payload 内部的原因;
  • 幻觉计数偏高。指标是用合成语料评分器同一套模式从 Agent 自己的推理中抽取的,所以一个形似域名的短语会被检查、若证据中不存在就被计入。公布的是上限,其依据的指标随报告同行,你可以自行重新推导。

与其他评估面的关系

Replay 在 AiSOC 的评估体系中位置明确:合成语料 benchmark 是回归门禁,衡量的是数据基础自洽与流水线一致;replay 是唯一在真实数据上测量"能否信任它"的入口。它也与 MCP 层打通,Agent 可以通过aisoc_list_replay_reports与aisoc_get_replay_report两个工具读取报告(见 replay.ts),且这两个工具会原样透传"头条被扣留"的原因句,不做数字替换 —— 把一次刻意拒绝改写成测量值,正是它们刻意避免的。

要开始,先确认你已为某个受支持 SIEM 保存了连接器,然后固定一个窗口跑一次:

aisoc replay \ --connector-id <uuid> \ --since 2026-07-01T00:00:00Z \ --until 2026-10-01T00:00:00Z \ --exclude-latency \ --output report.md

再一次,同样的命令应当产出一份字节相同的文件;如果你想把它交出去,用make replay-eval生成签名 bundle,先读一遍它的privacy块,再运行make replay-eval-verify确认它完好无损。

  • 人工智能
  • AI Agent
  • 网络安全
  • 后端
  • 前端

【免费下载链接】AiSOC

Open-source AI Security Operations Center: alert fusion, LLM-agent triage, MITRE ATT&CK investigation, and a replayable decision ledger for every agent step. Self-hostable, runs with no API keys, MIT licensed. Ships an MCP server for Claude, Cursor and Continue.

项目地址:https://gitcode.com/GitHub_Trending/ai/AiSOC
点击查看免费下载

相关推荐

上一篇:opencode-claude-auth 安全隐患全解析:token 如何被保护,以及你必须知道的 ToS 边界
下一篇:InterviewGuide 题解:面试题 04.02 最小高度树——用 DFS 二分分治从有序数组构造高度最小的二叉搜索树

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表