Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d897644
Create a new clean Command & CommandContext interface
bitterpanda63 Nov 13, 2025
1e005c0
Remove ATTACK event
bitterpanda63 Nov 13, 2025
dc5777f
Create a PutEventCommand - with the new modern command standard
bitterpanda63 Nov 13, 2025
c5f4251
Cleanup the process_incoming_command
bitterpanda63 Nov 13, 2025
2d30deb
Merge branch 'main' into clean-attack-reporting
bitterpanda63 Nov 24, 2025
0b35c50
Create a report_event on the cloud_connection_manager
bitterpanda63 Nov 24, 2025
14b34ed
use this report_event
bitterpanda63 Nov 24, 2025
cf3bf1c
cleanup useless functions on cloud_connection_manager
bitterpanda63 Nov 24, 2025
a81cfa5
rename to report_api_event
bitterpanda63 Nov 24, 2025
d179445
update the queue empty-ing
bitterpanda63 Nov 24, 2025
964ad49
Update comment in put_event
bitterpanda63 Nov 24, 2025
48d6df9
remove on_detected_attack in favour of create_detected_attack_api_event
bitterpanda63 Nov 24, 2025
fba6c2c
use the new create_detected_attack_api_event
bitterpanda63 Nov 24, 2025
7d4c053
move test cases for create_detected_attack_api_event
bitterpanda63 Nov 24, 2025
346d4e2
update how event is generated in vulnerabilities
bitterpanda63 Nov 24, 2025
f811a61
create a send_payload
bitterpanda63 Nov 24, 2025
fa94194
update command types to add Payload class
bitterpanda63 Nov 24, 2025
b7a25ac
create send_payload helper function
bitterpanda63 Nov 24, 2025
dcfe12d
move command.py to command_types in helper
bitterpanda63 Nov 24, 2025
88676cd
move is_private_ip to aikido_zen/helpers/net
bitterpanda63 Nov 24, 2025
5d481a4
Fix regular old commands
bitterpanda63 Nov 24, 2025
e240d03
Add limit to the serialize_to_json
bitterpanda63 Nov 24, 2025
5a7e6d1
revert: add limit
bitterpanda63 Nov 24, 2025
4901d34
improve log in vulnerabilities/__init__.py
bitterpanda63 Nov 24, 2025
9884fee
linting fix
bitterpanda63 Nov 24, 2025
34d1a32
Update e2e test to include regex package
bitterpanda63 Nov 24, 2025
2ac1c58
Fix test cases for new put_event data structure changes
bitterpanda63 Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions aikido_zen/background_process/aikido_background_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,7 @@ def send_to_connection_manager(self, event_scheduler):
EMPTY_QUEUE_INTERVAL, 1, self.send_to_connection_manager, (event_scheduler,)
)
while not self.queue.empty():
queue_attack_item = self.queue.get()
self.connection_manager.on_detected_attack(
attack=queue_attack_item[0],
context=queue_attack_item[1],
blocked=queue_attack_item[2],
stack=queue_attack_item[3],
)
self.connection_manager.report_api_event(self.queue.get())
Copy link

@aikido-pr-checks aikido-pr-checks bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced explicit unpacking and on_detected_attack call with direct report_api_event(self.queue.get()) without documenting the new expected queue payload or rationale

Details

✨ AI Reasoning
​​1) The send_to_connection_manager method changed how queue items are processed: previously the code unpacked a 4-tuple and called connection_manager.on_detected_attack; the updated code now forwards queue.get() directly to connection_manager.report_api_event.
​2) This is a meaningful behavioural change that affects the expected queue payload format and routing logic; the method has a docstring but no commentary about the new expected item shape or rationale for the changed dispatch, which reduces clarity for maintainers.
​3) The change occurred in this diff (replaced explicit unpack-and-call with direct pass-through), so documentation was effectively reduced/worsened by this PR.

🔧 How do I fix it?
Add docstrings or comments explaining what the function does, its parameters, and return values.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.



