Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
c19e677
Add optional content to the Data class
ondrej-lukas Apr 23, 2025
cd5430e
Create logfile upon reading cyst config
ondrej-lukas Apr 23, 2025
4130ffa
Correctly initialize the logfile
ondrej-lukas Apr 23, 2025
4614021
Fix the log updates
ondrej-lukas Apr 23, 2025
011e2e4
Use all keys when rebuilding the gamestate
ondrej-lukas Apr 23, 2025
b96ec3e
Parse the logs
ondrej-lukas Apr 23, 2025
6ef8949
Do not include in the __repr__ method
ondrej-lukas Apr 23, 2025
b1150e3
Do not use size in comparisons and hashes
ondrej-lukas Apr 24, 2025
c16763b
Correctly deserialize all fields
ondrej-lukas Apr 24, 2025
9aeb3a8
Fix tests to work with the content in Data
ondrej-lukas Apr 24, 2025
7a493d2
Create the log in the router when started
ondrej-lukas Apr 24, 2025
4b0361f
Fix missing logfile handelling
ondrej-lukas Apr 24, 2025
f89ec9b
Fix situation of agent not in list when trying to remove it
eldraco Apr 25, 2025
5facdd4
Always have in the conf the required players
eldraco Apr 27, 2025
3d46110
Add util to see the log file better
eldraco Apr 27, 2025
76c16f8
Improve log parser
eldraco Apr 27, 2025
d37a05b
Improve log parser
eldraco Apr 27, 2025
eb02dd3
Fix coordinator to process block action. It was ignored before
eldraco Apr 27, 2025
5fec7d8
Improve the log colorizer
eldraco Apr 27, 2025
fe118cd
Do not start the game until at least one active player joins
ondrej-lukas Apr 28, 2025
2914678
Change temporarirly to global defender yes
eldraco Apr 29, 2025
bd27b3b
Temporary change to 2 agents and without defender
eldraco May 8, 2025
b84591c
Add FP counter
ondrej-lukas May 14, 2025
bbe8527
Lower reward for FP
ondrej-lukas May 14, 2025
fb621ee
Add method for incrementing FP counter
ondrej-lukas May 14, 2025
e482e35
Method for checking benign agents
ondrej-lukas May 20, 2025
6d8b76b
New data structures for FP
ondrej-lukas May 20, 2025
7a17a1b
Add agent id to action execution
ondrej-lukas May 20, 2025
dad4e3d
reset firewall correctly
ondrej-lukas May 20, 2025
2c8f439
method for recording false positives
ondrej-lukas May 20, 2025
05e39ee
record false positives upon firewall block
ondrej-lukas May 20, 2025
ea1db4f
Include BlockIp in the game actions
ondrej-lukas May 20, 2025
17af79f
Use IP objects in the recording of FP
ondrej-lukas May 20, 2025
620be4d
move fp data structure to coordinator
ondrej-lukas May 20, 2025
5ffb75f
Make defender not play with timout
ondrej-lukas May 20, 2025
e4b0b02
add reward to FP
ondrej-lukas May 20, 2025
d9ce8ab
Fix the is_private calls
ondrej-lukas Jun 26, 2025
b705b47
Do not run ruff check in the Agent subrepo
ondrej-lukas Jun 26, 2025
cbb9266
Add test options to pyproject
ondrej-lukas Jun 26, 2025
a75acf0
Split tests for components
ondrej-lukas Jun 26, 2025
4197565
Add test ip from dict
ondrej-lukas Jun 26, 2025
e9e45b5
add missing tests for ip
ondrej-lukas Jun 26, 2025
da1c93f
add missing tests for network
ondrej-lukas Jun 26, 2025
ef64fb0
Remove name from test data
ondrej-lukas Jun 26, 2025
c58ba4e
Add missing tests for action
ondrej-lukas Jun 26, 2025
2c5e7bd
Use fixures
ondrej-lukas Jun 26, 2025
30b7a53
Convert data to fixtures
ondrej-lukas Jun 26, 2025
c5933be
remove redundant class
ondrej-lukas Jun 26, 2025
51bda4a
Add fixtures to service tests
ondrej-lukas Jun 26, 2025
8cb4b03
Add fixtures
ondrej-lukas Jun 26, 2025
e0172c7
use fixtures in gamestate tests
ondrej-lukas Jun 26, 2025
5b1011e
fix typo
ondrej-lukas Jun 26, 2025
4fd5d1e
compoents tests are split into smaller pieces
ondrej-lukas Jun 26, 2025
0e9648c
Add logging for agent peername duplicates
ondrej-lukas Jun 26, 2025
028c045
specify asyncio mode for testing
ondrej-lukas Jun 26, 2025
4dc8bc0
tests for agent server
ondrej-lukas Jun 26, 2025
aacd947
Tests for agent server
ondrej-lukas Jun 26, 2025
42fdf20
Add more tests for agent server
ondrej-lukas Jun 27, 2025
754f9ae
await closing of the writer
ondrej-lukas Jun 27, 2025
61470d4
move global defender tests to the coordintor dir
ondrej-lukas Jun 27, 2025
6d07213
More tests for core methods
ondrej-lukas Jun 27, 2025
73f31ca
add test for game actions
ondrej-lukas Jun 27, 2025
9d71cc1
moved to different file
ondrej-lukas Jun 27, 2025
284e285
Fix the Mocks
ondrej-lukas Jun 27, 2025
fdc0a3c
moved to different test set
ondrej-lukas Jun 27, 2025
6affe58
do not use with pytest for now
ondrej-lukas Jun 27, 2025
299fc5b
Add pytest and ruff parameters
ondrej-lukas Jun 27, 2025
6e40a12
Update readme
ondrej-lukas Jun 27, 2025
33c6014
Update the testig script
ondrej-lukas Jun 27, 2025
022a631
flush the buffer
ondrej-lukas Jun 27, 2025
58dd2dd
renamed file
ondrej-lukas Jun 27, 2025
8dce0d7
Fix ruff warinings
ondrej-lukas Jun 27, 2025
782a9a5
Add info to pyproject
ondrej-lukas Jun 27, 2025
cb17794
correctly wait for csloing of the writer
ondrej-lukas Jun 27, 2025
ccfc53f
remove unused method
ondrej-lukas Jun 27, 2025
51889fb
Solve connection error when agent leaves
ondrej-lukas Jun 27, 2025
c8cdf66
refactoring of method
ondrej-lukas Jun 27, 2025
3dcacf5
Make minimum players=1
ondrej-lukas Jun 27, 2025
1223321
Fix test to match changes in the code
ondrej-lukas Jun 27, 2025
9034c7c
Merge pull request #314 from stratosphereips/reorganize-tests
ondrej-lukas Jun 27, 2025
7c1e49e
replace awaited with called
ondrej-lukas Jun 28, 2025
f982534
move the assertion before writer 1 closes
ondrej-lukas Jun 30, 2025
e316cba
Only remove the queue if it was created by this agent
ondrej-lukas Jun 30, 2025
651a5d4
Fix test to awaited correct async call
ondrej-lukas Jun 30, 2025
ab0d236
Merge pull request #315 from stratosphereips/fix-agent-server-tests
ondrej-lukas Jun 30, 2025
d039b56
Move logging to debug level
ondrej-lukas Jun 30, 2025
bf9c5f6
Update python-checks.yml
ondrej-lukas Jun 30, 2025
bc8b047
Update python-checks.yml
ondrej-lukas Jun 30, 2025
a64c6b2
do not automatically detect manual testfile
ondrej-lukas Jun 30, 2025
d53af74
remove unused torch import
ondrej-lukas Jun 30, 2025
0b0c10f
Update python-checks.yml
ondrej-lukas Jun 30, 2025
e115b9e
add pytest-asyncio to dev options in pyproject
ondrej-lukas Jun 30, 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
4 changes: 2 additions & 2 deletions .github/workflows/python-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.12"]
python-version: ["3.12"]

