Skip to content
Merged
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
ba836ca
addd possible new test cases for socket
bitterpanda63 Dec 15, 2025
693faa3
Add a should_block_outgoing_request to service_config.py
bitterpanda63 Dec 15, 2025
e4334f1
add props to service_config (not final)
bitterpanda63 Dec 15, 2025
7d72f58
linting
bitterpanda63 Dec 15, 2025
3162f63
Change socket.py to @before so we can block outbound
bitterpanda63 Dec 15, 2025
e3d7216
add API for outbound to update_service_config.py
bitterpanda63 Dec 15, 2025
9bcb674
Update aikido_zen/sinks/socket.py
bitterpanda63 Dec 15, 2025
2d05b84
Apply suggestions from code review
bitterpanda63 Dec 15, 2025
54ac2ae
Fix socket.py, have both an @before and @after
bitterpanda63 Dec 15, 2025
6a9a7df
remove unused get_argument for port in @before on socket.py (domain b…
bitterpanda63 Dec 15, 2025
f25687e
Merge branch 'main' into add-outbound-blocking
bitterpanda63 Dec 22, 2025
69e8750
remove "test_outbound_domain_blocking" from skip list
bitterpanda63 Dec 22, 2025
aaf9933
Update the mock_aikido_core
bitterpanda63 Dec 22, 2025
8f51b51
Before blocking check if its a bypassed ip
bitterpanda63 Dec 22, 2025
138f4fe
use get_hostname_options, normalize punycode
bitterpanda63 Dec 22, 2025
27d1e85
Add comment explaining why we use get_hostname_options
bitterpanda63 Dec 22, 2025
236101a
fix socket_test.py test cases
bitterpanda63 Dec 22, 2025
4afaa38
Revert "use get_hostname_options, normalize punycode"
bitterpanda63 Dec 22, 2025
f9754f3
socket_test add extra test cases
bitterpanda63 Dec 22, 2025
7515687
reset thread cache after running tests
bitterpanda63 Dec 22, 2025
3efddb2
Update http_api_test: add domains & blockNewOutgoingRequests
bitterpanda63 Dec 22, 2025
adbec91
Update socket: Also report hostnames
bitterpanda63 Dec 23, 2025
fbae003
Update socket_test.py test cases
bitterpanda63 Dec 23, 2025
9ed0614
Remove punycode test case in socket_test.py
bitterpanda63 Dec 23, 2025
bc631c7
Remove the failing adds_hostname test case
bitterpanda63 Dec 23, 2025
fa320ba
add extra test cases
bitterpanda63 Dec 23, 2025
97bc477
Revert "remove "test_outbound_domain_blocking" from skip list"
bitterpanda63 Dec 23, 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
2 changes: 2 additions & 0 deletions aikido_zen/background_process/api/http_api_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def test_report_local_valid():
"route": "/test_ratelimiting_1",
}
],
"domains": [],
"blockNewOutgoingRequests": False,
"receivedAnyStats": False,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,12 @@ def update_service_config(connection_manager, res):
bypassed_ips=res.get("allowedIPAddresses", []),
received_any_stats=res.get("receivedAnyStats", True),
)

# Handle outbound request blocking configuration
if "blockNewOutgoingRequests" in res:
connection_manager.conf.set_block_new_outgoing_requests(
res["blockNewOutgoingRequests"]
)

