수집 거부 카운터는 사유별 숫자만 남겨, "우리 노드 오설정 vs 외부 스캐너"를
구분할 수 없었다. 서명불일치 2건이 우리 노드의 키 불일치인지 인터넷 프로브인지
대시보드만으론 판별 불가했다.
- config.php: 최근 거부 이벤트 링버퍼(var/ingest_reject_events.json, 상한
INGEST_REJECT_EVENTS_MAX=50, flock 기반 DoS 안전) 추가. record_ingest_rejection()
이 모든 거부에서 출처 IP(REMOTE_ADDR, 형식검증)를 자동 기록한다. 본문 유래
값(device_id 등)은 신뢰불가 → ingest_sanitize_token() 으로 제어문자 제거·절단
(로그 인젝션 방어). var/ 는 .htaccess 로 웹 접근 차단됨.
- api/sensor_data.php: 서명 거부 시 서명모드(raw=키불일치/none=헤더스트립)와
device_id 를 컨텍스트로 함께 남겨 출처 판별을 돕는다.
- dashboard.php: '수집 거부' 위젯 아래 '최근 거부 출처' 표(시각·IP·사유·모드·
device_id) 노출. 모든 필드 htmlspecialchars.
- tests/ingest_reject_log_test.php: 로그 인젝션 제거·IP 형식검증 회귀 추가,
run_tests.sh 등록. security_hardening_test.php 소스 회귀 5종 추가.
주: config.php 에는 작업 트리에 있던 선행 변경(METRIC_SENSOR_IDS — 대시보드
온습도 센서카드 목록)이 함께 포함된다.
누수/온습도 경보 쿨다운이 경보 태그만 조회해, 직전에 [누수복구]/[정상복귀]가
있었어도 이전 경보 발송 시각만 보고 억제했다. 그 결과 복구 직후 재발한 새 사고
(별개 에피소드)가 쿨다운 창(기본 30분) 동안 통째로 누락됐다.
- alert_cooldown_suppresses(): 경보/복구 중 "최신"이 경보이고(=미복구) 쿨다운
이내일 때만 억제하는 순수 헬퍼 추가. 최신이 복구면 새 에피소드로 즉시 발송.
- send_leak_alert_sms / send_metric_alert_sms: 쿨다운 조회를 (경보|복구) 최신
1건 기준으로 교체하고 위 헬퍼로 판정. 지속 경보 폭주 방지는 그대로 유지.
- tests/cooldown_episode_test.php: 복구 직후 재발 발송 등 9종 회귀 추가,
run_tests.sh 에 등록.
- tests/security_hardening_test.php: dew_point_c 의 forecast.php 단일출처 이동에
맞춰 낡은 단언 갱신(정의는 forecast.php, sms_send.php 는 사용 확인).
작업 트리에 있던 dew_point_c 중복정의 제거(forecast.php 단일출처화)도 sms_send.php
변경에 함께 포함된다.