先把误报压下去再谈自动化——把初筛交给 Gemini 3.5 Flash Cyber

文章导读
想直接上自动处置的团队,卡点通常不在模型能不能判断,而在于没人能证明初筛在这批告警上比人工稳定。把模型放进流程之前,有三件事必须先落地:这批告警的现状基线、处置动作的分级边界、以及一个随时能关掉的试点范围。缺任何一项就先补数据,而不是继续调提示词。
📋 目录
  1. 壹 先算清这批告警的现状基线
  2. 贰 把处置动作分级,不让模型碰不可逆操作
  3. 叁 给初筛设上线门槛
  4. 肆 安排人工兜底的位置
  5. 伍 小范围试点并留下回滚开关
A A

想直接上自动处置的团队,卡点通常不在模型能不能判断,而在于没人能证明初筛在这批告警上比人工稳定。把模型放进流程之前,有三件事必须先落地:这批告警的现状基线、处置动作的分级边界、以及一个随时能关掉的试点范围。缺任何一项就先补数据,而不是继续调提示词。

先别把处置权交给模型。用两到四周的真实告警算清日均量、误报口径和人工耗时,把动作分成可逆与不可逆两段,模型只在可逆区间出建议;再用样本覆盖、结论可解释、输出可解析、复核通道四条门槛卡住上线,小范围试点并预设回滚条件。任一条不满足,先补数据和流程,不要放量。

先算清这批告警的现状基线

没有基线,“初筛更准”就只是主观感受,后面所有门槛都没法设。基线通常取最近两到四周的真实告警,按告警源和规则 ID 分开统计,节假日、活动期单独标注,否则日均量会被个别高峰拉偏。下面是一个通用聚合骨架,字段名按你自己的日志结构替换:

# 按日期 + 规则统计告警条数,用于确认日均量和长尾规则
jq -r '[.date, .rule_id] | @tsv' alerts.ndjson \
  | sort | uniq -c | sort -k2,2

# 只看某个规则最近 7 天,用于抽样人工复核
jq -r 'select(.rule_id=="RULE_ID") | [.ts, .src_ip, .asset] | @tsv' alerts.ndjson \
  | tail -n 200

已确认误报的口径建议提前写死:需要两条以上人工复核确认,并且能指出是哪条规则在什么上下文里被误触发,才计入“已确认误报”;只是“看着不像攻击”的,归入“待定”。待定和误报混在一起统计,门槛就会被算歪。

人工处理耗时只记方法,不猜数值:有工单的,取“首次响应时间”到“关闭时间”的差值;没有工单的,用告警平台自身的状态变更时间戳计算,并按规则 ID 分组看分布。耗时的中位数和长尾比平均值更能说明哪些规则值得先交给初筛。

把处置动作分级,不让模型碰不可逆操作

分级要在接模型之前做完,否则接进来以后没人说得清哪些动作是“模型建议的”。建议按可逆性切分,而不是按严重程度:

先把误报压下去再谈自动化——把初筛交给 Gemini 3.5 Flash Cyber
级别典型动作可逆性模型参与方式
L0 通知写工单、发消息、记录结论可逆可自动生成内容
L1 取证拉日志、查资产归属、抓取进程信息可逆可自动触发
L2 临时处置限流、短时封单 IP、会话踢下线、打标隔离可逆,有失效时间只出建议,人工确认后执行
L3 不可逆删账号、清数据、下线业务、批量改配置、永久封禁不可逆不参与,模型不输出此类动作

L2 的关键是动作本身带失效时间,并且能一条命令撤回;做不到这点的,都算 L3。模型侧只让它输出“结论 + 建议动作 + 依据字段”的结构化结果,执行层单独写权限判断,看到 L2 以上动作一律转人工,不要指望提示词里写“不要执行”来兜底。

给初筛设上线门槛

