From 09ee0698d42344bf7ccf3df7cad87e34f1208ef0 Mon Sep 17 00:00:00 2001 From: Prowler Bot Date: Fri, 23 Jan 2026 16:26:05 +0100 Subject: [PATCH] fix(attack-paths): clear Neo4j database cache after scan and queries (#9878) Co-authored-by: Josema Camacho --- api/CHANGELOG.md | 1 + api/src/backend/api/attack_paths/database.py | 11 +++++++++++ api/src/backend/api/attack_paths/retryable_session.py | 4 ++-- api/src/backend/api/tests/test_views.py | 3 +++ api/src/backend/api/v1/views.py | 2 ++ api/src/backend/tasks/jobs/attack_paths/scan.py | 5 +++++ api/src/backend/tasks/tests/test_attack_paths_scan.py | 1 + 7 files changed, 25 insertions(+), 2 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 34ad1d222d..19d323bc0b 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to the **Prowler API** are documented in this file. - Use `Findings.all_objects` to avoid the `ActiveProviderPartitionedManager` [(#9869)](https://github.com/prowler-cloud/prowler/pull/9869) - Lazy load Neo4j driver for workers only [(#9872)](https://github.com/prowler-cloud/prowler/pull/9872) - Improve Cypher query for inserting Findings into Attack Paths scan graphs [(#9874)](https://github.com/prowler-cloud/prowler/pull/9874) +- Clear Neo4j database cache after Attack Paths scan and each API query [(#9877)](https://github.com/prowler-cloud/prowler/pull/9877) ## [1.18.0] (Prowler v5.17.0) diff --git a/api/src/backend/api/attack_paths/database.py b/api/src/backend/api/attack_paths/database.py index b006005c75..08b5552054 100644 --- a/api/src/backend/api/attack_paths/database.py +++ b/api/src/backend/api/attack_paths/database.py @@ -125,6 +125,17 @@ def drop_subgraph(database: str, root_node_label: str, root_node_id: str) -> int return 0 # As there are no nodes to delete, the result is empty +def clear_cache(database: str) -> None: + query = "CALL db.clearQueryCaches()" + + try: + with get_session(database) as session: + session.run(query) + + except GraphDatabaseQueryException as exc: + logging.warning(f"Failed to clear query cache for database `{database}`: {exc}") + + # Neo4j functions related to Prowler + Cartography DATABASE_NAME_TEMPLATE = "db-{attack_paths_scan_id}" diff --git a/api/src/backend/api/attack_paths/retryable_session.py b/api/src/backend/api/attack_paths/retryable_session.py index 05d0be9c30..2c70bc6a8e 100644 --- a/api/src/backend/api/attack_paths/retryable_session.py +++ b/api/src/backend/api/attack_paths/retryable_session.py @@ -64,9 +64,9 @@ class RetryableSession: return method(*args, **kwargs) except ( - neo4j.exceptions.ServiceUnavailable, - ConnectionResetError, BrokenPipeError, + ConnectionResetError, + neo4j.exceptions.ServiceUnavailable, ) as exc: # pragma: no cover - depends on infra last_exc = exc attempt += 1 diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index a05345aaac..989c019198 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -3867,6 +3867,7 @@ class TestAttackPathsScanViewSet: "api.v1.views.attack_paths_views_helpers.execute_attack_paths_query", return_value=graph_payload, ) as mock_execute, + patch("api.v1.views.graph_database.clear_cache") as mock_clear_cache, ): response = authenticated_client.post( reverse( @@ -3889,6 +3890,7 @@ class TestAttackPathsScanViewSet: query_definition, prepared_parameters, ) + mock_clear_cache.assert_called_once_with(attack_paths_scan.graph_database) result = response.json()["data"] attributes = result["attributes"] assert attributes["nodes"] == graph_payload["nodes"] @@ -4000,6 +4002,7 @@ class TestAttackPathsScanViewSet: "api.v1.views.attack_paths_views_helpers.execute_attack_paths_query", return_value={"nodes": [], "relationships": []}, ), + patch("api.v1.views.graph_database.clear_cache"), ): response = authenticated_client.post( reverse( diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index db3a5a9bf0..9d43116d21 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -77,6 +77,7 @@ from rest_framework_json_api.views import RelationshipView, Response from rest_framework_simplejwt.exceptions import InvalidToken, TokenError from api.attack_paths import ( + database as graph_database, get_queries_for_provider, get_query_by_id, views_helpers as attack_paths_views_helpers, @@ -2435,6 +2436,7 @@ class AttackPathsScanViewSet(BaseRLSViewSet): graph = attack_paths_views_helpers.execute_attack_paths_query( attack_paths_scan, query_definition, parameters ) + graph_database.clear_cache(attack_paths_scan.graph_database) status_code = status.HTTP_200_OK if not graph.get("nodes"): diff --git a/api/src/backend/tasks/jobs/attack_paths/scan.py b/api/src/backend/tasks/jobs/attack_paths/scan.py index 88b46ccec6..9db4d6bf6e 100644 --- a/api/src/backend/tasks/jobs/attack_paths/scan.py +++ b/api/src/backend/tasks/jobs/attack_paths/scan.py @@ -137,6 +137,11 @@ def run(tenant_id: str, scan_id: str, task_id: str) -> dict[str, Any]: neo4j_session, prowler_api_provider, scan_id, cartography_config ) + logger.info( + f"Clearing Neo4j cache for database {cartography_config.neo4j_database}" + ) + graph_database.clear_cache(cartography_config.neo4j_database) + logger.info( f"Completed Cartography ({attack_paths_scan.id}) for " f"{prowler_api_provider.provider.upper()} provider {prowler_api_provider.id}" diff --git a/api/src/backend/tasks/tests/test_attack_paths_scan.py b/api/src/backend/tasks/tests/test_attack_paths_scan.py index 923cde4cba..0309e7162a 100644 --- a/api/src/backend/tasks/tests/test_attack_paths_scan.py +++ b/api/src/backend/tasks/tests/test_attack_paths_scan.py @@ -68,6 +68,7 @@ class TestAttackPathsRun: "tasks.jobs.attack_paths.scan.graph_database.get_session", return_value=session_ctx, ) as mock_get_session, + patch("tasks.jobs.attack_paths.scan.graph_database.clear_cache"), patch( "tasks.jobs.attack_paths.scan.cartography_create_indexes.run" ) as mock_cartography_indexes,