diff --git a/backend/app/services/host_silence_scan.py b/backend/app/services/host_silence_scan.py index d7647ac..2d9adb1 100644 --- a/backend/app/services/host_silence_scan.py +++ b/backend/app/services/host_silence_scan.py @@ -12,7 +12,6 @@ from sqlalchemy.orm import Session from app.config import get_settings from app.models import Host, Problem from app.services.problem_rules import RuleMatch, build_fingerprint, host_silence_match_for_host -from app.services.problems import _correlation_cutoff logger = logging.getLogger(__name__) @@ -44,15 +43,13 @@ def open_or_refresh_host_silence_problem( match.rule_id, match.fingerprint_suffix, ) - cutoff = _correlation_cutoff(ref) open_problem = db.scalar( select(Problem) .where( Problem.status == "open", - Problem.fingerprint == fingerprint, + Problem.rule_id == match.rule_id, Problem.host_id == host_id, - Problem.last_seen_at >= cutoff, ) .order_by(Problem.last_seen_at.desc()) .limit(1) @@ -60,6 +57,7 @@ def open_or_refresh_host_silence_problem( if open_problem: open_problem.summary = match.summary open_problem.title = match.title + open_problem.fingerprint = fingerprint open_problem.last_seen_at = ref open_problem.updated_at = ref db.flush() diff --git a/backend/app/version.py b/backend/app/version.py index e1be65f..bdd9d13 100644 --- a/backend/app/version.py +++ b/backend/app/version.py @@ -1,5 +1,5 @@ """Единый источник версии SAC (API, health, логи, OpenAPI).""" APP_NAME = "Security Alert Center" -APP_VERSION = "0.4.7" +APP_VERSION = "0.4.8" APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}" diff --git a/backend/tests/test_host_silence_scan.py b/backend/tests/test_host_silence_scan.py index 6101d53..1f12782 100644 --- a/backend/tests/test_host_silence_scan.py +++ b/backend/tests/test_host_silence_scan.py @@ -82,6 +82,43 @@ def test_scan_does_not_duplicate_open_problem(db_session, scan_settings): assert len(problems) == 1 +def test_scan_does_not_duplicate_after_correlation_window(db_session, scan_settings, monkeypatch): + """host_silence — одна open-проблема на хост, даже если прошло > correlation window.""" + monkeypatch.setenv("SAC_PROBLEM_CORRELATION_WINDOW_MINUTES", "60") + get_settings.cache_clear() + + hb = _ingest( + db_session, + type="agent.heartbeat", + category="agent", + severity="info", + title="hb", + summary="heartbeat", + ) + hb.received_at = datetime.now(timezone.utc) - timedelta(hours=2) + db_session.flush() + + t0 = datetime.now(timezone.utc) + first = run_host_silence_scan(db_session, now=t0) + assert first[0].created is True + first[0].problem.last_seen_at = t0 - timedelta(hours=2) + db_session.flush() + + later = run_host_silence_scan(db_session, now=t0 + timedelta(hours=3)) + assert len(later) == 1 + assert later[0].created is False + assert later[0].problem.id == first[0].problem.id + + problems = db_session.scalars( + select(Problem).where( + Problem.host_id == hb.host_id, + Problem.rule_id == RULE_HOST_SILENCE, + Problem.status == "open", + ) + ).all() + assert len(problems) == 1 + + def test_scan_skips_host_without_heartbeat(db_session, scan_settings): _ingest( db_session, diff --git a/frontend/src/version.ts b/frontend/src/version.ts index a7532a8..2709179 100644 --- a/frontend/src/version.ts +++ b/frontend/src/version.ts @@ -1,4 +1,4 @@ /** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */ export const APP_NAME = "Security Alert Center"; -export const APP_VERSION = "0.4.7"; +export const APP_VERSION = "0.4.8"; export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;