告警疲劳:我的安全监控器一天发来82封邮件
一个人运维的安全监控器,应该发多少告警? 按我的数据,大约每天一封邮件,外加偶尔一封真正需要我做点什么的。11月1日,从太平洋时间午夜到13:32,我的监控器把74封邮件交给了邮件服务:55封关于它已经执行的拦截,16封严重(critical)告警,两封每小时流量告警,还有一封来自我Mac上的扫描器。12 按我那天下午的解读,每一封描述的都是边缘已经挡下的例行扫描;3 至于严重级别的洪泛告警,第6节会表明这个解读过于宽容。解决办法是一套策略,而不是一个阈值:每天一封兼作心跳的摘要;拦截通知发到聊天而非邮件;边缘已处理的严重告警并入摘要;云端的每条严重告警都写明处置结果。第二天,一个扫描器在不到六分钟内在我的几个站点上触发了四条严重告警,而我的监控器没有为此发出一封邮件。4 但那一天仍然产生了19封邮件,而不是一封,其中的原因是这个故事里最有用的部分。5
我的监控器的邮件只有我一个读者。收件箱显示74封时,我问的是:这些是例行的,还是真实的威胁?老实说,答案是监控器无法从主题行告诉我,因为每个主题都在用不同的音量说同一件事。1 一个让唯一读者不再阅读的监控器,无论它的检测器还能抓到什么,都已经停止了监控。下面依次是:邮件的计数、取而代之的策略、一个测试案例,以及这个故事的干净版本在三处地方的破绽。
TL;DR
- 13:32前74封,到午夜82封。 在我提出抱怨之前,我自己的表格和从日志推导出的计数一致,都是74封(逐拦截55封、严重16封、每小时两封、本地一封);11月1日全天,日志记录81封,另加一封我手动发出的摘要。125
- 74封中有22封无法证实。 它们来自不检查邮件服务应答就记录“sent”的代码,因此日志证明的是尝试过交付,而不是邮件已经送达。26
- 文献早已给出规则。 Google的《Site Reliability Engineering》(SRE书)只允许三种输出(告警、工单和日志),写道“Every page should be actionable”(每个页面告警都应当是可处置的),并把邮件告警称为“alert spam”(告警垃圾);The Joint Commission(美国医疗机构认证组织)的警示引用了一项估计:“between 85 and 99 percent of alarm signals do not require clinical intervention”(85%至99%的报警信号不需要临床干预)。78910
- 测试案例成功了。 11月2日,一家荷兰托管服务商的一个地址在5分51秒内于我的几个站点上触发了四条严重告警;监控器拦截了它一次,把拦截发到聊天,把每条严重告警都并入摘要,没有发出任何邮件。41112
- 第二天仍然发了19封,而不是一封。 其中14封是严重的WAF洪泛告警,部分事件没有记录拦截或质询;即使边缘一个事件都没有拦截,它们的主题仍写着“WAF blocks”(当天两次;前一天晚上,有一封在边缘既未拦截也未质询任何事件的情况下写着“104 WAF blocks”);我在统计这些数字当天做的第一次改写,仍然把两种计数混在一行里,是本文的审阅发现了这一点。51314
- 有两个信号是错的。 我Mac上的代码签名检查把Apple自己的Spotlight守护进程判成了未签名的加密货币挖矿程序;摘要里的“RUNS SHORT 162/207”(运行次数不足 162/207)结果一部分是睡眠,一部分是实际运行节奏偏慢,按时间推断还有一部分是三次看门狗强制终止。151617
- 改变了什么: 每天一封摘要承载心跳、所有拦截以及云端每条严重告警的处置结果;只有两种并入条件都不覆盖的严重告警、每小时流量告警和本地扫描器仍会立即发邮件;11月1日的82封在11月2日变成了19封。185
1. 11月1日的收件箱
11月1日太平洋时间13:32,我给运行我基础设施的智能体会话写了一条消息:监控器的邮件正在像垃圾邮件一样塞满我的收件箱,有没有什么要改的,这些是例行的东西还是真实的威胁?1 我那天下午记下的表格按发送方统计了午夜以来的74封邮件:每次拦截一封的通知55封,严重告警16封,每小时流量告警两封,以及我Mac上扫描器发来的一封严重告警。1
计数依赖一条规则,所以先说规则,再给数字。在本文中,一封邮件就是监控器日志中记录一次向邮件服务交付的一行。一个脚本遍历了10月31日至11月2日的六个日志文件,把每一条这样的行按数据流和结果分类。5 截至13:32,脚本与我的表格逐流一致:55、16、2、1,共74。2 整个当地日期,到午夜为止,日志记录的邮件为81封(逐拦截55封、严重22封、每小时3封、本地1封),另加一封我在13:48手动发出的摘要,它在监控器日志中没有发送记录,合计82封。51
一致之中有两个细节很重要。我的表格对严重告警写的是“9 before the fold went live at 00:30 PT; the 7 after”(并入机制于00:30 PT上线前9封,之后7封);而日志显示00:30之前有11条严重告警的“sent”行,之后5条,第一条并入拦截通知的严重告警出现在00:54:43,晚于并入改动生效的时间00:46 PDT,而不是我表格中写的00:30。218 总数吻合;我手写的拆分差了两封。第二个细节更大。74封中有22封(逐拦截10封、严重11封、每小时1封)来自这样的发送代码:它把邮件服务的应答丢弃,然后不管返回什么都记录“sent”。26 当天早上01:04 PDT、时钟回拨之前的一次修复,让云端的发送代码记录服务返回的HTTP状态,只有在2xx时才写“sent”;本地扫描器的发送代码则只在请求没有抛出错误时,写下一行简单的“Critical email sent”(严重告警邮件已发送)。6 早先那22行证明的是监控器尝试过;它们并不证明邮件服务接受了任何东西。我10月31日的日志判断云端邮件“most likely were”(很可能已经)送达,这是一个合理的猜测,但仍然是猜测。19
即便是2xx,证明的东西也比“sent”这个词暗示的要少。Resend的文档把email.sent事件定义为“Occurs whenever the API request was successful. Resend will attempt to deliver the message to the recipient’s mail server,”(每当API请求成功时触发。Resend会尝试把邮件投递到收件人的邮件服务器),并把投递列为另一个、更晚的事件。20 发送调用本身只返回一个id。21 下文的每一个计数,都是邮件服务接受的交付次数;对于11月1日的22封和10月31日的10封,共32封,则是监控器尝试的交付次数。5 没有一个是我读过的邮件数。
按小时看,那一天像一道悬崖接一片高原。
11月1日,当地时间:每小时的邮件数与未变成邮件的告警数,并标出13:50的策略变更。22
第一个小时有22封邮件,全部来自未经验证的代码(逐拦截10封、严重11封、每小时1封)。22 此后逐拦截通知以每小时一到五封的速度持续,因为边缘不断拦截扫描器,而每次拦截都给我写一封信。最后一封逐拦截邮件在13:25:57发出;第一条改为并入摘要的逐拦截通知出现在14:20:35。22 13:50变更之后,邮件降到每小时零到两封,而被并入和被抑制的告警升到每小时最多23条。22 流量没有下降。下降的是邮件。
前一天不适合做基线。我11月1日的日志写着“Yesterday had 10”(昨天有10封),脚本也得出相同的数,但10月31日的日志从22:31开始,也就是我重新启用监控器的那个晚上,所以那10封只覆盖了89分钟。15 按我的解读,74这个数字衡量的是检测器在互联网背景噪声下工作了一整天,而不是扫描器的行为发生了变化。
2. 这些告警原本是做什么的
监控器分为两半。云端这一半被调度为每五分钟在我的Mac上运行一次,向Cloudflare查询我每个区域(zone)的防火墙事件和分组请求计数:哪些地址访问了哪些路径、频率如何,以及边缘对每个事件采取了什么动作。1823 Cloudflare自己对这些数据的描述正是合适的框架:“Security Events displays information about requests actioned or flagged by Cloudflare security products, including features such as Browser Integrity Check.”(Security Events显示被Cloudflare安全产品处理或标记的请求的信息,包括Browser Integrity Check等功能)24 本地那一半是一个扫描器,每10分钟分层检查Mac本身:进程及其代码签名、机器上的智能体钩子、在路径上遮蔽系统二进制文件的工具,等等。12
云端这一半如何处理数据,说起来很简单。它按严重性级别(critical、high、medium)和类别生成发现项;对于严重级别,它会先尝试行动,再通知任何人:当某个地址占了足够多的事件时,它把该地址加入一个账户级拦截列表,所有区域都在边缘执行这个列表,有效期七天。124 在11月1日的改动之前,每一次这样的行动也都会产生一封邮件;未被每站点每类别两小时限额抑制的每条严重告警、每次会话结束、每个拦截量大的小时,也都会。185 本地扫描器生成自己的发现项,并自行发送严重告警邮件。25 Cloudflare自己的Alerts有两类,“Monitors that you define yourself, and Platform Notifications”(自己定义的Monitors,以及Platform Notifications):即定时执行的SQL API查询和预配置的产品告警,二者都在其控制台中设置。26 我的两者都不是:它是我Mac上一个自己读取防火墙事件的脚本,这也意味着噪声要由我自己来治理。
3. 文献早已说过的话
下面没有一样是新东西,这正是令人难堪之处。这些内容我大多读过,却还是做出了一个逐事件发邮件的监控器。
三种输出,而邮件不在其中。 Google《Site Reliability Engineering》的导论写道:“There are three kinds of valid monitoring output”(有效的监控输出有三种):告警,“Signify that a human needs to take action immediately”(表示需要人立即采取行动);工单,“Signify that a human needs to take action, but not immediately.”(表示需要人采取行动,但不必立即);以及日志,“No one needs to look at this information, but it is recorded for diagnostic or forensic purposes.”(没有人需要查看这些信息,但它们被记录下来以供诊断或取证)。7 同一页对我当时的设计毫不客气:“a system that requires a human to read an email and decide whether or not some type of action needs to be taken in response is fundamentally flawed.”(一个需要人去读邮件、再判断是否需要采取某种应对行动的系统,从根本上就是有缺陷的)7 按这个分类,我那55封逐拦截邮件是以告警形式投递的日志。边缘已经行动了,没有留下任何需要人去做的事。
每个页面告警都应当是可处置的。 同一本书中Rob Ewaschuk讲监控的那一章,把告警定义为“A notification intended to be read by a human and that is pushed to a system such as a bug or ticket queue, an email alias, or a pager,”(一种供人阅读、被推送到缺陷或工单队列、邮件别名或寻呼机等系统的通知),而关于邮件这一类的脚注写道:“Sometimes known as “alert spam,” as they are rarely read or acted on.”(有时被称为“告警垃圾”,因为它们很少被阅读或处理)9 这一章关于页面告警的规则三行就能写完:“Every page should be actionable.”(每个页面告警都应当是可处置的)“I can only react with a sense of urgency a few times a day before I become fatigued.”(在疲惫之前,我一天只能带着紧迫感反应几次)“If a page merely merits a robotic response, it shouldn’t be a page.”(如果一个页面告警只值得机械式的回应,它就不该是页面告警)8 我对逐拦截邮件的机械式回应就是删除。这一章也点明了噪声的代价:“When pages occur too frequently, employees second-guess, skim, or even ignore incoming alerts, sometimes even ignoring a “real” page that’s masked by the noise.”(当页面告警过于频繁时,员工会怀疑、略读甚至忽略收到的告警,有时甚至会忽略被噪声掩盖的“真正”页面告警)27 它还描述了一个团队,“spent significant amounts of time triaging the alerts to find the few that were really actionable,”(花了大量时间对告警进行分诊,只为找出少数真正需要处置的),这相当准确地描述了我11月1日的那个下午。28
症状优先于原因,以及页面告警的检验标准。 Ewaschuk更早的一篇文章(他说它奠定了那一章的基础)把检验标准压缩成一句话:“Pages should be urgent, important, actionable, and real.”(页面告警应当紧急、重要、可处置且真实)29 关于原因,它在题为“Cause-based alerts are bad (but sometimes necessary)”(基于原因的告警不好(但有时必要))的一节中写道:“Include cause-based information in symptom-based pages or on dashboards, but avoid alerting directly on causes,”(把基于原因的信息放进基于症状的页面告警或仪表盘,但避免直接针对原因告警)。29 我把一次防火墙拦截理解为原因层面的事实:一个扫描器命中了一条规则。我在意的症状是敏感路径被返回或站点宕机;按我的解读,74封中没有一封报告了其中任何一项;当天针对探测路径的请求在敏感路径上没有任何2xx。3 这篇文章也预见到了摘要:“A daily (or more frequent) report can work too,”(每日(或更频繁的)报告也可行),同时警告“without a system of accountability this amounts to less-spammy email alerts”(如果没有问责机制,这不过是垃圾少一点的邮件告警)。30 他的对策是一个人:“make sure the oncall person (or someone else) is designated to triage these every day”(确保指定值班人员(或其他人)每天对这些进行分诊)。30 在一个人运维的监控器上,这个人只能是我。第4节里的处置结果字段是我自己的辅助机制,不是他的:它给每天的分诊提供了可核查的内容,即每条严重告警的去向,但它并不代替分诊本身。
精确率与召回率。 The Site Reliability Workbook(SRE书的实践篇)讲告警的那一章,提供了描述问题所在的词汇。精确率是“The proportion of events detected that were significant.”(检测到的事件中重要事件的比例)召回率是“The proportion of significant events detected.”(重要事件中被检测到的比例)31 我没有测量我的监控器的召回率,那需要统计它漏掉的重要事件数,而第5节描述了它漏掉的一个。作为一条邮件流,它的精确率接近零:74封邮件,按我的解读,其中没有一封要求我做任何事。同一章还定义了重置时间:“How long alerts fire after an issue is resolved. Long reset times can lead to confusion or to issues being ignored.”(问题解决后告警还会持续触发多久。重置时间过长可能导致混乱或问题被忽视)31 重置时间是针对持续触发的告警定义的;逐拦截邮件触发一次之后,就不再带来任何解决进展:它从不告诉你扫描器已经离开。
同一种失败的医院版本。 SRE那一章本身就指向软件之外,指向心脏监护报警管理,“for an example of alert fatigue in another context.”(作为另一种情境下告警疲劳的例子)28 The Joint Commission于2013年4月8日发布的关于医疗器械报警安全的Sentinel Event Alert(警讯事件警示),给出了数字:“The number of alarm signals per patient per day can reach several hundred”(每位患者每天的报警信号数可达数百个),以及“It is estimated that between 85 and 99 percent of alarm signals do not require clinical intervention”(据估计,85%至99%的报警信号不需要临床干预)。10 这份警示有一种值得效仿的审慎:85%到99%涵盖的是不需要干预的报警,而不仅是误报,且警示并未把二者分开。它记录的后果,正是我已经陷入一半的那种:临床人员“become desensitized or immune to the sounds,”(对声音变得麻木或免疫),并且“may turn down the volume of the alarm, turn it off, or adjust the alarm settings outside the limits that are safe and appropriate for the patient”(可能调低报警音量、关闭报警,或把报警设置调到超出对患者安全且适当的范围)。10 它统计了2009年1月至2012年6月间98起与报警相关的事件,其中80起“resulted in death,”(导致死亡),并且在其数据库所报告的因素之外,把报警疲劳列为“the most common contributing factor.”(最常见的促成因素)10 我面临的风险要低得多。机制是一样的:一个人一旦认定报警不需要自己,对下一次真正的报警也会这样认定。
NIST,过去与现在。 NIST事件处理指南2012年的修订版SP 800-61r2曾警告:“it is not uncommon for an organization to receive thousands or even millions of intrusion detection sensor alerts per day.”(一个组织每天收到数千乃至数百万条入侵检测传感器告警并不罕见)32 2025年4月的修订版SP 800-61r3“supersedes SP 800-61r2,”(取代SP 800-61r2),删掉了这句话;最接近的一条是这项建议:“The volume of potentially adverse events to be analyzed is generally quite high, so organizations should rely on technical solutions that filter large event datasets down to a subset that is suitable for human viewing and analysis.”(需要分析的潜在不利事件量通常相当大,因此组织应依靠技术方案,把大型事件数据集过滤成适合人工查看和分析的子集)33 我把这个过滤器当作指令,把摘要当作其中一种。
一份厂商调查,并标明是厂商调查。 Vectra AI的2023年调查由Sapio Research执行,覆盖2,000名安全分析师,报告称“SOC teams receive an average of 4,484 alerts per day, and over two-thirds (67%) of them are ignored.”(SOC团队平均每天收到4,484条告警,其中超过三分之二(67%)被忽略)34 这是一份厂商委托的意见调查,67%是受访者的说法,不是测量结果。我引用它只为说明一点:这种失败在小规模上同样会发生。分析师说他们忽略了每天4,484条告警中的三分之二,而一个没有团队的读者在74封时就不再读了。
4. 以规则形式写下的重新设计
改动在11月1日13:50上线,距我的消息18分钟;14:00的第二次改动收紧了其中一个条件。18 我把它写成规则,每条都附上它存在的理由。
规则1:每天一封摘要,它就是心跳。 当地时间07:00之后的第一次监控器运行,会发出一封邮件,覆盖自上一封摘要以来的时间窗口。1835 摘要自身的说明写出了这封邮件取代了什么:“One email a day … in place of an email per auto-block and per WAF-handled CRITICAL.”(每天一封邮件……取代每次自动拦截、每条由WAF处理的CRITICAL各发一封)35 它以运行健康状况开头(记录的运行次数、开始并完成的运行次数、失败次数、已发送和被抑制的严重告警邮件数),接着是附有区域、原因和网络运营商的自动拦截,每条严重告警及其处置结果,按严重性级别和类别列出的发现项,被探测的路径,以及本地扫描器未解决的发现项。12 做摘要的理由是收件箱。每天都发的理由写在同一段说明里:“The digest is also the heartbeat: it goes out on a quiet day too”(摘要也是心跳:平静的日子里也会发出)。35 只在出事时才开口的告警流,无法让我分辨平静的一天和一个已经死掉的监控器。一封总会到达的摘要可以:沉默意味着故障,而不是平静。这个程序自己的笔记记录了我为何在意这一点:2026年早些时候,这个监控器连续几个月处于失效状态,而没有任何东西察觉。35
规则2:拦截发到聊天,不发邮件。 设计笔记用四个词概括:“Per block: Slack only.”(每次拦截:只发Slack)3 一次拦截是边缘代表我采取的行动。用SRE书的术语,它属于日志:被记录下来,日后有用,不需要人去处置。7 聊天保留实时流,供我想盯着看的时候用;摘要保留记录。
规则3:边缘已处理的严重告警并入摘要。 严重告警仍会先尝试拦截;然后监控器再判断它是否值得一封邮件。3 有两个条件会让它改为并入摘要,按以下顺序检查。第一:发现项自身的地址,以及其中所有符合自动拦截条件的地址,都已在生效的拦截列表上,也就是说边缘此刻正在挡住这些地址。314 这项检查不看任何其他地址,并且在检查样本之前运行,因此被它并入的洪泛,仍可能包含来自未达到拦截门槛的地址、既无拦截也无质询记录的事件;这个分支说的是响应器(responder)的目标已被拦截,而不是每个事件都已缓解。第二个条件适用于WAF洪泛,即某个区域上防火墙事件的突增:只有当样本中每一个事件(无论是否为机器人)都记录了拦截或质询,并且样本完整时,才会并入。“A solved or bypassed challenge, log, allow and skip all count as unmitigated.”(被解决或绕过的质询、log、allow和skip都算作未缓解)3 这四种之所以对并入不利,原因在于Cloudflare的定义:质询之后,“If successful, Cloudflare accepts the matched request”(如果成功,Cloudflare会接受匹配的请求),而log动作只是“Records matching requests in the Cloudflare Logs.”(在Cloudflare Logs中记录匹配的请求)36 持续扫描器的发现项只统计边缘拦截或质询过的事件,因此出于这个原因按WAF并入条件并入;若其地址已在列表上(如11月2日那一条),则按拦截列表并入条件并入。3 其余一切都会立即发邮件。
规则4:处置结果写在发现项上。 策略规定,云端的每条严重告警都带有一个记录下来的处置结果:因地址在拦截列表上而并入、因边缘拦截或质询了每个抽样事件而并入、已发邮件、被抑制(在每站点每类别每两小时一封的限额之内),或邮件发送失败。337 记录没有达到策略的要求。在按UTC计为11月1日的严重告警中,有118条没有处置结果:117条产生于该字段存在之前,另有一条云端严重告警产生于之后的14:13 PST;它所在的运行是第7节中那三次在启动约四分钟后结束的运行之一,在自动拦截过程中、写入处置结果之前就停止了。本地扫描器的严重告警从不带处置结果;11月2日的三条也没有。3738 自11月3日起,监控器会在自动拦截运行之前给严重告警写上“pending”(待定),这样中途被截断的运行会留下一个可见的状态,而不是空白。14 摘要在一个承诺了这一点的标题“Every CRITICAL and what became of it”(每条CRITICAL及其去向)之下,在云端每条严重告警旁边列出处置结果。12 处置结果字段是我的机制,不是Ewaschuk的。他对每日报告的条件是有一个被指定每天分诊的人,在这里这个人就是我;这个字段给分诊提供了可核查的内容。30 每一行都带着处置结果,摘要就能一条一条地回答我在11月1日提出的问题:例行的还是真实的。不过,“emailed”或“folded”说的是一条严重告警去了哪里,并不代表任何事情已经解决。
规则5:保留每小时流量告警。 还是笔记:“Hourly volume alert (responder): still immediate, at most once an hour”(每小时流量告警(响应器):仍然立即发送,每小时最多一次)。3 单次拦截是例行的;一小时内拦截突然堆积,是云端这一半所拥有的最接近症状的东西,即互联网对我的行为发生了变化。它在11月2日触发了一次。5
规则6:本地扫描器的严重告警仍立即发邮件。 设计笔记让它们保持即时,是因为它们本应很少见。3 我保留它们还有第二个理由:边缘挡在我的站点和扫描器之间,而我的Mac和本地发现项之间却没有任何东西挡着。拦截列表能挡住一个地址;Mac之外的任何东西都挡不住Mac上的一个进程。这条规则的代价在第7节显现。
规则上线当天,邮件流从13:32之前的74封,降到了当天剩余时间里日志记录的7封(六条严重告警和一条每小时告警),另加我手动发出的摘要。52 我那天下午写下的预测是“Under today’s traffic that would have been 0 immediate emails instead of 16.”(按今天的流量,即时邮件本应是0封而不是16封)1 这个预测没有成立,第6节解释原因。
5. 测试案例:11月2日,11:03至11:21
规则的第一次真正考验在第二天早上到来:一家荷兰托管服务商名下的一个地址,对我的站点运行了一个漏洞扫描器。12 下面是监控器写下的内容,逐字摘自其日志,使用当地太平洋时间,地址替换为“[the address]”,中间的行已省略。4
[2026-11-02 11:03:53] [INFO] New finding [critical]: Request flood from [the address] on blakecrosley.com
[2026-11-02 11:04:02] [RESPOND] [INFO] Block notification: Slack posted, email folded into the daily digest
[2026-11-02 11:04:03] [INFO] Critical alert folded into the daily digest (IP on the block ledger): Request flood from [the address] on blakecrosley.com
[2026-11-02 11:04:04] [INFO] New finding [critical]: Scanner from [the address] on blakecrosley.com
[2026-11-02 11:04:05] [RESPOND] [INFO] Skip: [the address] already has active real block in blakecrosley.com (dedup)
[2026-11-02 11:04:05] [INFO] Critical alert folded into the daily digest (IP on the block ledger): Scanner from [the address] on blakecrosley.com
[2026-11-02 11:09:22] [INFO] New finding [critical]: 726 WAF blocks in 5 minutes
[2026-11-02 11:09:23] [INFO] Critical alert folded into the daily digest (IP on the block ledger): 726 WAF blocks in 5 minutes
[2026-11-02 11:09:44] [INFO] New finding [critical]: Persistent scanner: [the address] blocked 743 times today
[2026-11-02 11:09:47] [RESPOND] [INFO] Block notification: Slack posted, email folded into the daily digest
[2026-11-02 11:09:47] [INFO] Critical alert folded into the daily digest (IP on the block ledger): Persistent scanner: [the address] blocked 743 times today
逐分钟来看:
- 11:03:53。 请求洪泛检测器针对该地址生成一条严重告警。八秒后,即11:04:01,响应器把它加入拦截列表;11:04:02,拦截通知发到聊天,对应的邮件并入摘要。4
- 11:04:03。 严重告警本身被并入:该地址现在已在拦截台账(block ledger)上,所以边缘已经在做那封邮件本来要请我去做的事。4
- 11:04:04。 第二个检测器,即扫描器检查,为同一地址生成它自己的严重告警。响应器的拦截请求作为重复项被跳过,这条严重告警在一秒后被并入。4
- 11:09:22。 WAF洪泛检测器为该区域生成第三条严重告警。其肇事地址在台账上,于是被并入。411 决定这一结果的只有拦截列表这一条并入条件:抽样事件中有八个既无拦截也无质询记录,且样本已达上限,所以单靠WAF并入条件,它本会被发成邮件。11
- 11:09:44。 持续扫描器检查统计的是边缘在我所有区域上对单个地址拦截或质询过的事件,它生成了第四条,记录在我的另一个站点上:当天来自该地址的被拦截或质询事件共743个。12 响应器把该地址再次写入列表(这是一个缺陷,见下文),发到聊天,并在11:09:47把这条严重告警并入。411
- 11:14:58和11:20:40。 同一区域又出现两条WAF洪泛严重告警;两条都以同样方式并入。11
5分51秒内四条严重告警,到11:20共六条,全部在发现项上记录为因地址已被拦截而并入。11 拦截了一个地址;由于当时的去重检查按站点工作,它的列表条目被写入了两次(已于11月3日修复);另外四个拦截请求作为重复项被跳过。414 邮件:零。日志中11:03至11:21之间没有任何邮件发送记录;那个时段内唯一一条既未并入也未发邮件的严重告警,是在11:03:13被抑制的,那是另一个地址在另一个站点上的洪泛。4
下一封摘要把它呈现为自动拦截表中的两行(第一次拦截,附区域、原因和网络运营商;以及持续扫描器的重新加入),以及“每条严重告警”表中的六行,每行标注“blocked”(已拦截)或“blocked earlier”(此前已拦截)。12 按我的解读,对于一个在首个发现项后八秒、扫描开始后八分钟就被监控器拦截的扫描器,这样的分量正合适:几行可以就着咖啡读完的文字,而不是一次页面告警。4
测试案例没有显示的一件事,是11月3日发现的。同一个扫描器也在向我站点的联系表单提交内容,我的应用对这些提交都返回了200,它们本身至多给我发来31封邮件,也是姊妹篇的主题;而监控器的表单洪泛检查在实时运行中没有生成任何发现项。用完整数据回放截至11:03:23的五分钟,同一检查会以high严重性级别(一行摘要内容,不发邮件也不拦截)生成该发现项,依据是39次提交,其中23次被拒绝。39 实时运行向Cloudflare请求的时间窗口,截止于发出请求的那一刻,即运行开始后37秒,此时最新的事件还没有完全到达:它在该窗口内统计到来自该地址的1,914个请求,而用完整数据回放统计到的是2,143个。439 这个滞后是在该地址的全部请求上测得的,而不是在检查所统计的表单提交上;按我的解读,正是同样的滞后让那些提交在实时运行中停留在检查门槛之下。此外,每次运行都恰好回看五分钟,而运行之间的启动间隔约为5.6到5.8分钟,所以每个间隔都有一部分从未被扫描。3917 修复在当天上线:现在每个窗口从上一个窗口结束的地方开始,在运行前90秒结束;除非上一条记录缺失、不到一分钟,或超过15分钟(例如Mac睡眠之后),这时运行会退回到简单的五分钟回看。39 用修改后的代码回放11月2日的窗口,得到相同的发现项,所以这次改动没有扰乱固定窗口上的检测。回放无法检验窗口衔接(tiling)或滞后,因为二者只在实时运行中起作用;11月3日以后的实时运行就是对它们的检验。39
6. 仍会发邮件的,以及原因
11月2日是新规则下的第一个完整日,它发出了19封邮件,而不是一封:14封严重告警、一封每小时流量告警、一封摘要,以及来自本地扫描器的三封。5 没有发出任何逐拦截邮件;108条拦截通知改为并入摘要,177条严重告警被并入,另有37条被两小时限额抑制,以上均按当地日期统计;本文后面的处置结果计数按UTC日期统计,因此那些数字有所不同。5
按数据流划分的每个当地日期的邮件数;10月31日只覆盖日志开始后的89分钟。5
从13:50变更到11月2日结束,所有发了邮件的云端严重告警共20条(11月1日剩余时间6条,11月2日14条),全部是resumegeni.com或blakecrosley.com上的WAF洪泛,其中至少有一个事件没有记录拦截或质询。13 WAF并入条件按它自己的规则正确地拒绝了每一条:样本中有未缓解的事件,所以监控器无法断言边缘已经处理了它。3 在那些洪泛中,没有哪个地址的事件多到符合自动拦截条件,因此拦截列表那一条并入条件没有地址可查。13 摘要也用同样的方式称呼这一组:“a CRITICAL that was neither blocked nor already handled by the WAF”(一条既未被拦截、也未被WAF处理过的CRITICAL)。12
这些邮件的主题讲的却是另一回事,而这是一个缺陷。洪泛摘要写的是“N WAF blocks in 5 minutes”(5分钟内N次WAF拦截),但N统计的是窗口内所有非机器人防火墙事件,不管边缘对它采取了什么动作。13 下面是发现项中的两个例子,使用当地时间:
| 时间(PT) | 站点 | 主题写的是 | 拦截 | 质询 | 未缓解 |
|---|---|---|---|---|---|
| 11月1日 16:51 | blakecrosley.com | “104 WAF blocks in 5 minutes” | 0 | 0 | 117个事件中的117个 |
| 11月1日 22:45 | resumegeni.com | “108 WAF blocks in 5 minutes” | 18 | 62 | 179个事件中的99个 |
第一封邮件声称有104次拦截,而边缘什么也没有拦截。13 按我的解读,一个在没有拦截记录的事件上写着“blocks”的主题比噪声更糟:它告诉唯一的读者,系统已经处理了一件它可能并没有处理的事。我在11月3日、也就是统计这些数字当天做的第一次修复,本身也是错的。它让N仍取非机器人事件数,却对所有事件统计拦截、质询和其余部分,于是16:51的突增会显示为104 WAF events in 5 minutes: 0 blocked, 0 challenged, 117 through,一行里两个总体。本文的审阅发现了这一点,我在同一天再次修复。14 洪泛摘要,以及随之而来的邮件主题,现在只统计一个总体,即每个被抽样的防火墙事件,显示为N WAF events in 5 minutes: B blocked, C challenged, U unmitigated (S from non-bot user agents);JavaScript质询计为质询,自动拦截的原因显示为N WAF events from this address in 5 min。14 对16:51的突增,这会得到117 WAF events in 5 minutes: 0 blocked, 0 challenged, 117 unmitigated (104 from non-bot user agents)。1314 WAF并入条件本来就以未缓解事件为准,所以现在主题和判定统计的是同一样东西。这次修复改变的是那14封邮件主题所说的内容。它不改变发出的数量,19仍是11月2日的诚实数字。
我在11月1日写下的预测之所以落空,是因为它的前提错了。它说新规则下“would have been 0 immediate emails instead of 16,”(即时邮件本应是0封而不是16封),依据是一种信念:对于那天早上00:30之后的七条严重告警,“Cloudflare had already blocked every one of those requests.”(Cloudflare已经拦截了其中每一个请求)1 发现项对它们同样给出了不同的说法:日志显示00:30之后发了邮件的五条,每条最多记录了一次拦截和九次质询,而它们摘要中的事件数为76到89个。13 而在那天下午和晚上发了邮件的六条严重告警中,每一条边缘都只拦截或质询了部分事件(16:51的突增则完全没有),所以即便是13:50的规则,即只有当拦截和质询覆盖每个非机器人事件时才并入,也会把每一条都发成邮件。131 13:50变更十分钟后,第二次改动让WAF并入条件统计每一个事件(无论是否为机器人用户代理),并且只信任完整的样本;这让规则更严格了,但这不是预测落空的原因。183 预测统计的是我相信存在的拦截;规则统计的是确实存在的事件。
我不知道的是,这14封,即11月2日发了邮件的那些严重告警,是否值得一封邮件。发现项记录了有多少事件是未缓解的,即没有记录拦截或质询,但没有记录它们带着什么动作:是log规则、skip、allow,还是客户端已经解开的质询。13 未缓解的事件也不等于到达了我站点的请求。Cloudflare写道:“A single HTTP request can generate one or more security events when it triggers security features,”(单个HTTP请求在触发安全功能时可以产生一个或多个安全事件),并把log和skip列为不会像拦截那样终止规则评估的动作,所以按我的解读,另一条规则仍可能作用于同一个请求。2436 Cloudflare的GraphQL教程展示了action字段以及"log"和"block"之类的示例值,但并未列出该字段所有可能的取值。23 Cloudflare的Security Events仪表盘也是抽样的:“For large volumes of traffic, Cloudflare uses sampling to return results faster.”(对于大流量,Cloudflare使用抽样来更快地返回结果)24 在监控器记录下每个未缓解事件背后的动作、并把事件与请求关联起来之前,我无法说出这些邮件中哪些描述的是边缘放行的流量,而这个答案决定了它们是否值得一封邮件。
11月2日的三封本地邮件是另一个悬而未决的事项。它们报告了13:50的一次“Browser -> shell chain”(浏览器到shell的调用链),其中一个shell启动了curl,这些发现项至今仍未解决。40 我还没有核实它们是我那天下午运行的智能体工具,还是别的什么,所以我不把它们算作误报。
7. 两个错误信号
原来是Spotlight的加密货币挖矿程序
11月1日10:03,本地扫描器针对Apple自己的Spotlight守护进程spotlightknowledged生成了一条严重告警,“High CPU unsigned process - possible crypto miner”(高CPU未签名进程:可能是加密货币挖矿程序),当时CPU占用为74.9%,两秒后记录了“Critical email sent”。15 错误在我。这项检查在代码签名显示命令codesign -d的输出中寻找短语“valid on disk”。扫描器的笔记如今用一个分句记下了这个缺陷:该短语是“which only codesign -v prints, so every process read as unsigned”(只有codesign -v才会输出的,因此每个进程都被读成未签名)。25 一个跳过列表掩盖了这个缺陷,直到一个Apple框架守护进程的占用飙升,我那天的日志也是这么写的。1 这个发现项现在带着它的结论:“false positive, Apple platform binary”(误报,Apple平台二进制文件)。41
它的代价是一封邮件,也是11月1日唯一一封本地邮件。5 在它之前,10月31日和11月1日的16条本地严重告警,标记的是Xcode、Homebrew的Python、我自己的应用构建、钩子变更、路径上的版本管理器垫片(shim)、一个匹配到另一个项目智能体说明的AI蠕虫模式,以及一个匹配到自身查询的事件监听(event tap)检查;它们产生了15次发送尝试,每一次都失败了。5251915 邮件服务位于Cloudflare之后,而Cloudflare在读取密钥之前,就以错误1010回应了Python的默认用户代理;我故意用一个错误的密钥复现了这一点。19 Cloudflare关于该错误的页面,在其针对Pages和Workers主机的说明中,直接点名了这个客户端:“a User-Agent header that Browser Integrity Check treats as a non-browser client (for example, the default Python urllib user agent).”(一个被Browser Integrity Check视为非浏览器客户端的User-Agent头(例如Python urllib的默认用户代理))42 一个坏掉的发送程序,让我免收了15封关于我自己的工具和我自己的改动的邮件。15 扫描器的笔记直白地写下了更大的教训:“most of the CRITICALs it raised in its first live days were its own noise.”(它上线头几天生成的CRITICAL大多是它自己的噪声)25 尽管如此,我仍让本地严重告警保持即时,并且在每条错误告警的检查处修复它,而不是把整条流静音。
RUNS SHORT 162/207
11月2日早上,第一封按计划发出的摘要带着这样的主题:“Mon Nov 2: 71 blocks on 15 zones, 11 criticals emailed, RUNS SHORT 162/207”(11月2日周一:15个区域共71次拦截,11条严重告警已发邮件,运行次数不足 162/207)。16 摘要的规则是简单的算术:预期运行次数等于窗口分钟数除以五,主题会标出不足。35 窗口从11月1日13:48到11月2日07:05,共1,036.5分钟,即207个五分钟时段;日志记录了162次运行。17
我11月2日的日志用一行解释了这个差距:“The RUNS SHORT line is the Mac asleep,”(RUNS SHORT那一行是因为Mac在睡眠),并认为监控器没有故障。43 这一行在监控器没有失败这一点上是对的(11月2日没有任何运行未完成),在原因上则是错的,无论是对11月2日,还是对这个数字所描述的整个窗口。运行间隔分析把缺失的45个时段大致拆分为:约25个归于运行节奏,约16个归于睡眠,一次提前结束的运行之后的长间隔中约1个,以及三次从未记录自身的运行。17
| 原因 | 时段数(近似) | 证据 |
|---|---|---|
| 实际运行节奏 | 25 | 运行之间的160个普通间隔,中位数5.60分钟,从未低于5.35;合计926.0分钟,相当于185个五分钟时段 |
| 睡眠 | 16 | 三个超过10分钟的间隔,共93.0分钟,位于电源日志的空闲睡眠期内,运行在短暂的维护唤醒期间恢复 |
| 提前停止后的长间隔 | 1 | 第四个超过10分钟的间隔,即14:19那次提前结束的运行之后的10.0分钟 |
| 从未记录自身的运行 | 3 | 165次运行启动,对应162次记录的运行 |
| 这三次背后 | 三次运行在启动约四分钟后结束,没有记录结束行,按时间推断是240秒看门狗,全部发生在11月1日:下午两次,当晚深夜一次 |
电源日志显示23:21:30进入空闲睡眠、00:30:05完全唤醒,以及06:01:20再次空闲睡眠、06:30:28完全唤醒。44 那三次运行在11月1日的14:09、14:19和23:10启动;每次都在约四分钟后结束且没有记录结束行,按我的解读,这就是240秒看门狗。第四次运行在启动28秒后结束,没有可见的原因。1738 在11月2日这个日历日内,288个五分钟时段对应242次运行启动,少了46次,且没有一次未完成;只有两个间隔超过10分钟,共35.8分钟,因此那一天不足的大部分同样来自运行节奏,而不是睡眠。17
错误的假设是我的,就在摘要里:一个设为五分钟的调度器,即便在Mac醒着的时候,也大约每5.6到5.8分钟才执行一次运行,所以五分钟的算术会把健康的一天判为不足。17 我还没有查明醒着时的运行节奏为什么偏慢。我在11月3日替换了这套算术。运行健康状况现在报告记录的运行次数、运行之间分钟数的中位数,以及最长间隔及其时间,主题只在间隔超过20分钟时才标出。那天早上的预览显示“about one every 5.6 min”(约每5.6分钟一次),主题以“RUN GAP 31 min at 05:56”(05:56出现31分钟运行间隔)结尾。14 间隔是我能据以行动的症状:Mac睡着了,或者监控器停了。而相对于一个调度器从未兑现过的节奏的比率,两者都不是。
我保留下来的规则
- 改动邮件之前先计数,按数据流计,并写明规则。 我的表格和日志计数之所以在74上一致,只是因为我先写下了什么算一封邮件。12
- 只有当邮件服务说“是”时才记录“sent”。 我的74封邮件中有22封,我能证明的只是尝试过,仅此而已。26
- 边缘已经执行的拦截是一行日志,不是一条告警。 SRE书的三种输出把它放在那里,而把55封逐拦截邮件挪到聊天中,没有让我丢失任何我曾据以行动的东西。75
- 即使什么都没发生,也要发每日摘要。 一封总会到达的摘要就是心跳;它的缺席就是告警。35
- 只在边缘能证明的条件下并入严重告警。 我的条件是:发现项中可拦截的地址已在拦截列表上;以及样本完整且每个事件都记录了拦截或质询。第二个条件正是11月2日仍有14封发出的原因。313
- 在云端的每条严重告警上写下处置结果。 一份无人负责的每日报告,用Ewaschuk的话说就是“less-spammy email alerts”(垃圾少一点的邮件告警);每一行上的处置结果,正是我每天分诊时核查的内容。3012
- 让主题说出边缘做了什么。 在一次零拦截的突增上写“104 WAF blocks”,是我那一周写得最糟的一行。13
- 报告间隔,而不是相对于计划的比率。 一个五分钟的定时器大约每5.6分钟才执行一次运行,所以162/207看起来像一次故障,其实主要是时钟问题。17
- 在检查处修复误报,而不是在收件箱里。 误读了一条命令的输出,就让Apple的Spotlight守护进程变成了加密货币挖矿程序;修复放进了检查里。25
什么可能会改变
- 如果未缓解的事件被证明是已解开的质询, 仅凭这一点,并不能让那些仍会发邮件的WAF洪泛严重告警变成噪声。当客户端通过质询时,“Cloudflare accepts the matched request”(Cloudflare会接受匹配的请求):请求继续前进,而这既不表明请求无害,也不表明无需任何回应。要并入这些严重告警,需要与同一批事件关联的请求证据,证明其中没有任何需要人处理的东西,而仅有质询完成并不构成这样的证据。如果它们被证明是
log或skip规则,问题就变成这些规则是否在做我想让它们做的事,在我弄清之前,每封邮件都值得一读。监控器目前还没有记录是哪一种。1336 - 如果某天的严重告警邮件重新攀升到几十封, 那就说明每站点每类别两小时的限额在做本该由规则做的工作,规则需要重新审视。3
- 如果摘要不再到达, 那就是监控器和我的收件箱之间的某个环节停了:监控器、它所运行的Mac、发送程序,或者投递路径,因为一次被接受的发送还不是一次已投递的发送。这是这套设计唯一一种把沉默当作“去看看”信号的情形。3520
- 如果本地扫描器生成了一条我无法用自己的工具解释的严重告警, 那么即时邮件就是正确的选择;11月2日那三条未解决的shell调用链发现项,就是对此尚未完成的检验。40
- 如果我增加一个读者, 页面告警和工单就不再由同一个人处理,SRE书中告警与工单的划分就需要一个工单队列,而不是我的收件箱。7
关键要点
如果你为自己运行一个监控器
- 我在半天内74封邮件之前的某个地方就不再读了,而我的收件箱在下午早些时候就达到了这个数目。1
- 一封在平静日子里也会到达的每日摘要,就是一个免费的心跳;一个只在有事件时才开口的监控器,无法告诉你它已经死了。35
- 你的日志可能会对从未发出的邮件写“sent”。在相信计数之前先检查代码。619
如果你要构建一个监控器
- 先行动,再决定是否通知:拦截,然后如果拦截列表或边缘已经覆盖了它,就把告警并入。3
- 把每条严重告警的处置结果记录在发现项上并在摘要中列出,这样Ewaschuk所说的必须对每日报告进行分诊的那个人就有东西可查;在我的监控器上,这个人就是我。3012
- 按边缘为事件记录下的内容来命名事件(拦截、质询、未缓解),一行只统计一个总体,绝不按你希望边缘做了什么来命名。1314
- 测量你实际拥有的调度器。我的在五分钟定时器下每5.6分钟运行一次。17
如果读收件箱的人是你
- 对每条告警都问一句,它是否“urgent, important, actionable, and real”(紧急、重要、可处置且真实);不符合的就该进摘要或日志。29
- 我第一天的预测落空了:它说即时严重告警为零,而第二天发出了14封。15
常见问题
什么是告警疲劳?
告警疲劳,是指接收了太多不需要自己做任何事的报警的人身上发生的情况:用The Joint Commission的话说,他们会“desensitized or immune to the sounds,”(对声音变得麻木或免疫),并开始调低或关闭报警。10 Google的SRE书描述了运维中的同一现象,人们会“second-guess, skim, or even ignore incoming alerts, sometimes even ignoring a “real” page that’s masked by the noise.”(怀疑、略读甚至忽略收到的告警,有时甚至会忽略被噪声掩盖的“真正”页面告警)27 我自己的情况只用了一天:13:32之前74封,而按我那天下午的解读,没有一封要求我采取行动。1
每天多少条安全告警算太多?
对一个人来说,SRE那一章的诚实答案是几条:“I can only react with a sense of urgency a few times a day before I become fatigued.”(在疲惫之前,我一天只能带着紧迫感反应几次)8 Vectra的厂商调查报告称,团队平均每天收到4,484条告警,并据他们自己所说忽略了其中的67%。34 对我这个一人监控器而言,可行的答案是每天一封摘要,加上此刻需要人处理的告警;在11月2日这仍然是19封,而我把其中14条WAF洪泛严重告警当作一个悬而未决的问题,而不是已经定论的代价。513
安全告警应该做成每日摘要还是实时发送?
两者都要,按人需要做什么来划分。监控器视为已处理的一切(它已列入拦截列表的地址,或每个抽样事件都被拦截或质询过的洪泛)进入摘要;其余的在两小时限额内立即发出。3 Ewaschuk承认每日报告“can work too,”(也可行),但有一个条件:没有问责,它就“amounts to less-spammy email alerts,”(不过是垃圾少一点的邮件告警),他希望有人被“designated to triage these every day”(指定每天对这些进行分诊)。30 在我的监控器上这个人是我,我的摘要列出了云端每条严重告警的去向,让分诊有东西可查。12
什么是心跳告警?
心跳是一条按计划到达的消息,无论是否发生了什么,因此它的缺席就意味着某处出了故障。我的每日摘要就是心跳:“it goes out on a quiet day too,”(平静的日子里也会发出),所以一个已经停止运行的监控器会表现为一封没有到达的邮件。35 发送失败或投递丢失也会这样表现,所以一封缺席的摘要提示你去检查监控器、它所运行的主机、发送程序和投递路径。20 它的运行健康状况部分还会报告运行之间的最长间隔,并在主题中标出超过20分钟的间隔。14
为什么Cloudflare WAF告警会淹没我的邮箱?
我的邮箱被淹没,是因为每次拦截和每条严重告警都成了一封单独的邮件,而互联网全天都在扫描每一个公开的区域。5 Cloudflare自己的Alerts,即“Monitors that you define yourself, and Platform Notifications,”(自己定义的Monitors,以及Platform Notifications),可以通过邮件投递,并在其控制台中设置;像我这样基于其API自己构建的监控器两者都不是,它会继承你写进去的任何逐事件发邮件逻辑。26 也要检查一条告警统计的是什么:Cloudflare的动作包括log和skip,它们不会像拦截那样终止规则评估,而客户端解开的质询最终以Cloudflare接受该请求告终,所以“WAF events”和“WAF blocks”是两个不同的数字。36 我的主题在11月3日之前一直把二者混为一谈。1314
邮件API返回2xx是否意味着告警已经送达?
不是。Resend的email.sent事件“Occurs whenever the API request was successful. Resend will attempt to deliver the message to the recipient’s mail server,”(每当API请求成功时触发。Resend会尝试把邮件投递到收件人的邮件服务器),而投递是另一个独立的事件。20 2xx意味着服务接受了这封邮件。记录状态码,并把投递当作另一个问题来对待。6
本站相关文章:为什么垃圾信息打败了我有速率限制的联系表单从联系表单一侧追踪了同一个11月2日的扫描器;每晚入睡前我都会运行的检查是每晚的生产环境检查,是另一个在我不看的时候运行的例行程序;无人监督运行AI智能体时,究竟什么会出问题列出了无人值守的智能体会话的各种失败模式,每种都附有检测信号;每个钩子都是一道疤把我的每个智能体钩子追溯到催生它的那次失败,这与在检查处修复误报是同一种习惯;Claude Code 的 auto 模式不是安全边界论证了智能体周围的围墙比它的分类器更重要;而AI 智能体可观测性阐述了为什么要看清智能体对磁盘、CPU和网络做了什么。
来源
作者自有数据中的时间,除标注UTC者外,均为当地太平洋时间;11月1日02:00从PDT回拨到PST。公开页面于2026年11月3日、在所注明的UTC时间获取。
-
作者的运维日志(Captain’s Log),2026年11月1日:13:32 PT的消息;午夜以来74封邮件按数据流的表格(逐拦截55、严重16、每小时流量2、本地1);13:50的并入规则(拦截和质询覆盖每个非机器人事件);严重告警拆分“9 before the fold went live at 00:30 PT; the 7 after”,以及“Cloudflare had already blocked every one of those requests”这一信念;“Yesterday had 10”;13:48 PT手动发出的第一封摘要;预测“Under today’s traffic that would have been 0 immediate emails instead of 16”;以及跳过列表掩盖了代码签名缺陷的记录。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩
-
作者数据:截至11月1日13:32 PT、由脚本从监控器日志推导的邮件计数:74(逐拦截55、严重16、每小时流量2、本地1),其中22(逐拦截10、严重11、每小时1)来自无条件记录“sent”的发送程序;当地全天为81。同一脚本的逐行清单显示,严重告警的“sent”行在00:30之前有11条、之后5条,第一条并入拦截通知的严重告警在00:54:43。 ↩↩↩↩↩↩↩↩↩
-
作者的监控器设计笔记,通知部分,截至2026年11月3日:“Every one described routine mass-scanning the edge had already stopped”;“Per block: Slack only”;两个并入条件及其顺序;“A solved or bypassed challenge, log, allow and skip all count as unmitigated”;保持即时的每小时流量告警;因本应罕见而保持即时的本地扫描器严重告警。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩
-
作者数据:监控器2026年11月2日11:03至11:21 PT的日志:第5节引用的各行(地址已替换),11:04:01和11:09:46写入拦截列表、有效期七天,四个被作为重复项跳过的拦截请求,聊天发布,以及该时段内除11:03:13另一站点上被抑制的洪泛外没有任何邮件行;生成第一条严重告警的运行于11:02:46开始,其检测器窗口在37秒后的11:03:23结束,其拦截原因统计到该窗口内来自该地址的1,914个请求。扫描的第一批请求在10:56 PT到达站点,比拦截早八分钟(依据作者11月2日的运维日志以及Cloudflare对该地址的每分钟请求计数)。 ↩↩↩↩↩↩↩↩↩↩↩↩↩
-
作者数据:由脚本从监控器2026年10月31日至11月2日的六个日志(云端和本地扫描器)推导的邮件计数,每一行记录一次向邮件服务的交付即算一封。按当地日期:10月31日(日志从22:31开始)10封(逐拦截4、严重6,全部来自无条件记录“sent”的发送程序;3条严重告警被两小时限额抑制;13次本地发送失败);11月1日日志记录81封(逐拦截55、严重22、每小时流量3、本地1;2次本地发送失败),另加一封手动发出的摘要;11月2日19封(严重14、每小时流量1、摘要1、本地3)。11月2日未发邮件的:108条拦截通知和177条严重告警并入摘要,37条严重告警被抑制。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩
-
作者代码:2026年10月31日启用时监控器的发送程序,它丢弃邮件请求的应答,然后无条件记录“sent”;以及11月1日01:04 PDT、时钟回拨之前的改动,此后云端发送程序记录邮件服务的HTTP状态,只在2xx时写“sent”;本地扫描器的发送程序只在请求没有抛出错误时写一行简单的“Critical email sent”。 ↩↩↩↩↩↩
-
Benjamin Treynor Sloss, “Introduction,” Site Reliability Engineering: How Google Runs Production Systems(Google,O’Reilly Media出版)第1章,14:19:33Z获取:三种有效监控输出(告警、工单、日志)的引文,以及把读邮件再判断的告警方式称为“fundamentally flawed”的段落。 ↩↩↩↩↩↩
-
Rob Ewaschuk, “Monitoring Distributed Systems,” Site Reliability Engineering(Google,O’Reilly Media)第6章,14:18:54Z获取:“Every page should be actionable”、关于紧迫感的一句和关于机械式回应的一句的引文。 ↩↩↩
-
Ewaschuk, “Monitoring Distributed Systems,” 14:18:54Z获取:告警的定义,以及关于邮件告警的脚注22(“alert spam”)的引文。 ↩↩
-
The Joint Commission, “Medical device alarm safety in hospitals,” Sentinel Event Alert Issue 50,2013年4月8日,读自Wayback Machine 2024年11月13日的快照,14:21:49Z获取(发布方自己的URL返回403):每位患者每天的报警信号数;85%至99%的估计(警示将其归于AAMI,涵盖不需要临床干预的报警,未单独区分误报);麻木和调低音量的段落;2009年1月至2012年6月间98起与报警相关的事件,其中80起导致死亡;以及“the most common contributing factor”,引自警示中关于其Sentinel Event数据库所报告因素之外的因素清单,该清单与数据库自身的已报告因素清单是分开的。 ↩↩↩↩↩
-
作者数据:监控器发现项文件中关于11月2日扫描器的六条严重发现项(11:03:53请求洪泛、11:04:04扫描器、11:09:22 WAF洪泛、11:09:44持续扫描器、11:14:58和11:20:40 WAF洪泛),每一条都记录为因地址在拦截列表上而并入;11:09:22的WAF洪泛还记录了八个既无拦截也无质询记录的抽样事件,以及与监控器样本上限相等的防火墙事件数,因此其样本已达上限。 ↩↩↩↩↩↩
-
作者数据:2026年11月3日由摘要程序的预览模式渲染的下一封摘要(窗口为11月2日周一07:05至11月3日周二06:17 PST;预览不发送任何内容):其各个部分、本地扫描器的10分钟分层、在每个区域执行七天的账户级拦截列表、附网络运营商的自动拦截表(11月2日的扫描器为一家荷兰托管服务商)、“Criticals that emailed you”标题文字,以及“Every CRITICAL and what became of it”表。 ↩↩↩↩↩↩↩↩↩↩↩↩
-
作者数据:从11月1日13:50 PST变更(处置结果字段由此开始)到11月2日PST结束,所有记录为已发邮件的严重告警,共20条(按UTC计为11月1日和2日的15条,按UTC计为11月3日的5条,发送于16:42、17:58、20:34、21:07和23:08 PST),取自发现项文件并附上决定每条去向的细节:全部是resumegeni.com或blakecrosley.com上的WAF洪泛,含有既无拦截也无质询记录的事件;11月1日的六条中,边缘拦截或质询的非机器人事件数(发现项的acted计数)低于事件数(83中5、153中95、104中0、128中67、135中54、108中80);11月2日有两条零拦截(05:55 PST,质询26;12:06 PST,质询204);对全部20条,监控器日志都显示没有哪个单一地址的事件多到足以拦截;第6节表格中的两行(2026-11-02T00:51:04Z和06:45:59Z);把每个非机器人防火墙事件都计为“WAF block”的摘要文字;16:51的突增,非机器人事件104个、总计117个,无一被拦截或质询;以及日志显示在11月1日00:30至13:32 PT之间发了邮件的五条严重告警,其发现项中的拦截数和质询数(79个事件中0和0、81个中0和9、89个中1和0、84个中1和9、76个中0和5);发现项没有记录这些事件带着什么动作。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩
-
作者代码与数据,2026年11月3日:监控器的WAF洪泛摘要模板,当天上午先改写为
N WAF events in 5 minutes: B blocked, C challenged, U through(N为非机器人计数),在本文审阅之后又改写为针对每个抽样事件的N WAF events in 5 minutes: B blocked, C challenged, U unmitigated (S from non-bot user agents),JavaScript质询计为质询;其自动拦截原因N WAF events from this address in 5 min;在自动拦截运行之前写在严重告警上的“pending”处置结果,摘要将其显示为在告警路径完成之前被截断的运行;拦截列表并入条件的地址集合(发现项自身的地址以及符合自动拦截条件的地址)及其位于WAF并入条件之前的位置;自动拦截去重检查此前按站点工作,因而把11月2日的地址写入列表两次,当天改为按地址检查(该版本的监控器设计笔记和11月3日的运维日志);摘要的运行健康规则(记录的运行次数、运行之间分钟数的中位数、最长间隔及其时间、仅对超过20分钟的间隔在主题中标出);以及那天早上的预览,它显示“about one every 5.6 min”和“RUN GAP 31 min at 05:56”。 ↩↩↩↩↩↩↩↩↩↩↩ -
作者数据:本地扫描器2026年11月1日的日志:10:03:04针对Apple的
spotlightknowledged、CPU占用74.9%的严重发现项,以及10:03:06的“Critical email sent”行;当天更早的两条严重告警(00:48:55,路径垫片),各自之后都有一次发送失败;以及10月31日日志中的14条严重告警,其中包括22:52:57的AI蠕虫模式和22:41的五条“Claude hook modified”发现项,其中四条之后有发送失败(钩子在10月24日被修改过,10月31日的运维日志有记录)。 ↩↩↩↩ -
作者数据:11月2日摘要之后摘要程序的状态记录:主题“Mon Nov 2: 71 blocks on 15 zones, 11 criticals emailed, RUNS SHORT 162/207,”,窗口2026-11-01T21:48:49Z至2026-11-02T15:05:17Z,162次运行;监控器日志记录发送时间为07:05:19 PST。 ↩↩
-
作者数据:由脚本对监控器2026年11月1日和2日日志进行的运行间隔分析。对11月2日摘要的窗口(1,036.5分钟,207个五分钟时段):165次运行启动,其中4次在下一次启动之前没有“run complete”;160个普通间隔,中位数5.60分钟,平均5.79,最小5.35,合计926.0分钟(185个时段);4个超过10分钟的间隔,共103.0分钟。对11月2日这个日历日(午夜至午夜,1,440分钟,288个五分钟时段):242次启动,比时段数少46,没有未完成的;239个普通间隔,中位数5.62分钟,合计1,374.7分钟(约275个时段);2个超过10分钟的间隔,共35.8分钟。拆分是基于这些数字的近似算术:运行节奏约25.2个时段(160个普通间隔横跨185.2个五分钟时段,容纳160次运行),三个睡眠间隔中约15.6个时段(93.0分钟,18.6个时段,3次运行),14:19那次运行之后10.0分钟间隔中约1.0个时段,以及3次未记录的运行,合计约45个。 ↩↩↩↩↩↩↩↩↩
-
作者数据:从代码仓库读取的监控器配置历史:10月31日22:44 PDT启用(每条严重告警、每次会话结束各发一封邮件);11月1日00:46 PDT(严重告警并入拦截邮件);11月1日13:50 PST(07:00摘要开启,逐拦截邮件关闭,边缘已处理的严重告警并入);11月1日14:00 PST(无配置变更;WAF并入条件的代码改为统计每个事件并要求样本完整)。 ↩↩↩↩↩↩↩
-
作者的运维日志(Captain’s Log),2026年10月31日:本地扫描器的发送因Cloudflare对Python默认用户代理返回错误1010而失败,用错误的密钥复现;当晚14条严重告警对应13次发送失败的计数;云端邮件“most likely were”已送达的判断。 ↩↩↩↩
-
Resend, “Event Types,” webhook文档,14:23:37Z获取:
email.sent定义的引文,以及作为独立事件的email.delivered。 ↩↩↩↩ -
Resend, “Send Email,” API参考,14:18:59Z获取:响应体是一个
id。 ↩ -
作者数据:2026年11月1日按当地小时的统计,来自同一脚本,01:00时段按PDT和PST分开:每小时邮件数(午夜时段22封,全部未经验证)、每小时被并入或抑制的告警数、13:25:57的最后一封逐拦截邮件,以及14:20:35第一条被并入的逐拦截通知。 ↩↩↩↩
-
Cloudflare, “Querying Firewall Events with GraphQL,” Analytics文档,14:19:01Z获取:查询的字段(包括
action),以及含有"log"和"block"的示例输出;该页面未列出所有action取值。 ↩↩ -
Cloudflare, “Security Events,” WAF文档,14:23:10Z获取:安全事件的定义、单个请求可产生多个事件的句子,以及抽样的句子的引文。 ↩↩↩
-
作者的本地扫描器设计笔记,“Check fixes and false positives”:“most of the CRITICALs it raised in its first live days were its own noise”、误报表(Homebrew Python、Xcode、作者的应用构建、事件监听自匹配、版本管理器垫片、针对另一个项目智能体说明的AI蠕虫模式),以及代码签名条目的引文;扫描器自行发送其严重告警邮件。 ↩↩↩↩↩
-
Cloudflare, “Cloudflare Alerts,” Notifications文档,14:19:00Z获取:两类告警的引文;Monitors为定时执行的SQL API查询,Platform Notifications为预配置的产品告警,二者都从控制台创建,所有套餐都支持邮件投递。 ↩↩
-
Ewaschuk, “Monitoring Distributed Systems,” 14:18:54Z获取:关于页面告警过于频繁的段落的引文。 ↩↩
-
Ewaschuk, “Monitoring Distributed Systems,” 14:18:54Z获取:一个团队分诊告警的案例研究,以及指向心脏监护报警管理“for an example of alert fatigue in another context”的脚注24的引文。 ↩↩
-
Rob Ewaschuk, “My Philosophy on Alerting,” Google文档,读自其HTML导出,14:18:55Z获取:文档中关于其观点奠定了SRE书那一章基础的说明、“Pages should be urgent, important, actionable, and real”,以及基于原因的告警的段落和小节标题的引文。 ↩↩↩
-
Ewaschuk, “My Philosophy on Alerting,” 14:18:55Z获取:关于每日报告的段落、其关于问责的附加说明,以及要求指定一个人每天对报告进行分诊的内容的引文。 ↩↩↩↩↩↩
-
Steven Thurgood, with Jess Frame, Anthony Lenton, Carmela Quinito, Anton Tolchanov, and Nejc Trdin, “Alerting on SLOs,” The Site Reliability Workbook(Google,O’Reilly Media)第5章,14:18:55Z获取:精确率、召回率和重置时间定义的引文。 ↩↩
-
NIST, SP 800-61 Revision 2, Computer Security Incident Handling Guide(2012年,已被取代),14:18:58Z获取:“thousands or even millions”一句的引文。 ↩
-
NIST, SP 800-61 Revision 3(2025年4月),14:18:57Z获取:其取代r2的声明,以及Adverse Event Analysis下建议R1的引文。 ↩
-
Vectra AI, “2023 State of Threat Detection”(厂商调查;2023年3月至4月,由Vectra委托,Sapio Research执行实地调查,对象为十个市场中员工超过1,000人的组织的2,000名安全分析师),14:22:49Z获取:受访者报告的每天4,484条告警和67%被忽略。 ↩↩
-
作者代码:摘要程序的说明(“One email a day … in place of an email per auto-block and per WAF-handled CRITICAL”;“The digest is also the heartbeat: it goes out on a quiet day too”;2026年早些时候监控器长达数月的失效期),以及截至11月2日,其以窗口分钟数除以五计算预期运行次数的规则。 ↩↩↩↩↩↩↩↩↩
-
Cloudflare, “Actions reference,” Ruleset Engine文档,14:19:01Z获取:
block、质询、skip和log动作,质询和log定义的引文,以及被列为非终止动作的skip和log。 ↩↩↩↩ -
作者数据:按UTC日期,来自监控器发现项文件的、按记录的处置结果和类别划分的严重发现项。11月1日:129条,其中118条没有处置结果:117条产生于处置结果开始记录之前(其中包括本地扫描器的进程、钩子和供应链类别,它们从不带处置结果),另一条是变更之后14:13 PST的云端扫描器严重告警,其运行记录的最后一行是它触发的自动拦截(监控器11月1日的日志)。11月2日:236条(因地址被拦截而并入160,因WAF已处理而并入1,被抑制58,已发邮件14,无处置结果的本地3)。 ↩↩
-
作者数据:监控器2026年11月1日的日志:三次运行(启动于14:09:49、14:19:46和23:10:31),其最后一行落在启动后约四分钟,没有记录看门狗、超时或终止的行,作者将其解读为监控器的240秒看门狗(代码会在240秒时终止运行);以及一次在14:36:06启动、于14:36:34结束且没有“run complete”的运行。 ↩↩
-
作者的回放,2026年11月3日:在11月2日18:58:23至19:03:23 UTC(10:58:23至11:03:23 PST)的完整Cloudflare数据上运行监控器自身的表单洪泛检查,它以high严重性级别(监控器把这一级别发往摘要,而不是邮件或拦截列表)生成了实时运行漏掉的发现项,依据是39次提交、其中23次被拒绝,并统计到来自该地址的2,143个请求,而实时运行统计的是1,914个;当天从监控器代码中读出的两个原因(每次运行的窗口截止于检测器发出查询的那一刻,早于Cloudflare最新数据完全到达;以及在相隔超过五分钟启动的运行上做五分钟回看,使每个间隔都有一部分未被扫描),以及当天所做的修复(实时窗口从上一个窗口结束时间的记录开始衔接,并提前90秒结束;记录缺失、不足60秒或超过15分钟时,退回五分钟回看;回放从不读取该记录);用修改后的代码回放同一窗口得到完全相同的发现项,这表明改动没有影响固定窗口上的检测,但并不检验窗口衔接或滞后;扫描器向联系表单的提交,以作者11月2日运维日志的记录为准。通过11月3日代码进行的回放已与本文的其他证据一同保存:其确切命令、读取的窗口,以及检测器针对运行窗口和18:58至19:05 UTC表单分钟的完整输出,运行于11月3日15:55:04 UTC。11月3日更早时候通过修复前代码进行的第一次回放,以相同的计数记录在我的程序笔记中;其原始输出没有保存。 ↩↩↩↩↩
-
作者数据:本地扫描器2026年11月2日的日志以及11月3日的摘要预览:13:50 PST的三条严重“Browser -> shell chain”发现项,一个shell启动了
curl,在发现项文件中未解决。 ↩↩ -
作者数据:发现项文件中记录在11月1日
spotlightknowledged发现项上的结论的引文。 ↩ -
Cloudflare, “Error 1010,” 支持文档,14:19:00Z获取:该错误的定义和Python urllib示例的引文;该页面针对发往
*.pages.dev和*.workers.dev主机名以及Pages自定义域名的请求给出这个示例。 ↩ -
作者的运维日志(Captain’s Log),2026年11月2日:RUNS SHORT那一行的引文。 ↩
-
作者数据:Mac的电源日志(自11月1日13:40 PST起的Sleep和Wake行):23:21:30空闲睡眠、00:30:05完全唤醒;06:01:20空闲睡眠、06:30:28完全唤醒。 ↩