def add_exit_handlers():
Expand Down
39 changes: 25 additions & 14 deletions aikido_zen/background_process/cloud_connection_manager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
from aikido_zen.storage.users import Users
from aikido_zen.storage.hostnames import Hostnames
from ..realtime.start_polling_for_changes import start_polling_for_changes
from ...helpers.get_current_unixtime_ms import get_unixtime_ms
from ...storage.ai_statistics import AIStatistics
from ...storage.firewall_lists import FirewallLists
from ...storage.statistics import Statistics

# Import functions :
from .on_detected_attack import on_detected_attack
from .get_manager_info import get_manager_info
from .update_service_config import update_service_config
from .on_start import on_start
Expand Down Expand Up @@ -58,7 +58,7 @@ def __init__(self, block, api, token, serverless):

def start(self, event_scheduler):
"""Send out start event and add heartbeats"""
res = self.on_start()
res = on_start(self)
if res.get("error", None) == "invalid_token":
logger.info(
"Token was invalid, not starting heartbeats and realtime polling."
Expand All @@ -82,26 +82,37 @@ def report_initial_stats(self):
if should_report_initial_stats:
self.send_heartbeat()

def on_detected_attack(self, attack, context, blocked, stack):
"""This will send something to the API when an attack is detected"""
return on_detected_attack(self, attack, context, blocked, stack)

def on_start(self):
"""This will send out an Event signalling the start to the server"""
return on_start(self)

def send_heartbeat(self):
"""This will send a heartbeat to the server"""
return send_heartbeat(self)

def get_manager_info(self):
"""This returns info about the connection_manager"""
return get_manager_info(self)

def update_service_config(self, res):
"""Update configuration based on the server's response"""
return update_service_config(self, res)

def update_firewall_lists(self):
"""Will update service config with blocklist of IP addresses"""
return update_firewall_lists(self)

def report_api_event(self, event):
Copy link

@aikido-pr-checks aikido-pr-checks bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method report_api_event lacks a docstring describing expected event payload, return structure, and error handling behavior.

Details

✨ AI Reasoning
​​1) The new CloudConnectionManager.report_api_event method constructs a payload, merges event fields, calls the API, and handles error logging and exceptions — this is non-trivial control flow affecting reporting behavior.
​2) Lack of a docstring means future readers won't have guidance on intent, expected event shape, return values, or error semantics, harming maintainability.
​3) The method's name alone doesn't fully document the payload merging behavior or the error handling policy.
​4) Adding a brief docstring would be a small, self-contained improvement appropriate for this PR.
​5) This is not a trivial getter; it's a business-logic method for reporting.
​6) Fixable within the PR by adding a short docstring/comment.

🔧 How do I fix it?
Add docstrings or comments explaining what the function does, its parameters, and return values.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

if not self.token:
return {"success": False, "error": "invalid_token"}
try:
payload = {
"time": get_unixtime_ms(),
"agent": get_manager_info(self),
}
payload.update(event) # Merge default fields with event fields
Copy link

@aikido-pr-checks aikido-pr-checks bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

report_api_event merges the incoming 'event' dict via payload.update(event) without copying, causing unsafe access to shared mutable state across threads

Details

✨ AI Reasoning
​​1) The new CloudConnectionManager.report_api_event method takes an external 'event' object from the background thread and directly calls payload.update(event) to merge it into a new payload; 2) If that 'event' is a mutable dict that is concurrently produced or modified by other threads, merging it without copying can result in race conditions or inconsistent reads; 3) This harms thread-safety because the method reads shared mutable data without synchronization; 4) Fixing this would meaningfully reduce the chance of subtle concurrent-bug reproduction and is feasible within this PR by making a shallow copy of the event before merging; 5) Using immutable or copied data is a common expectation for cross-thread messaging; 6) This is a localized change (in report_api_event) and doesn't require major refactoring.

🔧 How do I fix it?
Use locks, concurrent collections, or atomic operations when accessing shared mutable state. Avoid modifying collections during iteration. Use proper synchronization primitives like mutex, lock, or thread-safe data structures.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.


