diff --git a/netx_api/audit_async.py b/netx_api/audit_async.py index ebd7ba8..54876e7 100644 --- a/netx_api/audit_async.py +++ b/netx_api/audit_async.py @@ -32,6 +32,15 @@ _MIDDLEWARE_NOISE_ACTIONS = frozenset( } ) +# Frontend/session polls under /v1/auth/* — middleware tags them auth.{tail}. +# Keep failures (expired session etc.); drop successful chatter. +_AUTH_READ_NOISE_ACTIONS = frozenset( + { + "auth.me", + "auth.sessions", + } +) + _ALWAYS_KEEP_PREFIXES = ( "auth.", "users.", @@ -62,7 +71,9 @@ def audit_should_persist( """Decide whether a candidate audit event is worth writing. Policy: - - Always keep auth / users / tokens / NE / port_traffic / config_sync / semantic webcrt. + - Always keep auth login/logout/security events, users/tokens/NE/port_traffic/config_sync, + and semantic webcrt session/command events. + - Drop successful ``auth.me`` / ``auth.sessions`` polls (UI session checks). - Always keep HTTP failures (status >= 400), except pure list noise. - Drop successful GET/HEAD/OPTIONS ``http.*`` (page polling). - Always keep mutating ``http.*`` (POST/PUT/PATCH/DELETE) — no sampling. @@ -76,6 +87,10 @@ def audit_should_persist( if act in _MIDDLEWARE_NOISE_ACTIONS: return False + # /v1/auth/me and /v1/auth/sessions are polled constantly by the UI. + if act in _AUTH_READ_NOISE_ACTIONS and code < 400: + return False + if act.startswith("webcrt.session_") or act == "webcrt.command": return True diff --git a/netx_api/auth_service.py b/netx_api/auth_service.py index bbb0a08..ebda386 100644 --- a/netx_api/auth_service.py +++ b/netx_api/auth_service.py @@ -745,6 +745,8 @@ def list_audit_logs( "webcrt.delete", "users.get", "api_tokens.get", + "auth.me", + "auth.sessions", ) q = q.filter(~AuditLog.action.like("http.%")) q = q.filter(~AuditLog.action.in_(noise_actions)) diff --git a/tests/test_audit_noise.py b/tests/test_audit_noise.py index e77b76f..4d4da51 100644 --- a/tests/test_audit_noise.py +++ b/tests/test_audit_noise.py @@ -42,8 +42,15 @@ class AuditShouldPersistTests(unittest.TestCase): self.assertTrue(audit_should_persist(action="webcrt.command", status_code=0)) self.assertTrue(audit_should_persist(action="webcrt.session_closed", status_code=0)) + def test_drop_auth_me_poll(self) -> None: + self.assertFalse(audit_should_persist(action="auth.me", method="GET", status_code=200)) + self.assertFalse(audit_should_persist(action="auth.sessions", method="GET", status_code=200)) + # Failures still useful (expired session / forbidden). + self.assertTrue(audit_should_persist(action="auth.me", method="GET", status_code=401)) + def test_keep_business_prefixes(self) -> None: self.assertTrue(audit_should_persist(action="auth.login", status_code=200)) + self.assertTrue(audit_should_persist(action="auth.logout", status_code=200)) self.assertTrue(audit_should_persist(action="ne.exec", status_code=200)) self.assertTrue(audit_should_persist(action="port_traffic.device.start", status_code=200)) self.assertTrue(audit_should_persist(action="config_sync.start", status_code=200))