steps:
- uses: actions/checkout@v3
Expand All @@ -22,7 +22,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install ruff pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f pyproject.toml ]; then pip install .[dev] ; fi
- name: Test with pytest
run: |
tests/run_all_tests.sh
Expand Down
198 changes: 127 additions & 71 deletions AIDojoCoordinator/coordinator.py

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions AIDojoCoordinator/game_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,12 @@ class Data():
"""
owner: str
id: str
size: int = 0
size: int = field(compare=False, hash=False, default=0)
type: str = ""

content: str = field(compare=False, hash=False, repr=False, default_factory=str)

def __hash__(self) -> int:
return hash((self.owner, self.id, self.size, self.type))
return hash((self.owner, self.id, self.type))
@classmethod
def from_dict(cls, data: dict):
return cls(**data)
Expand Down Expand Up @@ -379,7 +380,7 @@ def from_dict(cls, data_dict:dict):
controlled_hosts = {IP(x["ip"]) for x in data_dict["controlled_hosts"]},
known_services = {IP(k):{Service(s["name"], s["type"], s["version"], s["is_local"])
for s in services} for k,services in data_dict["known_services"].items()},
known_data = {IP(k):{Data(v["owner"], v["id"]) for v in values} for k,values in data_dict["known_data"].items()},
known_data = {IP(k):{Data(v["owner"], v["id"], v["size"], v["type"], v["content"]) for v in values} for k,values in data_dict["known_data"].items()},
known_blocks = known_blocks
)
return state
Expand All @@ -396,7 +397,7 @@ def from_json(cls, json_string):
controlled_hosts = {IP(x["ip"]) for x in json_data["controlled_hosts"]},
known_services = {IP(k):{Service(s["name"], s["type"], s["version"], s["is_local"])
for s in services} for k,services in json_data["known_services"].items()},
known_data = {IP(k):{Data(v["owner"], v["id"]) for v in values} for k,values in json_data["known_data"].items()},
known_data = {IP(k):{Data(v["owner"], v["id"], v["size"], v["type"], v["content"]) for v in values} for k,values in json_data["known_data"].items()},
known_blocks = {IP(target_host):{IP(blocked_host) for blocked_host in blocked_hosts} for target_host, blocked_hosts in json_data["known_blocks"].items()}
)
return state
Expand Down
2 changes: 2 additions & 0 deletions AIDojoCoordinator/netsecenv_conf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,12 @@ env:
use_dynamic_addresses: False
use_firewall: True
save_trajectories: False
required_players: 1
rewards:
success: 100
step: -1
fail: -10
false_positive: -5
actions:
scan_network:
prob_success: 1.0
Expand Down
207 changes: 207 additions & 0 deletions AIDojoCoordinator/utils/aidojo_log_colorizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#!/usr/bin/env python3
"""
A simple log processor that colorizes log entries by agent and component,
interprets JSON payloads into readable summaries,
pretty-prints key fields, and highlights actions & agents.