把“感觉能用”换成四条可检查的条件,每条都要有对应的验证动作:

  • 样本覆盖:基线期内的每条规则都至少被抽到若干条真实告警跑过初筛,长尾规则单独列出。验证方式是从告警表里按规则 ID 抽样重放,而不是只挑几条典型告警演示。
  • 结论可解释:模型给出的判定必须引用输入中的具体字段(规则名、时间、资产、关联告警条数)。验证方式是人工抽查若干条,删掉 reason 里的泛泛描述后看是否还站得住。
  • 输出可解析:要求固定 JSON 结构,任何多余文字都视为失败并落到人工队列。验证方式是用下面这类校验函数批量跑一遍历史输出。
  • 复核通道:每一条模型结论都必须能回看到原始告警并一键改判。验证方式是让不在项目里的人按流程走一遍改判。
import json

REQUIRED = {"alert_id", "verdict", "reason", "suggested_action", "evidence"}

# 输入:模型原始输出字符串;返回:(是否可用, 原因)
def check(raw: str):
    try:
        obj = json.loads(raw)
    except json.JSONDecodeError as e:
        return False, f"unparsable: {e}"
    missing = REQUIRED - obj.keys()
    if missing:
        return False, f"missing_field: {sorted(missing)}"
    if obj["verdict"] not in {"true_positive", "false_positive", "need_human"}:
        return False, "bad_verdict"
    if not obj.get("evidence"):
        return False, "no_evidence"
    return True, "ok"

提示词按这个输出契约写,模型名和参数以你实际可用的接口为准,下面只是输入输出骨架:

你是告警初筛助手,只做判断,不执行任何处置动作。
输入:告警原文、触发规则、前后 5 分钟内同类告警条数、资产标签。
输出严格 JSON,不要任何多余文字:
{"alert_id":"...",
 "verdict":"true_positive|false_positive|need_human",
 "reason":"不超过两句话,必须引用输入中的具体字段",
 "suggested_action":"none|notify|collect_context",
 "evidence":["字段名=值"]}
约束:无法从输入中确认的内容一律填 need_human,不要推测;
不要输出封禁、删除、下线类动作。

安排人工兜底的位置

兜底不是“模型拿不准就通知一声”,而是要有队列、有时限、有默认动作。

先把误报压下去再谈自动化——把初筛交给 Gemini 3.5 Flash Cyber

复核队列建议按风险而不是按时间组织:L2 相关结论、need_human 结论、以及解析失败的原始输出各排一个队列,按告警级别排序,值班人按队列顺序处理。每条记录要带上原始告警链接、模型结论、依据字段和触发规则,避免复核人自己再去翻日志。

超时未处理的处理规则提前定好:超过约定时限仍无人复核的,维持告警原有状态并升级到上一级值班,不做自动处置。宁可多留一条待处理告警,也不要在无人复核时执行阻断类动作。

模型输出缺失时的默认动作写死为 need_human:接口超时、返回非 JSON、字段缺失、evidence 为空,四种情况都直接进人工队列,不重试到成功为止,也不套用上一次的结论。这条默认规则要能在告警平台里直接看到,而不是只写在文档里。

先把误报压下去再谈自动化——把初筛交给 Gemini 3.5 Flash Cyber

小范围试点并留下回滚开关

试点范围按“一个维度”收窄,不要同时切多个维度:只接单一告警源、或只接某一组规则、或只在业务低峰时段启用,三者选其一先跑,观察没问题再扩。范围在配置里以白名单形式存在,扩量就是往白名单里加条目,回滚就是清空白名单,不要靠改代码回滚。

对照观察的指标沿用第一步的基线:同期误报数与待定数、人工复核改判模型结论的条数、单条告警的人工耗时、模型输出解析失败与超时的条数。人工耗时用同一套时间戳口径统计,才有可比性。

回滚触发条件写成可执行判断,阈值按第一步得到的基线填,不要拍脑袋:

rollback_if:
  - 连续 3 个观察窗内,人工改判模型结论的条数高于该规则基线误报数
  - 连续 2 个观察窗内,模型输出解析失败或超时占比高于 <按基线设定的阈值>
  - 出现任何一条 L3 动作被模型建议并进入执行链路(应为 0)
  - 复核队列积压超过 <值班可承受条数> 且无人处理

触发回滚时只做一件事:清空白名单,让这批告警回到纯人工流程,同时保留模型输出用于离线比对。把误报压到能用基线解释的水平,再谈把 L2 动作交给自动化,顺序反了,试点就会变成一次误封事故的复盘材料。