result = self.api.report(self.token, payload, self.timeout_in_sec)
if not result.get("success", True):
logger.error(
"CloudConnectionManager: Reporting to api failed, error=%s",
result.get("error", "unknown"),
)
return result
except Exception as e:
logger.debug(e)
logger.error(
"CloudConnectionManager: Reporting to api failed, unexpected error (see debug logs)"
)

This file was deleted.

24 changes: 7 additions & 17 deletions aikido_zen/background_process/cloud_connection_manager/on_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,16 @@


def on_start(connection_manager):
"""
This will send out an Event signalling the start to the server
"""
if not connection_manager.token:
return
res = connection_manager.api.report(
connection_manager.token,
{
"type": "started",
"time": get_unixtime_ms(),
"agent": connection_manager.get_manager_info(),
},
connection_manager.timeout_in_sec,
)
event = {"type": "started"}
Copy link

@aikido-pr-checks aikido-pr-checks bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function on_start no longer fails fast when connection_manager.token is missing; the prior early-return was removed, reducing clarity of control flow

Details

✨ AI Reasoning
​​1) The on_start function was modified to remove the prior early-return guard that immediately returned when connection_manager.token was falsy; it now always constructs an event and calls connection_manager.report_api_event(event), relying on that function to handle invalid-token cases.
​2) This change worsens clarity by removing a local fail-fast guard and reintroducing an execution path through report_api_event even when no token exists, making the control flow less explicit at the call site and slightly harder to reason about.
​3) Because the PR removed the early-return from on_start (it previously prevented further work when token was missing), this is a regression against the prefer-early-return guideline and therefore a valid violation introduced by this diff.

🔧 How do I fix it?
Place parameter validation and guard clauses at the function start. Use early returns to reduce nesting levels and improve readability.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

res = connection_manager.report_api_event(event)

if not res.get("success", True):
# Update config time even in failure :
connection_manager.conf.last_updated_at = get_unixtime_ms()
logger.error("Failed to communicate with Aikido Server : %s", res["error"])
connection_manager.conf.last_updated_at = (
get_unixtime_ms()
) # Update config time even in failure
else:
connection_manager.update_service_config(res)
connection_manager.update_firewall_lists()
logger.info("Established connection with Aikido Server")

return res
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def mock_connection_manager():
connection_manager = MagicMock()
connection_manager.token = "test_token"
connection_manager.timeout_in_sec = 5
connection_manager.api.report = MagicMock(return_value={"success": True})
connection_manager.report_api_event = MagicMock(return_value={"success": True})
connection_manager.get_manager_info = lambda: {}
connection_manager.update_service_config = MagicMock()
return connection_manager
Expand All @@ -19,15 +19,15 @@ def test_on_start_no_token():
connection_manager = MagicMock()
connection_manager.token = None
on_start(connection_manager)
connection_manager.api.report.assert_not_called()
connection_manager.report_api_event.assert_called()


def test_on_start_success(mock_connection_manager, caplog):
"""Test that the API call is made successfully and the service config is updated."""
on_start(mock_connection_manager)

# Check that the API report method was called
mock_connection_manager.api.report.assert_called_once()
mock_connection_manager.report_api_event.assert_called_once()

# Check that the service config was updated
mock_connection_manager.update_service_config.assert_called_once()
Expand All @@ -38,18 +38,15 @@ def test_on_start_success(mock_connection_manager, caplog):

