알림 피로: 보안 모니터가 하루에 보낸 이메일 82통
혼자 운영하는 보안 모니터는 알림을 얼마나 보내야 할까요? 제 수치로는 하루에 이메일 약 한 통, 그리고 무언가 조치를 요구하는 드문 한 통입니다. 11월 1일, 제 모니터는 태평양 시간 자정부터 13:32까지 74통의 이메일을 메일 서비스에 넘겼습니다. 이미 실행한 차단에 관한 것이 55통, 크리티컬 알림이 16통, 시간별 볼륨 알림이 2통, 그리고 제 Mac의 스캐너에서 온 것이 1통이었습니다.12 그날 오후 제 판단으로는 모두 엣지가 이미 막아 낸 일상적인 스캔을 알리는 것이었습니다.3 크리티컬 플러드에 대해서는 6절에서 보이듯 이 판단이 너무 후했습니다. 해법은 임계값이 아니라 정책이었습니다. 하트비트를 겸하는 하루 한 통의 다이제스트, 차단 알림은 메일이 아니라 채팅으로, 엣지가 이미 처리한 크리티컬은 다이제스트로 합치기, 그리고 클라우드 쪽의 각 크리티컬에 처리 결과(outcome)를 기록하기입니다. 다음 날 한 스캐너가 6분도 안 되는 사이에 제 사이트들에서 크리티컬 4건을 일으켰지만, 모니터는 그에 관해 이메일을 한 통도 보내지 않았습니다.4 그래도 그날 이메일은 한 통이 아니라 19통이었고, 그 이유가 이 이야기에서 가장 쓸모 있는 부분입니다.5
제 모니터의 메일을 읽는 사람은 저 하나뿐입니다. 받은편지함에 74통이 쌓였을 때 저는 이것이 일상적인 것인지 실제 위협인지 물었고, 정직한 답은 제목만으로는 모니터도 알려 줄 수 없다는 것이었습니다. 모든 제목이 음량만 달리해서 같은 말을 하고 있었기 때문입니다.1 유일한 독자가 읽기를 멈춘 모니터는, 탐지기가 여전히 무엇을 잡아내든 이미 감시를 멈춘 것입니다. 이어지는 내용은 그 건수, 그것을 대체한 정책, 하나의 테스트 사례, 그리고 깔끔한 버전의 이야기가 무너지는 세 지점입니다.
TL;DR
- 13:32까지 74통, 자정까지 82통. 제가 불만을 제기하기 전 시점에 대해 제가 직접 만든 표와 로그에서 도출한 건수는 74통으로 일치합니다(차단별 55, 크리티컬 16, 시간별 2, 로컬 1). 11월 1일 전체로는 로그에 기록된 81통에 제가 수동으로 보낸 다이제스트 1통이 더해집니다.125
- 74통 중 22통은 입증되지 않습니다. 이들은 메일 서비스의 응답을 확인하지 않고 “sent”를 기록하는 코드에서 나왔기 때문에, 로그가 입증하는 것은 전달이 시도되었다는 사실이지 메시지가 도착했다는 사실이 아닙니다.26
- 규칙은 이미 문헌에 있었습니다. Google의 SRE 책(Site Reliability Engineering)은 출력을 세 가지(알림, 티켓, 로깅)만 인정하고 “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초 동안 제 사이트들에서 크리티컬 4건을 일으켰습니다. 모니터는 그 주소를 한 번 차단하고, 차단을 채팅에 올리고, 모든 크리티컬을 다이제스트로 합쳤으며, 이메일은 보내지 않았습니다.41112
- 다음 날에도 한 통이 아니라 19통이 갔습니다. 14통은 일부 이벤트에 차단도 챌린지도 기록되지 않은 크리티컬 WAF 플러드 알림이었고, 그 제목은 엣지가 이벤트를 하나도 차단하지 않았을 때조차 “WAF blocks”라고 적었습니다(그날 두 번. 전날 저녁에는 엣지가 아무것도 차단하거나 챌린지하지 않았는데 “104 WAF blocks”라고 적힌 것이 하나 있었습니다). 건수를 센 바로 그날 한 첫 번째 문구 수정도 여전히 두 가지 건수를 한 줄에 섞고 있었고, 이 글의 검토가 그것을 잡아냈습니다.51314
- 두 신호는 틀렸습니다. 제 Mac의 코드 서명 검사는 Apple의 Spotlight 데몬을 서명되지 않은 암호화폐 채굴기라고 불렀고, 다이제스트의 “RUNS SHORT 162/207”(실행 부족 162/207)은 일부는 절전, 일부는 실제로 더 느린 실행 주기, 그리고 타이밍으로 보아 일부는 워치독의 강제 종료 3회였습니다.151617
- 바뀐 것: 하루 한 통의 다이제스트가 하트비트, 모든 차단, 클라우드 쪽 모든 크리티컬의 처리 결과를 담고, 즉시 이메일을 보내는 것은 어느 합치기 조건에도 해당하지 않는 크리티컬, 시간별 볼륨 알림, 로컬 스캐너뿐입니다. 11월 1일의 82통은 11월 2일에 19통이 되었습니다.185
1. 11월 1일의 받은편지함
11월 1일 태평양 시간 13:32, 저는 제 인프라를 돌리는 에이전트 세션에 이렇게 썼습니다. 모니터의 이메일이 받은편지함을 스팸처럼 채우고 있는데 바꿀 것이 있는지, 이것들이 일상적인 것인지 실제 위협인지.1 그날 오후에 만든 표는 자정 이후의 이메일 74통을 발신원별로 셌습니다. 차단 1건당 한 통씩인 알림 55통, 크리티컬 알림 16통, 시간별 볼륨 알림 2통, 그리고 제 Mac의 스캐너에서 온 크리티컬 1통입니다.1
건수는 하나의 규칙에 기대고 있으므로 숫자보다 규칙을 먼저 밝힙니다. 이 글에서 이메일이란 메일 서비스로의 전달을 기록한 모니터 로그의 한 줄입니다. 스크립트가 10월 31일부터 11월 2일까지의 로그 파일 6개를 훑어 그런 줄을 모두 스트림별, 결과별로 분류했습니다.5 13:32까지로 자르면 스크립트는 스트림마다 제 표와 일치합니다. 55, 16, 2, 1, 합계 74입니다.2 현지 시간 하루 전체, 자정까지는 로그에 기록된 이메일이 81통(차단별 55, 크리티컬 22, 시간별 3, 로컬 1)이었고, 여기에 제가 13:48에 수동으로 보낸 다이제스트 1통이 더해집니다. 이 다이제스트는 모니터 로그에 발송 줄이 없으며, 합계는 82통입니다.51
일치 속에 중요한 세부가 두 가지 있습니다. 제 표는 크리티컬에 대해 “9 before the fold went live at 00:30 PT; the 7 after”(합치기가 00:30 PT에 적용되기 전 9통, 이후 7통)라고 적었습니다. 그런데 로그에는 00:30 이전의 크리티컬 “sent” 줄이 11개, 이후가 5개이고, 차단 알림에 합쳐진 첫 크리티컬은 00:54:43에 나타납니다. 합치기 변경이 반영된 것은 제 표가 적은 00:30이 아니라 00:46 PDT였습니다.218 합계는 맞지만 제가 손으로 적은 내역은 2만큼 어긋났습니다. 두 번째 세부는 더 큽니다. 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 그 뒤로 차단별 알림은 시간당 1~5통씩 이어졌습니다. 엣지가 스캐너를 계속 차단했고, 차단할 때마다 제게 편지를 한 통씩 썼기 때문입니다. 마지막 차단별 이메일은 13:25:57에 나갔고, 대신 다이제스트로 합쳐진 첫 차단별 알림은 14:20:35에 나타납니다.22 13:50 변경 이후 이메일은 시간당 0~2통으로 떨어지고, 합쳐지거나 억제된 알림은 시간당 최대 23건까지 올라갑니다.22 트래픽은 줄지 않았습니다. 줄어든 것은 이메일입니다.
전날은 기준선으로 적절하지 않습니다. 11월 1일의 제 로그에는 “Yesterday had 10”(어제는 10통)이라고 적혀 있고 스크립트도 같은 수를 내지만, 10월 31일의 로그는 제가 모니터를 다시 가동한 저녁 22:31부터 시작하므로 그 10통은 89분만을 다룹니다.15 제 판단으로는, 74라는 숫자는 스캐너들이 하는 일이 바뀐 것이 아니라 인터넷의 배경 소음을 온종일 받으며 탐지기가 작동한 결과를 잰 것입니다.
2. 알림은 무엇을 위한 것이었나
모니터는 두 부분으로 이루어져 있습니다. 클라우드 쪽은 제 Mac에서 5분마다 실행되도록 예약되어 있고, 제 각 영역(zone)에 대해 Cloudflare에 방화벽 이벤트와 그룹화된 요청 건수를 묻습니다. 어떤 주소가 어떤 경로를 얼마나 자주 요청했는지, 그리고 엣지가 각 이벤트에 어떤 조치를 취했는지입니다.1823 이 데이터를 보는 틀로는 Cloudflare 자신의 설명이 적절합니다. “Security Events displays information about requests actioned or flagged by Cloudflare security products, including features such as Browser Integrity Check.”(Security Events는 Browser Integrity Check 같은 기능을 포함해 Cloudflare 보안 제품이 조치를 취하거나 표시한 요청에 관한 정보를 보여 준다)24 로컬 쪽은 Mac 자체를 10분마다 단계별로 점검하는 스캐너로, 프로세스와 그 코드 서명, 머신의 에이전트 훅, 경로상에서 시스템 바이너리를 가리는 도구 같은 것들을 살핍니다.12
클라우드 쪽이 그 데이터로 하는 일은 간단히 말할 수 있습니다. 심각도(critical, high, medium)와 범주별로 탐지 결과를 올리고, 크리티컬이라면 누구에게 알리기 전에 먼저 조치하려 합니다. 한 주소가 이벤트의 충분한 몫을 차지하면, 모든 영역이 엣지에서 적용하는 계정 전체 차단 목록에 그 주소를 7일 동안 추가합니다.124 11월 1일의 변경 전까지는 그런 조치 하나하나가 이메일도 만들어 냈고, 사이트와 범주별 2시간 제한이 억제하지 않은 모든 크리티컬, 모든 세션 종료, 차단이 많았던 모든 시간대도 마찬가지였습니다.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 책의 실천편)의 알림 장은 무엇이 잘못되었는지 말할 어휘를 줍니다. 정밀도(precision)는 “The proportion of events detected that were significant.”(탐지된 이벤트 중 중요했던 것의 비율), 재현율(recall)은 “The proportion of significant events detected.”(중요한 이벤트 중 탐지된 것의 비율)입니다.31 저는 제 모니터의 재현율을 재지 않았습니다. 그러려면 놓친 중요한 이벤트의 수가 필요한데, 5절은 그렇게 놓친 하나를 설명합니다. 메일 스트림으로서의 정밀도는 거의 0이었습니다. 이메일 74통 중, 제 판단으로는 제게 무언가를 하라고 요구한 메시지가 하나도 없었습니다. 같은 장은 재설정 시간(reset time)에도 이름을 붙입니다. “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 2013년 4월 8일 발행된 The Joint Commission의 Sentinel Event Alert(중대 사건 경보)는 의료기기 알람 안전에 관해 여기에 숫자를 붙입니다. “The number of alarm signals per patient per day can reach several hundred”(환자 1명당 하루 알람 신호 수는 수백 건에 이를 수 있다) 그리고 “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, 그때와 지금. 2012년판 NIST 사고 대응 지침 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 저는 그 필터를 지침으로 받아들이고, 다이제스트를 그 하나로 봅니다.
벤더 설문 하나, 벤더 설문이라고 밝혀서. Sapio Research가 보안 분석가 2,000명을 대상으로 수행한 Vectra AI의 2023년 설문은 “SOC teams receive an average of 4,484 alerts per day, and over two-thirds (67%) of them are ignored.”(SOC 팀은 하루 평균 4,484건의 알림을 받으며, 그중 3분의 2 이상(67%)이 무시된다)라고 보고합니다.34 이는 벤더가 의뢰한 의견 조사이며, 67%는 응답자들이 그렇다고 말한 것이지 측정값이 아닙니다. 제가 이것을 인용하는 이유는 단 하나입니다. 이 실패는 규모가 작아져도 일어난다는 점입니다. 분석가들은 하루 4,484건의 알림 중 3분의 2를 무시한다고 말하고, 팀이 없는 한 명의 독자는 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: 처리 결과는 탐지 결과에 기록한다. 정책상 클라우드 쪽의 모든 크리티컬은 기록된 처리 결과를 하나씩 갖습니다. 주소가 차단 목록에 있어서 합침, 엣지가 샘플의 모든 이벤트를 차단하거나 챌린지해서 합침, 이메일 발송, 억제(사이트와 범주별 2시간에 한 통이라는 제한 안쪽), 이메일 실패 중 하나입니다.337 기록은 정책에 미치지 못합니다. UTC 기준 11월 1일자 크리티컬 중 118건에는 처리 결과가 없습니다. 117건은 그 필드가 생기기 전에 올라온 것이고, 1건의 클라우드 크리티컬은 14:13 PST로 그 이후에 왔습니다. 그 실행은 7절에서 다루는, 시작 후 약 4분 만에 끝난 세 번의 실행 중 하나였고, 자동 차단 도중 처리 결과가 기록되기 전에 멈췄습니다. 로컬 스캐너의 크리티컬은 처리 결과를 갖지 않으며, 11월 2일의 3건에도 없습니다.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”(시간별 볼륨 알림(대응기): 여전히 즉시, 최대 시간당 1회)3 차단 한 건은 일상입니다. 한 시간 안에 차단이 갑자기 쌓이는 것은 클라우드 쪽이 가진 것 중 증상에 가장 가까운 것, 즉 인터넷이 제게 하는 일의 변화입니다. 11월 2일에 한 번 울렸습니다.5
규칙 6: 로컬 스캐너의 크리티컬은 여전히 즉시 이메일로 보낸다. 설계 메모는 그것들이 드물어야 한다는 이유로 즉시 발송을 유지합니다.3 저는 두 번째 이유로 유지합니다. 엣지가 제 사이트와 스캐너 사이에 서 있는 것처럼 제 Mac과 로컬 탐지 결과 사이에 서 있는 것은 아무것도 없습니다. 차단 목록은 주소를 막을 수 있지만, Mac 바깥의 어떤 것도 Mac 안의 프로세스를 막지 못합니다. 이 규칙의 대가는 7절에서 드러납니다.
규칙이 적용된 날, 메일 스트림은 13:32 이전의 74통에서 그날 나머지 시간 동안 로그에 기록된 7통(크리티컬 6통과 시간별 알림 1통)으로 줄었고, 여기에 제가 수동으로 보낸 다이제스트가 더해집니다.52 그날 오후 제가 쓴 예측은 “Under today’s traffic that would have been 0 immediate emails instead of 16.”(오늘의 트래픽이라면 즉시 이메일은 16통이 아니라 0통이었을 것이다)였습니다.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. 요청 플러드 탐지기가 그 주소에 대해 크리티컬을 올립니다. 8초 뒤인 11:04:01에 대응기가 주소를 차단 목록에 추가하고, 11:04:02에 차단 알림이 채팅으로 가며 그 이메일은 다이제스트로 합쳐집니다.4
- 11:04:03. 크리티컬 자체가 합쳐집니다. 주소가 이제 차단 원장(block ledger)에 있으므로, 이메일이 제게 요청했을 일을 엣지가 이미 하고 있기 때문입니다.4
- 11:04:04. 두 번째 탐지기인 스캐너 검사가 같은 주소에 대해 자체 크리티컬을 올립니다. 대응기의 차단 요청은 중복으로 건너뛰어지고, 크리티컬은 1초 뒤에 합쳐집니다.4
- 11:09:22. WAF 플러드 탐지기가 그 영역에 대해 세 번째 크리티컬을 올립니다. 문제의 주소가 원장에 있으므로 합쳐집니다.411 이를 결정한 것은 차단 목록 합치기뿐입니다. 샘플된 이벤트 중 8건에는 차단도 챌린지도 기록되지 않았고 샘플은 상한에 걸렸으므로, WAF 합치기만으로는 이메일이 나갔을 것입니다.11
- 11:09:44. 지속 스캐너 검사는 한 주소에서 온, 엣지가 차단하거나 챌린지한 이벤트를 제 모든 영역에 걸쳐 세는데, 이것이 네 번째를 올립니다. 기록된 곳은 제 다른 사이트이며, 그날 그 주소에서 차단되거나 챌린지된 이벤트는 743건이었습니다.12 대응기는 주소를 목록에 한 번 더 기록하고(결함입니다. 아래에서 다룹니다), 채팅에 올리고, 11:09:47에 크리티컬을 합칩니다.411
- 11:14:58과 11:20:40. 같은 영역에 대한 WAF 플러드 크리티컬이 두 건 더 나오고, 둘 다 같은 방식으로 합쳐집니다.11
5분 51초 동안 크리티컬 4건, 11:20까지 6건이며, 모두 주소가 차단되었기 때문에 합쳐졌다고 탐지 결과에 기록되어 있습니다.11 차단된 주소는 하나이고, 당시 중복 검사가 사이트 단위로 작동했기 때문에 목록 항목이 두 번 기록되었으며(11월 3일 수정), 추가 차단 요청 4건이 중복으로 건너뛰어졌습니다.414 이메일은 0통입니다. 로그에는 11:03부터 11:21 사이에 발송된 이메일 기록이 없습니다. 그 구간에서 합쳐지지도 이메일로 나가지도 않은 유일한 크리티컬은 11:03:13에 억제된 것으로, 다른 주소에서 온 다른 사이트의 플러드였습니다.4
다음 다이제스트는 이를 자동 차단 표의 두 줄(영역, 사유, 네트워크 운영자가 붙은 첫 차단과 지속 스캐너에 의한 재추가)과 모든 크리티컬 표의 여섯 줄로 실었고, 각각에 “blocked”(차단됨) 또는 “blocked earlier”(이전에 차단됨) 표시가 붙어 있었습니다.12 제 판단으로는 첫 탐지 8초 뒤, 스캔 시작 8분 뒤에 모니터가 차단한 스캐너에는 그것이 알맞은 무게입니다. 커피를 마시며 읽을 수 있는 몇 줄이지, 페이지가 아닙니다.4
이 테스트 사례가 보여 주지 않는 것이 하나 있으며, 11월 3일에 발견되었습니다. 같은 스캐너가 제 사이트의 문의 양식에도 글을 올리고 있었고, 제 앱은 그 제출들에 200으로 응답했고, 그 자체로 생긴 이메일은 많아야 31통이며, 자매 글의 주제입니다. 그런데 모니터의 양식 플러드 검사는 실시간으로는 아무 탐지 결과도 올리지 않았습니다. 11:03:23까지의 5분을 완전한 데이터로 재실행하면 같은 검사가 심각도 high(이메일이나 차단이 아니라 다이제스트의 한 줄)로 탐지 결과를 올리며, 근거는 제출 39건이고 그중 23건이 거부되었습니다.39 실시간 실행은 Cloudflare에 묻는 바로 그 순간에 끝나는 구간을 요청했습니다. 실행 시작 37초 뒤, 최신 이벤트가 다 도착하기 전입니다. 그 구간에서 해당 주소의 요청을 1,914건으로 셌지만, 완전한 데이터로 재실행하면 2,143건입니다.439 이 지연은 그 주소의 전체 요청에 대해 잰 것이지, 검사가 세는 양식 제출에 대해 잰 것이 아닙니다. 제 판단으로는 같은 지연이 실시간에서 그 제출들을 검사 기준 아래에 머물게 했습니다. 게다가 각 실행은 정확히 5분을 거슬러 보았는데 실행 시작 간격은 약 5.6~5.8분이었으므로, 모든 간격의 일부가 한 번도 검사되지 않았습니다.3917 수정은 같은 날 들어갔습니다. 각 구간은 이제 이전 구간이 끝난 곳에서 시작해 실행 90초 전에 끝납니다. 다만 이전 기록이 없거나, 1분이 안 되었거나, Mac이 절전한 뒤처럼 15분보다 오래되었으면 단순한 5분 되돌아보기로 돌아갑니다.39 11월 2일 구간을 바뀐 코드로 재실행하면 같은 탐지 결과가 나오므로, 이 변경은 고정된 구간에서의 탐지를 흐트러뜨리지 않았습니다. 재실행으로는 구간 이어 붙이기(타일링)나 지연을 시험할 수 없습니다. 그것들은 실시간 실행에서만 작용하기 때문입니다. 11월 3일 이후의 실시간 실행이 그 시험입니다.39
6. 여전히 이메일로 가는 것과 그 이유
11월 2일은 새 규칙 아래의 첫 온전한 하루였고, 발송된 이메일은 한 통이 아니라 19통이었습니다. 크리티컬 14통, 시간별 볼륨 알림 1통, 다이제스트 1통, 로컬 스캐너에서 3통입니다.5 차단별 이메일은 한 통도 나가지 않았습니다. 대신 차단 알림 108건이 다이제스트로 합쳐졌고, 크리티컬 177건이 합쳐졌으며, 37건이 2시간 제한으로 억제되었으며, 모두 현지 날짜 기준으로 센 것입니다. 이 글 뒤쪽의 처리 결과 건수는 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분 동안 WAF 차단 N건)라고 적었지만, 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,”(즉시 이메일은 16통이 아니라 0통이었을 것)이라고 했는데, 그 근거는 그날 아침 00:30 이후의 크리티컬 7건에 대해 “Cloudflare had already blocked every one of those requests.”(Cloudflare가 그 요청들을 이미 전부 차단했다)라는 믿음이었습니다.1 탐지 결과는 그것들에 대해서도 다르게 말합니다. 로그상 00:30 이후 이메일로 나간 5건은 각각 요약에 있는 76~89건의 이벤트에 대해 기록된 차단이 최대 1건, 챌린지가 최대 9건이었습니다.13 그리고 그날 오후와 저녁에 이메일로 나간 크리티컬 6건 각각에서 엣지가 차단하거나 챌린지한 것은 이벤트의 일부뿐이었으므로(16:51 급증에서는 전혀 없음), 봇이 아닌 모든 이벤트를 차단과 챌린지가 덮을 때만 합치던 13:50 규칙조차 모두 이메일로 보냈을 것입니다.131 13:50 변경 10분 뒤, 두 번째 변경으로 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일의 로컬 이메일 3통은 또 하나의 미결 사항입니다. 그것들은 13:50에 셸이 curl을 실행한 “Browser -> shell chain”(브라우저에서 셸로 이어지는 연쇄)을 보고하며, 탐지 결과 안에서 미해결로 남아 있습니다.40 그것이 그날 오후 제가 돌리던 에이전트 도구였는지 다른 무엇이었는지는 확인하지 않았으므로, 오경보로 세지 않습니다.
7. 두 가지 잘못된 신호
Spotlight였던 암호화폐 채굴기
11월 1일 10:03, 로컬 스캐너는 Apple 자체의 Spotlight 데몬인 spotlightknowledged에 대해 CPU 74.9%로 크리티컬 “High CPU unsigned process - possible crypto miner”(높은 CPU의 서명되지 않은 프로세스: 암호화폐 채굴기 가능성)를 올렸고, 2초 뒤 “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 웜 패턴, 그리고 자기 자신의 쿼리에 일치한 이벤트 탭 검사를 표시했습니다. 그것들은 발송 시도 15회를 만들었고, 그 모두가 실패했습니다.5251915 메일 서비스는 Cloudflare 뒤에 있었고, Cloudflare는 Python의 기본 사용자 에이전트에 대해 키를 읽기도 전에 오류 1010으로 응답했습니다. 저는 일부러 잘못된 키로 이를 재현했습니다.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 다이제스트의 규칙은 단순한 산수였습니다. 예상 실행 횟수는 구간의 분을 5로 나눈 값이고, 제목은 부족분을 표시합니다.35 구간은 11월 1일 13:48부터 11월 2일 07:05까지, 1,036.5분이므로 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개 더, 그리고 스스로를 기록하지 않은 실행 3회입니다.17
| 원인 | 슬롯(대략) | 근거 |
|---|---|---|
| 실제 실행 주기 | 25 | 실행 사이의 보통 간격 160개, 중앙값 5.60분, 5.35분 미만은 없음. 합계 926.0분으로 5분 슬롯 185개 분량 |
| 절전 | 16 | 10분을 넘는 간격 3개, 합계 93.0분. 전원 로그의 유휴 절전 구간 안에 있으며, 짧은 유지 관리용 깨어남 동안 실행이 재개됨 |
| 조기 종료 뒤의 긴 간격 | 1 | 10분을 넘는 네 번째 간격. 일찍 끝난 14:19 실행 뒤의 10.0분 |
| 스스로를 기록하지 않은 실행 | 3 | 기록된 실행 162회 대비 실행 시작 165회 |
| 그 3회의 배경 | 실행 3회가 시작 약 4분 뒤 종료 줄 없이 끝남. 타이밍으로 보아 240초 워치독이며, 모두 11월 1일. 오후에 두 번, 그날 밤늦게 한 번 |
전원 로그는 23:21:30에 유휴 절전, 00:30:05에 완전히 깨어남, 그리고 다시 06:01:20에 유휴 절전, 06:30:28에 완전히 깨어남을 보여 줍니다.44 세 실행은 11월 1일 14:09, 14:19, 23:10에 시작했고, 각각 약 4분 뒤 종료 줄 없이 끝났습니다. 제 판단으로는 이것이 240초 워치독입니다. 네 번째 실행은 시작 28초 뒤에 끝났으며 눈에 보이는 원인이 없습니다.1738 11월 2일 하루(달력 기준)에는 5분 슬롯 288개에 대해 실행 242회가 시작되어 46이 부족했고, 끝나지 않은 채 남은 실행은 없었습니다. 10분을 넘은 간격은 두 개뿐으로 합계 35.8분이었으므로, 그날도 부족분의 대부분은 절전이 아니라 실행 주기였습니다.17
잘못된 가정은 제 것이었고, 다이제스트 안에 있었습니다. 5분으로 설정된 스케줄러는 Mac이 깨어 있을 때조차 약 5.6~5.8분마다 한 번 실행을 내주었으므로, 5분 산수는 건강한 날도 부족하다고 판정하게 됩니다.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
- 제목이 엣지가 한 일을 말하게 하라. 차단이 0건인 급증에 붙은 “104 WAF blocks”는 그 주에 제가 쓴 최악의 한 줄이었습니다.13
- 일정 대비 비율이 아니라 간격을 보고하라. 5분 타이머가 약 5.6분마다 실행을 내주었으므로, 162/207은 장애처럼 보였지만 대부분은 시계 문제였습니다.17
- 오경보는 받은편지함이 아니라 그 검사에서 고쳐라. 명령 출력 하나를 잘못 읽은 탓에 Apple의 Spotlight 데몬이 암호화폐 채굴기가 되었고, 수정은 검사에 들어갔습니다.25
무엇이 바뀔 수 있는가
- 미처리 이벤트가 해결된 챌린지로 밝혀진다면, 그것만으로 여전히 이메일로 가는 WAF 플러드 크리티컬이 소음이 되지는 않습니다. 클라이언트가 챌린지를 통과하면 “Cloudflare accepts the matched request”(Cloudflare는 일치한 요청을 받아들인다), 즉 요청은 계속 진행되며, 그것이 요청이 무해했다거나 대응이 필요 없다는 것을 보여 주지는 않습니다. 그런 크리티컬을 합치려면 같은 이벤트에 연결된 요청 증거로 그 안에 사람이 필요한 것이 없었음을 보여야 하며, 챌린지 완료만으로는 그런 증거가 되지 못합니다. 그것들이
log나skip규칙으로 밝혀진다면, 문제는 그 규칙들이 제 의도대로 작동하는지이고, 그것을 알 때까지 각 이메일은 읽을 가치가 있습니다. 모니터는 아직 어느 쪽인지 기록하지 않습니다.1336 - 하루의 크리티컬 이메일이 다시 수십 통으로 늘어난다면, 사이트와 범주별 2시간 제한이 규칙이 해야 할 일을 대신하고 있는 것이고, 규칙을 다시 살펴봐야 합니다.3
- 다이제스트가 오지 않게 된다면, 모니터와 제 받은편지함 사이의 무언가가 멈춘 것입니다. 모니터, 그것이 도는 Mac, 발신 코드, 또는 전달 경로입니다. 받아들여진 발송이 아직 전달된 발송은 아니기 때문입니다. 이것이 이 설계가 살펴보라는 신호로 취급하는 유일한 침묵입니다.3520
- 로컬 스캐너가 제 도구로 설명할 수 없는 크리티컬을 올린다면, 즉시 이메일은 옳은 판단이었던 것입니다. 11월 2일의 미해결 셸 연쇄 탐지 결과 3건이 그에 대한 열린 시험입니다.40
- 독자를 한 명 더 둔다면, 페이지와 티켓은 더 이상 같은 사람이 아니게 되고, SRE 책의 알림과 티켓 구분에는 제 받은편지함이 아니라 티켓 대기열이 필요해질 것입니다.7
핵심 정리
자신을 위해 모니터를 운영한다면
- 저는 반나절 동안 74통에 이르기 전 어딘가에서 읽기를 멈췄습니다. 74통은 제 받은편지함이 이른 오후에 도달한 건수입니다.1
- 조용한 날에도 도착하는 일일 다이제스트는 공짜 하트비트입니다. 이벤트가 있을 때만 말하는 모니터는 자신이 죽었다는 것을 알려 줄 수 없습니다.35
- 로그는 한 번도 나가지 않은 메일에 대해 “sent”라고 말할 수 있습니다. 건수를 믿기 전에 코드를 확인하세요.619
모니터를 만든다면
- 먼저 조치하고, 그다음 알릴지 결정하세요. 차단한 뒤, 차단 목록이나 엣지가 이미 처리하고 있다면 알림을 합치세요.3
- 각 크리티컬의 처리 결과를 탐지 결과에 기록하고 다이제스트에 출력하세요. 그래야 Ewaschuk가 일일 보고서를 분류해야 한다고 말하는 사람에게 확인할 거리가 생깁니다. 제 모니터에서 그 사람은 저입니다.3012
- 이벤트에는 엣지가 그것에 대해 기록한 것(차단, 챌린지, 미처리)으로 이름을 붙이고, 한 줄에는 한 모집단만 세며, 엣지가 해 주기를 바랐던 것으로 이름을 붙이지 마세요.1314
- 가지고 있는 스케줄러를 측정하세요. 제 것은 5분 타이머로 5.6분마다 실행했습니다.17
받은편지함을 읽는 사람이 당신이라면
- 모든 알림에 대해 그것이 “urgent, important, actionable, and real”(긴급하고, 중요하고, 조치 가능하고, 진짜)인지 물으세요. 그렇지 않은 것은 다이제스트나 로그에 들어가야 합니다.29
- 제 첫날 예측은 빗나갔습니다. 즉시 크리티컬은 0통이라고 했지만 다음 날 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통이었고, 그중 WAF 플러드 크리티컬 14통은 확정된 비용이 아니라 열린 질문으로 다룹니다.513
보안 알림은 일일 다이제스트여야 합니까, 실시간이어야 합니까?
둘 다이며, 사람이 무엇을 해야 하는지로 나눕니다. 모니터가 이미 처리되었다고 보는 것(차단 목록에 올린 주소, 또는 샘플된 모든 이벤트가 차단되거나 챌린지된 플러드)은 다이제스트로 가고, 나머지는 2시간 제한 안에서 즉시 나갑니다.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의 7일 만료 차단 목록 기록, 중복으로 건너뛴 차단 요청 4건, 채팅 게시, 그리고 이 구간에 11:03:13 다른 사이트의 억제된 플러드를 제외하면 이메일 줄이 없음. 첫 크리티컬을 올린 실행은 11:02:46에 시작했고 그 탐지기 구간은 37초 뒤인 11:03:23에 끝났으며, 그 차단 사유는 그 구간에서 해당 주소의 요청을 1,914건으로 셈. 스캔의 첫 요청은 차단 8분 전인 10:56 PT에 사이트에 도달함(저자의 11월 2일 운영 일지와 해당 주소에 대한 Cloudflare의 분당 요청 수). ↩↩↩↩↩↩↩↩↩↩↩↩↩
-
저자의 데이터: 2026년 10월 31일부터 11월 2일까지 모니터의 로그 6개(클라우드 쪽과 로컬 스캐너)에서 스크립트로 도출한 이메일 건수로, 메일 서비스로의 전달을 기록한 한 줄을 한 통으로 셈. 현지 날짜별: 10월 31일(로그는 22:31 시작) 10통(차단별 4, 크리티컬 6, 모두 무조건 “sent”를 기록한 발신 코드에서. 2시간 제한으로 억제된 크리티컬 3, 실패한 로컬 발송 13). 11월 1일 로그 기록 81통(차단별 55, 크리티컬 22, 시간별 볼륨 3, 로컬 1. 실패한 로컬 발송 2)에 수동으로 보낸 다이제스트 1통. 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으로 응답): 환자 1명당 하루 알람 신호 수, 85~99% 추정치(경보는 이를 AAMI에 귀속하며, 오경보를 분리하지 않고 임상적 개입이 필요 없는 알람을 다룸), 둔감화와 음량을 줄이는 것에 관한 구절, 2009년 1월부터 2012년 6월까지의 알람 관련 사건 98건과 그중 사망에 이른 80건, 그리고 “the most common contributing factor”는 Sentinel Event 데이터베이스에 보고된 요인 외의 요인을 다룬 경보의 목록에서 인용한 것으로, 데이터베이스 자체의 보고 요인 목록과는 별개. ↩↩↩↩↩
-
저자의 데이터: 모니터의 탐지 결과 파일에 있는 11월 2일 스캐너에 대한 크리티컬 탐지 결과 6건(11:03:53 요청 플러드, 11:04:04 스캐너, 11:09:22 WAF 플러드, 11:09:44 지속 스캐너, 11:14:58과 11:20:40 WAF 플러드). 모두 주소가 차단 목록에 있어서 합쳐졌다고 기록됨. 11:09:22 WAF 플러드에는 차단도 챌린지도 기록되지 않은 샘플 이벤트 8건과, 모니터의 샘플 한도와 같은 방화벽 이벤트 수도 기록되어 있어 샘플이 상한에 걸렸음을 보여 줌. ↩↩↩↩↩↩
-
저자의 데이터: 2026년 11월 3일 다이제스트 프로그램의 미리보기 모드가 렌더링한 다음 다이제스트(구간은 11월 2일 월요일 07:05부터 11월 3일 화요일 06:17 PST까지. 미리보기는 아무것도 발송하지 않음): 그 섹션 구성, 로컬 스캐너의 10분 단계, 모든 영역에서 7일 동안 적용되는 계정 전체 차단 목록, 네트워크 운영자가 붙은 자동 차단 표(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일의 6건은 엣지가 차단하거나 챌린지한 봇이 아닌 이벤트(탐지 결과의 acted 수)가 이벤트 수에 못 미침(83 중 5, 153 중 95, 104 중 0, 128 중 67, 135 중 54, 108 중 80). 11월 2일의 2건은 차단 0건(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 사이에 이메일로 나갔다고 로그가 보여 주는 크리티컬 5건에 대한 탐지 결과의 차단 수와 챌린지 수(이벤트 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일 로그: Apple의
spotlightknowledged에 대한 CPU 74.9%의 10:03:04 크리티컬 탐지 결과와 10:03:06의 “Critical email sent” 줄. 그날 그 이전의 크리티컬 2건(00:48:55, 경로 심)으로 각각 뒤에 발송 실패. 그리고 10월 31일 로그의 크리티컬 14건으로, 그중에는 22:52:57의 AI 웜 패턴과 22:41의 “Claude hook modified” 탐지 결과 5건이 있으며 그중 4건 뒤에 발송 실패(훅은 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분, 5분 슬롯 207개)에 대해: 실행 시작 165회, 그중 4회는 다음 시작 전까지 “run complete” 없음. 보통 간격 160개, 중앙값 5.60분, 평균 5.79, 최소 5.35, 합계 926.0분(185 슬롯). 10분을 넘는 간격 4개, 합계 103.0분. 11월 2일 달력상 하루(자정부터 자정까지, 1,440분, 5분 슬롯 288개)에 대해: 시작 242회로 슬롯보다 46 적고, 끝나지 않은 것 없음. 보통 간격 239개, 중앙값 5.62분, 합계 1,374.7분(약 275 슬롯). 10분을 넘는 간격 2개, 합계 35.8분. 분해는 이 수치에서 나온 대략적인 산수: 실행 주기로 약 25.2 슬롯(보통 간격 160개는 5분 슬롯 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일: 로컬 스캐너의 발송이 Python 기본 사용자 에이전트에 대한 Cloudflare의 오류 1010으로 실패한 것(잘못된 키로 재현), 그날 밤 크리티컬 14건 대비 발송 실패 13건이라는 건수, 클라우드 쪽 이메일이 “most likely were” 전달되었다는 판단. ↩↩↩↩
-
Resend, “Event Types,” 웹훅 문서, 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 가져옴: 알림의 두 범주 인용. 예약 실행되는 SQL API 쿼리로서의 Monitors와 미리 구성된 제품 알림으로서의 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, 10개 시장의 직원 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일 기준 구간의 분을 5로 나누는 예상 실행 규칙. ↩↩↩↩↩↩↩↩↩
-
Cloudflare, “Actions reference,” Ruleset Engine 문서, 14:19:01Z 가져옴:
block, 챌린지,skip,log조치, 챌린지와 log 정의 인용, 그리고 비종결 조치로 나열된skip과log. ↩↩↩↩ -
저자의 데이터: 모니터의 탐지 결과 파일에서 UTC 날짜별로, 기록된 처리 결과와 범주별 크리티컬 탐지 결과. 11월 1일: 129건, 그중 118건은 처리 결과 없음. 117건은 처리 결과가 기록되기 전에 올라온 것(그중에는 처리 결과를 갖지 않는 로컬 스캐너의 프로세스, 훅, 공급망 범주가 포함됨)이고, 1건은 변경 후 14:13 PST의 클라우드 스캐너 크리티컬로, 그 실행의 마지막 로그 줄은 그것이 촉발한 자동 차단(모니터의 11월 1일 로그). 11월 2일: 236건(주소가 차단되어 합침 160, WAF가 처리해서 합침 1, 억제 58, 이메일 발송 14, 처리 결과 없는 로컬 3). ↩↩
-
저자의 데이터: 모니터의 2026년 11월 1일 로그: 실행 3회(시작 14:09:49, 14:19:46, 23:10:31)로, 마지막 줄이 시작 약 4분 뒤에 있고 워치독, 시간 초과, 강제 종료 줄이 기록되지 않음. 저자는 이를 모니터의 240초 워치독으로 읽음(코드는 240초에 실행을 강제 종료함). 그리고 14:36:06에 시작해 “run complete” 없이 14:36:34에 끝나는 실행. ↩↩
-
저자의 재실행, 2026년 11월 3일: 11월 2일 18:58:23부터 19:03:23 UTC(10:58:23부터 11:03:23 PST)까지의 완전한 Cloudflare 데이터에 대한 모니터 자체의 양식 플러드 검사. 실시간 실행이 놓친 탐지 결과를 심각도 high(모니터는 이를 이메일이나 차단 목록이 아니라 다이제스트로 보냄)로 올리며, 근거는 제출 39건 중 23건 거부이고, 실시간 실행이 1,914건으로 센 해당 주소의 요청을 2,143건으로 셈. 그날 모니터 코드에서 읽어 낸 두 원인(각 실행의 구간은 탐지기가 묻는 순간에 끝나 Cloudflare의 최신 데이터가 다 도착하기 전이었고, 5분보다 간격이 벌어진 실행에서의 5분 되돌아보기가 모든 간격의 일부를 검사하지 않은 채 남겼음)과 그날 한 수정(실시간 구간은 이전 구간 끝의 기록에서 이어 붙이고 90초 일찍 끝남. 기록이 없거나, 60초 미만이거나, 15분을 넘으면 5분 되돌아보기로 돌아감. 재실행은 기록을 읽지 않음). 바뀐 코드로 같은 구간을 재실행하면 동일한 탐지 결과가 나오며, 이는 변경이 고정된 구간의 탐지를 건드리지 않았음을 보여 줄 뿐 이어 붙이기나 지연을 시험하지는 않음. 문의 양식에 대한 스캐너의 제출은 저자의 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” 탐지 결과 3건으로, 셸이
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. ↩