if "domains" in res:
connection_manager.conf.update_domains(res["domains"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
"""Test module for update_service_config function"""

import pytest
from unittest.mock import MagicMock, patch
from .update_service_config import update_service_config
from aikido_zen.background_process.service_config import ServiceConfig


def test_update_service_config_outbound_blocking():
"""Test that update_service_config handles outbound request blocking configuration"""

# Create a mock connection manager with a real ServiceConfig
connection_manager = MagicMock()
connection_manager.conf = ServiceConfig(
endpoints=[],
last_updated_at=0,
blocked_uids=set(),
bypassed_ips=[],
received_any_stats=False,
)
connection_manager.block = False

# Test response with blockNewOutgoingRequests
res = {
"success": True,
"blockNewOutgoingRequests": True,
"domains": [
{"hostname": "example.com", "mode": "block"},
{"hostname": "allowed.com", "mode": "allow"},
],
}

update_service_config(connection_manager, res)

# Verify that the outbound blocking configuration was set
assert connection_manager.conf.block_new_outgoing_requests is True
assert connection_manager.conf.domains == {
"example.com": "block",
"allowed.com": "allow",
}


def test_update_service_config_outbound_blocking_false():
"""Test that update_service_config handles blockNewOutgoingRequests=False"""

# Create a mock connection manager with a real ServiceConfig
connection_manager = MagicMock()
connection_manager.conf = ServiceConfig(
endpoints=[],
last_updated_at=0,
blocked_uids=set(),
bypassed_ips=[],
received_any_stats=False,
)
connection_manager.block = True

# Test response with blockNewOutgoingRequests=False
res = {"success": True, "blockNewOutgoingRequests": False, "domains": []}

update_service_config(connection_manager, res)

# Verify that the outbound blocking configuration was set
assert connection_manager.conf.block_new_outgoing_requests is False
assert connection_manager.conf.domains == {}


def test_update_service_config_outbound_blocking_missing():
"""Test that update_service_config works when outbound blocking fields are missing"""

# Create a mock connection manager with a real ServiceConfig
connection_manager = MagicMock()
connection_manager.conf = ServiceConfig(
endpoints=[],
last_updated_at=0,
blocked_uids=set(),
bypassed_ips=[],
received_any_stats=False,
)
connection_manager.block = False

# Test response without outbound blocking fields
res = {
"success": True,
"endpoints": [],
"configUpdatedAt": 1234567890,
}

update_service_config(connection_manager, res)

# Verify that the outbound blocking configuration was not changed
assert connection_manager.conf.block_new_outgoing_requests is False
assert connection_manager.conf.domains == {}


def test_update_service_config_failure():
"""Test that update_service_config does nothing when response indicates failure"""

# Create a mock connection manager with a real ServiceConfig
connection_manager = MagicMock()
connection_manager.conf = ServiceConfig(
endpoints=[],
last_updated_at=0,
blocked_uids=set(),
bypassed_ips=[],
received_any_stats=False,
)
connection_manager.block = False

# Set initial values
connection_manager.conf.set_block_new_outgoing_requests(True)
connection_manager.conf.update_domains([{"hostname": "test.com", "mode": "block"}])

# Test failed response
res = {"success": False, "blockNewOutgoingRequests": False, "domains": []}

update_service_config(connection_manager, res)

# Verify that nothing was changed due to failure
assert connection_manager.conf.block_new_outgoing_requests is True
assert connection_manager.conf.domains == {"test.com": "block"}


def test_update_service_config_complete():
"""Test that update_service_config handles all fields correctly"""

# Create a mock connection manager with a real ServiceConfig
connection_manager = MagicMock()
connection_manager.conf = ServiceConfig(
endpoints=[],
last_updated_at=0,
blocked_uids=set(),
bypassed_ips=[],
received_any_stats=False,
)
connection_manager.block = False

# Test complete response
res = {
"success": True,
"block": True,
"endpoints": [{"route": "/test", "graphql": False}],
"configUpdatedAt": 1234567890,
"blockedUserIds": ["user1", "user2"],
"allowedIPAddresses": ["192.168.1.1"],
"receivedAnyStats": True,
"blockNewOutgoingRequests": True,
"domains": [
{"hostname": "blocked.com", "mode": "block"},
{"hostname": "allowed.com", "mode": "allow"},
{"hostname": "test.com", "mode": "block"},
],
}

update_service_config(connection_manager, res)

# Verify all configurations were updated
assert connection_manager.block is True
assert len(connection_manager.conf.endpoints) == 1
assert connection_manager.conf.last_updated_at == 1234567890
assert connection_manager.conf.blocked_uids == {"user1", "user2"}
assert connection_manager.conf.received_any_stats is True
assert connection_manager.conf.block_new_outgoing_requests is True
assert connection_manager.conf.domains == {
"blocked.com": "block",
"allowed.com": "allow",
"test.com": "block",
}


def test_update_service_config_domains_only():
"""Test that update_service_config handles domains update only"""

# Create a mock connection manager with a real ServiceConfig
connection_manager = MagicMock()
connection_manager.conf = ServiceConfig(
endpoints=[],
last_updated_at=0,
blocked_uids=set(),
bypassed_ips=[],
received_any_stats=False,
)
connection_manager.block = False

# Test response with only domains
res = {
"success": True,
"domains": [
{"hostname": "api.example.com", "mode": "block"},
{"hostname": "cdn.example.com", "mode": "allow"},
],
}

update_service_config(connection_manager, res)

# Verify that only domains were updated
assert connection_manager.conf.block_new_outgoing_requests is False # Not changed
assert connection_manager.conf.domains == {
"api.example.com": "block",
"cdn.example.com": "allow",
}


def test_update_service_config_block_new_outgoing_requests_only():
"""Test that update_service_config handles blockNewOutgoingRequests update only"""

# Create a mock connection manager with a real ServiceConfig
connection_manager = MagicMock()
connection_manager.conf = ServiceConfig(
endpoints=[],
last_updated_at=0,
blocked_uids=set(),
bypassed_ips=[],
received_any_stats=False,
)
connection_manager.block = False

# Set initial domains
connection_manager.conf.update_domains(
[{"hostname": "existing.com", "mode": "allow"}]
)

# Test response with only blockNewOutgoingRequests
res = {
"success": True,
"blockNewOutgoingRequests": True,
}

update_service_config(connection_manager, res)

# Verify that only blockNewOutgoingRequests was updated
assert connection_manager.conf.block_new_outgoing_requests is True
assert connection_manager.conf.domains == {"existing.com": "allow"} # Not changed
20 changes: 20 additions & 0 deletions aikido_zen/background_process/service_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ def __init__(
self.update(
endpoints, last_updated_at, blocked_uids, bypassed_ips, received_any_stats
)
self.block_new_outgoing_requests = False
self.domains = {}

def update(
self,
Expand Down Expand Up @@ -74,3 +76,21 @@ def set_bypassed_ips(self, bypassed_ips):
def is_bypassed_ip(self, ip):
"""Checks if the IP is on the bypass list"""
return self.bypassed_ips.has(ip)

def update_domains(self, domains):
self.domains = {domain["hostname"]: domain["mode"] for domain in domains}
Copy link

@aikido-pr-checks aikido-pr-checks bot Dec 23, 2025

Choose a reason for hiding this comment

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

update_domains mutates self.domains (a shared dict) without synchronization; concurrent readers may observe partial updates or race conditions.

Details

✨ AI Reasoning
​ServiceConfig gained mutable fields domains and block_new_outgoing_requests that are written by update_service_config and read by sinks. The methods update_domains and set_block_new_outgoing_requests mutate shared ServiceConfig state without any synchronization or atomic swap, introducing a data race between writer and readers.

🔧 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.


def set_block_new_outgoing_requests(self, value: bool):
"""Set whether to block new outgoing requests"""
self.block_new_outgoing_requests = bool(value)

def should_block_outgoing_request(self, hostname: str) -> bool:
mode = self.domains.get(hostname)

if self.block_new_outgoing_requests:
# Only allow outgoing requests if the mode is "allow"
# mode is None for unknown hostnames, so they get blocked
return mode != "allow"

# Only block outgoing requests if the mode is "block"
return mode == "block"
Loading
Loading