def test_on_start_failure(mock_connection_manager, caplog):
"""Test that an error is logged when the API call fails."""
mock_connection_manager.api.report.return_value = {
mock_connection_manager.report_api_event.return_value = {
"success": False,
"error": "Some error",
}

on_start(mock_connection_manager)

# Check that the API report method was called
mock_connection_manager.api.report.assert_called_once()
mock_connection_manager.report_api_event.assert_called_once()

# Check that the service config was not updated
mock_connection_manager.update_service_config.assert_not_called()

# Check that the error log was called
assert "Failed to communicate with Aikido Server : Some error" in caplog.text
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from aikido_zen.background_process.packages import PackagesStore
from aikido_zen.helpers.logging import logger
from aikido_zen.helpers.get_current_unixtime_ms import get_unixtime_ms


def send_heartbeat(connection_manager):
Expand All @@ -26,20 +25,16 @@ def send_heartbeat(connection_manager):
connection_manager.ai_stats.clear()
PackagesStore.clear()

res = connection_manager.api.report(
connection_manager.token,
res = connection_manager.report_api_event(
Copy link

@aikido-pr-checks aikido-pr-checks bot Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed outbound heartbeat reporting: switched to connection_manager.report_api_event and removed 'time' and 'agent' from the heartbeat payload which may break the server-side API contract.

Details

🔧 How do I fix it?
Support both old and new parameter names during transition periods. Add new optional parameters with defaults. Keep existing response fields while adding new ones. Focus on backwards compatibility.

More info - Comment @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.

{
"type": "heartbeat",
"time": get_unixtime_ms(),
"agent": connection_manager.get_manager_info(),
"stats": stats,
"ai": ai_stats,
"hostnames": outgoing_domains,
"packages": packages,
"routes": routes,
"users": users,
"middlewareInstalled": connection_manager.middleware_installed,
},
connection_manager.timeout_in_sec,
}
)
connection_manager.update_service_config(res)
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ def update_service_config(connection_manager, res):
Update configuration based on the server's response
"""
if res.get("success", False) is False:
logger.debug(res)
return

if "block" in res.keys() and res["block"] != connection_manager.block:
logger.debug("Updating blocking, setting blocking to : %s", res["block"])
connection_manager.block = bool(res["block"])
Expand Down
44 changes: 22 additions & 22 deletions aikido_zen/background_process/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
"""Commands __init__.py file"""

from aikido_zen.helpers.logging import logger
from .attack import process_attack
from aikido_zen.helpers.ipc.command_types import CommandContext
from .put_event import PutEventCommand
from .check_firewall_lists import process_check_firewall_lists
from .read_property import process_read_property
from .should_ratelimit import process_should_ratelimit
from .ping import process_ping
from .sync_data import process_sync_data

commands_map = {
# This maps to a tuple : (function, returns_data?)
# Commands that don't return data :
"ATTACK": (process_attack, False),
# Commands that return data :
"SYNC_DATA": (process_sync_data, True),
"READ_PROPERTY": (process_read_property, True),
"SHOULD_RATELIMIT": (process_should_ratelimit, True),
"PING": (process_ping, True),
"CHECK_FIREWALL_LISTS": (process_check_firewall_lists, True),
"SYNC_DATA": process_sync_data,
"READ_PROPERTY": process_read_property,
"SHOULD_RATELIMIT": process_should_ratelimit,
"PING": process_ping,
"CHECK_FIREWALL_LISTS": process_check_firewall_lists,
}

modern_commands = [PutEventCommand]


def process_incoming_command(connection_manager, obj, conn, queue):
"""Processes an incoming command"""
action = obj[0]
data = obj[1]
if action in commands_map:
func, returns_data = commands_map[action]
if returns_data:
return conn.send(func(connection_manager, data, queue))
func(connection_manager, data, queue)
else:
logger.debug("Command : `%s` not found, aborting", action)
inbound_identifier = obj[0]
inbound_request = obj[1]
if inbound_identifier in commands_map:
func = commands_map[inbound_identifier]
return conn.send(func(connection_manager, inbound_request))

for cmd in modern_commands:
if cmd.identifier() == inbound_identifier:
cmd.run(CommandContext(connection_manager, queue, conn), inbound_request)
return None

logger.debug("Command : `%s` not found - did not execute", inbound_identifier)
return None
9 changes: 0 additions & 9 deletions aikido_zen/background_process/commands/attack.py

This file was deleted.

Loading