Usage:
python log_processor.py path/to/logfile.log
If no file is given, reads from stdin.
"""
import sys
import re
import json
from itertools import cycle
from rich import print
from rich.console import Console
from rich.text import Text

# Force ANSI colors even when output is piped
console = Console(force_terminal=True, color_system="truecolor")

# Cycle of colors to assign to different agents (background colors to avoid clashes)
# We'll use text in black on these backgrounds so they're distinct from other log colors
COLOR_CYCLE = ["cyan", "magenta", "green", "yellow", "blue", "red"]
agent_colors = {}
color_picker = cycle(COLOR_CYCLE)
agent_names = {} # map ip:port -> agent name

# Regex patterns
timestamp_re = re.compile(r"^(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})")
line_re = re.compile(
r"^(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) (?P<source>\S+) (?P<level>\S+)\s+(?P<msg>.*)$"
)
agent_id_re = re.compile(r"\('(?P<ip>[\d\.]+)', (?P<port>\d+)\)")
agent_reg_re = re.compile(
r"Agent (?P<name>\S+) \(\('(?P<ip>[\d\.]+)', (?P<port>\d+)\)\)"
)
action_re = re.compile(r"ActionType\.[A-Za-z]+")


def get_agent_color(agent_id: str) -> str:
"""Assign or retrieve a consistent background style for an agent."""
if agent_id not in agent_colors:
base = next(color_picker)
agent_colors[agent_id] = f"black on {base}"
return agent_colors[agent_id]


def summarize_json(ts, source_styled, level_styled, prefix: str, parsed: dict):
"""Interpret and display key fields from a JSON payload."""
console.print(f"[bold]{ts}[/bold] ", source_styled, level_styled, f"{prefix}:" )
indent = " "
# Status
status = parsed.get('status')
if status is not None:
console.print(Text(f"{indent}Status: {status}", style="bold green"))

# To agent
to_agent = parsed.get('to_agent')
if to_agent:
console.print(Text(f"{indent}To Agent: {to_agent[0]}:{to_agent[1]}", style="bold cyan"))

# Message payload (optional)
msg_block = parsed.get('message')
if isinstance(msg_block, dict):
text = msg_block.get('message')
if text:
console.print(Text(f"{indent}Message: {text}", style="bold"))
max_steps = msg_block.get('max_steps')
if max_steps is not None:
console.print(f"{indent}Max Steps: {max_steps}")
goal = msg_block.get('goal_description')
if goal:
console.print(f"{indent}Goal: {goal}")
actions = msg_block.get('actions')
if actions:
console.print(f"{indent}Actions: {', '.join(actions)}")
conf = msg_block.get('configuration_hash')
if conf:
console.print(f"{indent}Config Hash: {conf}")

# Observation state: networks, hosts, services, data, blocks
obs = parsed.get('observation', {})
state = obs.get('state', {})
if state:
nets = state.get('known_networks', [])
if nets:
items = [f"{n['ip']}/{n['mask']}" for n in nets]
console.print(f"{indent}Known Networks: {', '.join(items)}")
hosts = state.get('known_hosts', [])
if hosts:
items = [h['ip'] for h in hosts]
console.print(f"{indent}Known Hosts: {', '.join(items)}")
ctrl = state.get('controlled_hosts', [])
if ctrl:
items = [h['ip'] for h in ctrl]
console.print(f"{indent}Controlled Hosts: {', '.join(items)}")
services = state.get('known_services', {})
if services:
for host, svcs in services.items():
names = [s['name'] for s in svcs]
console.print(f"{indent}Services on {host}: {', '.join(names)}")
data = state.get('known_data', {})
if data:
for host, entries in data.items():
ids = [e.get('id') for e in entries]
console.print(f"{indent}Data on {host}: {', '.join(ids)}")
# Known blocks
blocks = state.get('known_blocks', {})
if isinstance(blocks, dict):
ips = list(blocks.keys())
elif isinstance(blocks, list):
ips = [b.get('ip') for b in blocks]
else:
ips = []
if ips:
console.print(f"{indent}Blocked Hosts: {', '.join(ips)}")
else:
console.print(f"{indent}Blocked Hosts: None")

# Reward & End: top-level or under observation
reward = parsed.get('reward', obs.get('reward'))
if reward is not None:
console.print(Text(f"{indent}Reward: {reward}", style="bold magenta"))
end = parsed.get('end', obs.get('end'))
if end is not None:
console.print(Text(f"{indent}End: {end}", style="bold red"))

# End Reason: after End
info = parsed.get('info', {})
obs_info = obs.get('info', {})
end_reason = info.get('end_reason') or obs_info.get('end_reason')
if end_reason:
console.print(Text(f"{indent}End Reason: {end_reason}", style="bold yellow"))


def process_line(line: str):
raw = line.rstrip("\n")
m = line_re.match(raw)
if not m:
print(raw)
return

ts = m.group('ts')
source = m.group('source')
level = m.group('level')
msg = m.group('msg').strip()

# Capture agent registration
reg = agent_reg_re.search(msg)
if reg:
aid = f"{reg.group('ip')}:{reg.group('port')}"
agent_names[aid] = reg.group('name')

# Style source
if 'GameCoordinator' in source:
source_styled = Text(source, style='bold red')
elif 'AgentServer' in source:
source_styled = Text(source, style='bold blue')
else:
source_styled = Text(source, style='bold white')

# Style level
level_style = 'dim white' if level == 'INFO' else 'bold red'
level_styled = Text(level, style=level_style)

# Highlight actions
msg = action_re.sub(lambda m: f"[bold magenta]{m.group(0)}[/bold magenta]", msg)

# Annotate agents
def repl_agent(m):
aid = f"{m.group('ip')}:{m.group('port')}"
style = get_agent_color(aid)
name = agent_names.get(aid)
label = f"{name} {aid}" if name else aid
# Use background style for agent label
return f"[{style}]{label}[/{style}]"
msg_markup = agent_id_re.sub(repl_agent, msg)

# Detect JSON and interpret
if '{' in msg and (msg.strip().startswith('{') or ': {' in msg):
idx = msg.find('{')
prefix = msg[:idx].rstrip(': ')
json_part = msg[idx:]
try:
parsed = json.loads(json_part)
summarize_json(ts, source_styled, level_styled, prefix, parsed)
return
except json.JSONDecodeError:
pass

# Default print
console.print(Text(ts, style='bold'), source_styled, level_styled, Text.from_markup(msg_markup))


def main():
if len(sys.argv) > 1:
with open(sys.argv[1], 'r') as f:
for line in f:
process_line(line)
else:
for line in sys.stdin:
process_line(line)


if __name__ == '__main__':
main()
18 changes: 17 additions & 1 deletion AIDojoCoordinator/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from AIDojoCoordinator.scenarios import smaller_scenario_configuration
from AIDojoCoordinator.scenarios import tiny_scenario_configuration
from AIDojoCoordinator.scenarios import three_net_scenario
from AIDojoCoordinator.game_components import IP, Data, Network, Service, GameState, Action, Observation
from AIDojoCoordinator.game_components import IP, Data, Network, Service, GameState, Action, Observation, ActionType
import netaddr
import logging
import csv
Expand Down Expand Up @@ -114,6 +114,22 @@ def observation_as_dict(observation:Observation)->dict:
}
return observation_dict

def parse_log_content(log_content:str)->list:
try:
logs = []
data = json.loads(log_content)
for item in data:
ip = IP(item["source_host"])
action_type = ActionType.from_string(item["action_type"])
logs.append({"source_host":ip, "action_type":action_type})
return logs
except json.JSONDecodeError as e:
print(f"Error decoding JSON: {e}")
return None
except TypeError as e:
print(f"Error decoding JSON: {e}")
return None

class ConfigParser():
"""
Class to deal with the configuration file
Expand Down
Loading