-
Notifications
You must be signed in to change notification settings - Fork 13
attack wave detection #474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
db6151b
34b0cd5
ac9f7a8
9bf6c8d
4381b01
d742f45
9553549
4fbe510
c5a9474
31d6049
04c72c4
ec8bee6
90063ec
5a82406
0d2af82
66d59a5
683e11e
04a0dc3
171d15a
7660300
49f2b63
e50c085
5347e99
54dfea0
6697505
47b76a1
fc46f2f
6a5dc03
b3231dc
a801eb5
8654e40
0af7bb2
d90409c
f57b48a
d448274
9d1ec5d
fef7e80
a43629d
f37cf7b
821c98a
39f9473
dba2b84
2b4fde1
638a7d2
26b7a0b
a2cbe7f
15f806a
c82df82
6ff29ff
5cbcb9c
3b9a5b6
db52072
1b11d0f
c5ced77
7be910c
e4b48f8
5aa29dc
83399d3
da59957
ac77c31
d1ac363
cf96fbf
67c8478
614b0c1
9ae38d5
057b6be
2e18a88
dbb0598
436c2e8
11b9cfe
35d6ffb
c10bd15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| from aikido_zen.helpers.limit_length_metadata import limit_length_metadata | ||
| from aikido_zen.helpers.logging import logger | ||
|
|
||
|
|
||
| def create_attack_wave_event(context, metadata): | ||
| try: | ||
| return { | ||
| "type": "detected_attack_wave", | ||
| "attack": { | ||
| "user": getattr(context, "user", None), | ||
| "metadata": limit_length_metadata(metadata, 4096), | ||
| }, | ||
| "request": extract_request_if_possible(context), | ||
| } | ||
| except Exception as e: | ||
| logger.error("Failed to create detected_attack_wave API event: %s", str(e)) | ||
| return None | ||
|
|
||
|
|
||
| def extract_request_if_possible(context): | ||
| if not context: | ||
| return None | ||
| return { | ||
| "ipAddress": getattr(context, "remote_address", None), | ||
| "source": getattr(context, "source", None), | ||
| "userAgent": context.get_user_agent(), | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| import pytest | ||
| from unittest.mock import MagicMock | ||
| from .create_attack_wave_event import ( | ||
| create_attack_wave_event, | ||
| extract_request_if_possible, | ||
| ) | ||
| import aikido_zen.test_utils as test_utils | ||
|
|
||
|
|
||
| def test_create_attack_wave_event_success(): | ||
| """Test successful creation of attack wave event with basic data""" | ||
| metadata = {"test": "value"} | ||
| context = test_utils.generate_context() | ||
|
|
||
| event = create_attack_wave_event(context, metadata) | ||
|
|
||
| assert event is not None | ||
| assert event["type"] == "detected_attack_wave" | ||
| assert event["attack"]["user"] is None | ||
| assert event["attack"]["metadata"] == metadata | ||
| assert event["request"] is not None | ||
|
|
||
|
|
||
| def test_create_attack_wave_event_with_user(): | ||
| """Test attack wave event creation with user information""" | ||
| metadata = {"test": "value"} | ||
| context = test_utils.generate_context(user="test_user") | ||
|
|
||
| event = create_attack_wave_event(context, metadata) | ||
|
|
||
| assert event["attack"]["user"] == "test_user" | ||
| assert event["attack"]["metadata"] == metadata | ||
|
|
||
|
|
||
| def test_create_attack_wave_event_with_long_metadata(): | ||
| """Test that metadata longer than 4096 characters is truncated""" | ||
| long_metadata = "x" * 5000 # Create metadata longer than 4096 characters | ||
| metadata = {"test": long_metadata} | ||
| context = test_utils.generate_context() | ||
|
|
||
| event = create_attack_wave_event(context, metadata) | ||
|
|
||
| assert len(event["attack"]["metadata"]["test"]) == 4096 | ||
| assert event["attack"]["metadata"]["test"] == long_metadata[:4096] | ||
|
|
||
|
|
||
| def test_create_attack_wave_event_with_multiple_long_metadata_fields(): | ||
| """Test that multiple metadata fields longer than 4096 characters are truncated""" | ||
| long_value1 = "a" * 5000 | ||
| long_value2 = "b" * 6000 | ||
| metadata = { | ||
| "field1": long_value1, | ||
| "field2": long_value2, | ||
| } | ||
| context = test_utils.generate_context() | ||
|
|
||
| event = create_attack_wave_event(context, metadata) | ||
|
|
||
| assert len(event["attack"]["metadata"]["field1"]) == 4096 | ||
| assert len(event["attack"]["metadata"]["field2"]) == 4096 | ||
| assert event["attack"]["metadata"]["field1"] == long_value1[:4096] | ||
| assert event["attack"]["metadata"]["field2"] == long_value2[:4096] | ||
|
|
||
|
|
||
| def test_create_attack_wave_event_request_data(): | ||
| """Test that request data is correctly extracted from context""" | ||
| metadata = {"test": "value"} | ||
| context = test_utils.generate_context( | ||
| ip="198.51.100.23", | ||
| route="/test-route", | ||
| headers={"user-agent": "Mozilla/5.0"}, | ||
| ) | ||
|
|
||
| event = create_attack_wave_event(context, metadata) | ||
|
|
||
| request_data = event["request"] | ||
| assert request_data["ipAddress"] == "198.51.100.23" | ||
| assert request_data["source"] == "flask" | ||
| assert request_data["userAgent"] == "Mozilla/5.0" | ||
|
|
||
|
|
||
| def test_create_attack_wave_event_no_context(): | ||
| """Test attack wave event creation with None context""" | ||
| metadata = {"test": "value"} | ||
|
|
||
| event = create_attack_wave_event(None, metadata) | ||
|
|
||
| assert event["attack"]["user"] is None | ||
| assert event["attack"]["metadata"] == metadata | ||
| assert event["request"] is None | ||
|
|
||
|
|
||
| def test_create_attack_wave_event_exception_handling(): | ||
| """Test that exceptions during event creation are handled gracefully""" | ||
| # Create a context that will raise an exception when accessed | ||
| context = MagicMock() | ||
| context.user = "test_user" | ||
| context.remote_address = "1.1.1.1" | ||
| context.source = "test_source" | ||
| # Make get_user_agent raise an exception | ||
| context.get_user_agent.side_effect = Exception("Test exception") | ||
|
|
||
| metadata = {"test": "value"} | ||
|
|
||
| # This should not raise an exception, but return None | ||
| event = create_attack_wave_event(context, metadata) | ||
|
|
||
| # Since we're mocking and causing an exception, the function should handle it | ||
| # and return None based on the exception handling in the function | ||
| assert event is None | ||
|
|
||
|
|
||
| def test_extract_request_if_possible_with_valid_context(): | ||
| """Test request extraction with valid context""" | ||
| context = test_utils.generate_context( | ||
| ip="198.51.100.23", | ||
| route="/test-route", | ||
| headers={"user-agent": "Mozilla/5.0"}, | ||
| ) | ||
|
|
||
| request = extract_request_if_possible(context) | ||
|
|
||
| assert request is not None | ||
| assert request["ipAddress"] == "198.51.100.23" | ||
| assert request["source"] == "flask" | ||
| assert request["userAgent"] == "Mozilla/5.0" | ||
|
|
||
|
|
||
| def test_extract_request_if_possible_with_none_context(): | ||
| """Test request extraction with None context""" | ||
| request = extract_request_if_possible(None) | ||
| assert request is None | ||
|
|
||
|
|
||
| def test_extract_request_if_possible_with_minimal_context(): | ||
| """Test request extraction with minimal context data""" | ||
| context = test_utils.generate_context() | ||
|
|
||
| request = extract_request_if_possible(context) | ||
|
|
||
| assert request is not None | ||
| assert request["ipAddress"] == "1.1.1.1" | ||
| assert request["source"] == "flask" | ||
| assert request["userAgent"] is None | ||
|
|
||
|
|
||
| def test_create_attack_wave_event_empty_metadata(): | ||
| """Test attack wave event creation with empty metadata""" | ||
| metadata = {} | ||
| context = test_utils.generate_context() | ||
|
|
||
| event = create_attack_wave_event(context, metadata) | ||
|
|
||
| assert event is not None | ||
| assert event["attack"]["metadata"] == {} | ||
| assert event["request"] is not None | ||
|
|
||
|
|
||
| def test_create_attack_wave_event_complex_metadata(): | ||
| """Test attack wave event creation with complex nested metadata""" | ||
| metadata = { | ||
| "nested": {"key1": "value1", "key2": "value2"}, | ||
| "simple": "simple_value", | ||
| "json_string": "[1, 2, 3]", | ||
| "number_string": "42", | ||
| } | ||
| context = test_utils.generate_context() | ||
|
|
||
| event = create_attack_wave_event(context, metadata) | ||
|
|
||
| assert event["attack"]["metadata"] == metadata | ||
| assert event["attack"]["metadata"]["nested"]["key1"] == "value1" | ||
| assert event["attack"]["metadata"]["json_string"] == "[1, 2, 3]" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,14 @@ | |
| from aikido_zen.api_discovery.update_route_info import update_route_info_from_context | ||
| from aikido_zen.helpers.is_useful_route import is_useful_route | ||
| from aikido_zen.helpers.logging import logger | ||
| from aikido_zen.helpers.create_attack_wave_event import create_attack_wave_event | ||
| from aikido_zen.thread.thread_cache import get_cache | ||
| from .ip_allowed_to_access_route import ip_allowed_to_access_route | ||
| import aikido_zen.background_process.comms as c | ||
| from ...background_process.commands import PutEventCommand | ||
| from ...helpers.ipc.send_payload import send_payload | ||
| from ...helpers.serialize_to_json import serialize_to_json | ||
| from ...storage.attack_wave_detector_store import attack_wave_detector_store | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Module-level import Details✨ AI Reasoning 🔧 How do I fix it? More info - Comment |
||
|
|
||
|
|
||
| def request_handler(stage, status_code=0): | ||
|
|
@@ -79,25 +84,38 @@ def pre_response(): | |
| if block_type == "bot-blocking": | ||
| msg = "You are not allowed to access this resource because you have been identified as a bot." | ||
| return msg, 403 | ||
| return None | ||
|
|
||
|
|
||
| def post_response(status_code): | ||
| """Checks if the current route is useful""" | ||
| """Checks if the current route is useful and performs attack wave detection""" | ||
| context = ctx.get_current_context() | ||
| if not context: | ||
| return | ||
| route_metadata = context.get_route_metadata() | ||
|
|
||
| cache = get_cache() | ||
| if not cache: | ||
| return | ||
|
|
||
| attack_wave = attack_wave_detector_store.is_attack_wave(context.remote_address) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Call to shared attack_wave_detector_store.is_attack_wave from request handler can cause race conditions if the store is not thread-safe Details✨ AI Reasoning 🔧 How do I fix it? More info - Comment |
||
| if attack_wave: | ||
| cache.stats.on_detected_attack_wave(blocked=False) | ||
aikido-autofix[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| event = create_attack_wave_event(context, metadata={}) | ||
| logger.debug("Attack wave: %s", serialize_to_json(event)[:5000]) | ||
|
|
||
| # Report in background to core (send event over IPC) | ||
aikido-autofix[bot] marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment '# Report in background to core (send event over IPC)' restates the mechanics of the following code instead of explaining the rationale or intent. Details🔧 How do I fix it? More info - Comment |
||
| if c.get_comms() and event: | ||
| send_payload(c.get_comms(), PutEventCommand.generate(event)) | ||
|
|
||
| # Check if the current route is useful for API discovery | ||
| is_curr_route_useful = is_useful_route( | ||
| status_code, | ||
| context.route, | ||
| context.method, | ||
| ) | ||
| if not is_curr_route_useful: | ||
| return | ||
|
|
||
| cache = get_cache() | ||
| if cache: | ||
| if is_curr_route_useful: | ||
aikido-autofix[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| cache.routes.increment_route(route_metadata) | ||
|
|
||
| # api spec generation | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.