Overview
SessionManager holds all active agent sessions in memory for fast access, synchronized to the database on every mutation. It provides thread-safe operations using asyncio locks.
Source: server/session_manager.py
SessionState Dataclass
@dataclass
class SessionState :
session_id: str
hostname: str
username: str
os: str
agent_ver: str
first_seen: float
last_seen: float
jitter_pct: int
active: bool = True
Represents the state of a single agent session.
Unique UUID for the session
Current username on agent system
Operating system version (e.g., “Windows 10 22H2”)
Agent version string (e.g., “1.0.0”)
Unix timestamp when agent first checked in
Unix timestamp of last activity (beacon, heartbeat)
Beacon jitter percentage (0-100)
Whether session is active. Deactivated sessions receive TERMINATE message.
SessionManager Class
Constructor
Initializes empty session manager with asyncio lock for thread safety.
Internal State:
self ._sessions: dict[ str , SessionState] = {}
self ._lock = asyncio.Lock()
Methods
create_session()
async def create_session ( self , payload : dict , db : Database) -> str
Create a new in-memory SessionState, persist to DB, return session_id.
Agent check-in data containing:
hostname: Agent hostname
username: Current username
os: Operating system version
agent_ver: Agent version string
jitter_pct: Beacon jitter percentage (optional, defaults to 0)
Database instance for persistence
Newly created session_id (UUID)
Example:
from server.session_manager import SessionManager
from server.storage import Database
async with Database() as db:
mgr = SessionManager()
payload = {
'hostname' : 'VICTIM-PC' ,
'username' : 'jdoe' ,
'os' : 'Windows 10 22H2' ,
'agent_ver' : '1.0.0' ,
'jitter_pct' : 20
}
session_id = await mgr.create_session(payload, db)
print ( f "Created session: { session_id } " )
# Output: Created session: 550e8400-e29b-41d4-a716-446655440000
Behavior:
Generates new UUID for session_id
Sets first_seen and last_seen to current time
Defaults jitter_pct to 0 if not provided
Stores session in memory with lock
Persists to database
Logs creation event
get_session()
async def get_session ( self , session_id : str ) -> SessionState | None
Return the in-memory SessionState for session_id, or None if not found.
UUID of the session to retrieve
SessionState object if found, None otherwise
Example:
session = await mgr.get_session( '550e8400-e29b-41d4-a716-446655440000' )
if session:
print ( f "Hostname: { session.hostname } " )
print ( f "Last seen: { session.last_seen } " )
print ( f "Active: { session.active } " )
else :
print ( "Session not found" )
Performance: O(1) dictionary lookup with async lock
update_last_seen()
async def update_last_seen ( self , session_id : str , db : Database) -> None
Update last_seen in memory and persist to DB.
UUID of the session to update
Database instance for persistence
Example:
# Called on every beacon/heartbeat
await mgr.update_last_seen(session_id, db)
Behavior:
Updates session.last_seen to time.time()
Only updates if session exists in memory
Persists change to database
Silent no-op if session not found
list_sessions()
async def list_sessions ( self ) -> list[SessionState]
Return all in-memory sessions ordered by last_seen descending.
List of SessionState objects sorted by most recent activity first
Example:
sessions = await mgr.list_sessions()
for session in sessions:
status = "Active" if session.active else "Inactive"
print ( f " { session.hostname } ( { session.username } ) - { status } " )
print ( f " Last seen: { session.last_seen } " )
print ( f " Session ID: { session.session_id } " )
Output:
VICTIM-PC-2 (bob) - Active
Last seen: 1710177845.23
Session ID: 660e8400-e29b-41d4-a716-446655440000
VICTIM-PC (jdoe) - Inactive
Last seen: 1710177800.12
Session ID: 550e8400-e29b-41d4-a716-446655440000
Use Cases:
CLI sessions command
Web UI session listing
Monitoring dashboards
deactivate_session()
async def deactivate_session ( self , session_id : str , db : Database) -> None
Mark session inactive in memory and persist to DB.
UUID of the session to deactivate
Database instance for persistence
Example:
# Operator kills session via CLI
await mgr.deactivate_session(session_id, db)
# Next beacon from this agent will receive TERMINATE message
Behavior:
Sets session.active = False in memory
Persists to database (sets active=0)
Logs deactivation event
Agent receives TERMINATE on next beacon
Silent no-op if session not found
Note: Deactivated sessions remain in memory and database for audit/historical purposes.
restore_from_db()
async def restore_from_db ( self , db : Database) -> None
Reload active sessions from DB into memory on server restart.
Database instance to restore from
Example:
# Called during server startup
mgr = SessionManager()
await mgr.restore_from_db(db)
logger.info( 'sessions restored from DB' , extra = { 'count' : len (mgr._sessions)})
Behavior:
Fetches all session rows from database
Only restores sessions where active=1
Reconstructs SessionState objects in memory
Logs count of restored sessions
Use Case: Server restart without losing active agent sessions
Usage Patterns
Complete Session Lifecycle
async with Database() as db:
mgr = SessionManager()
# Server startup: restore existing sessions
await mgr.restore_from_db(db)
# Agent checks in
payload = {
'hostname' : 'VICTIM-PC' ,
'username' : 'jdoe' ,
'os' : 'Windows 10 22H2' ,
'agent_ver' : '1.0.0' ,
'jitter_pct' : 20
}
session_id = await mgr.create_session(payload, db)
# Agent beacons periodically
await mgr.update_last_seen(session_id, db)
# Operator lists sessions
sessions = await mgr.list_sessions()
for s in sessions:
if s.active:
print ( f "Active: { s.hostname } " )
# Operator kills session
await mgr.deactivate_session(session_id, db)
# Next beacon: server checks session status
session = await mgr.get_session(session_id)
if not session.active:
# Send TERMINATE message to agent
pass
Thread Safety
All methods use async with self._lock for thread-safe access to _sessions dict:
async with self ._lock:
self ._sessions[session_id] = state
This ensures safe concurrent access from multiple beacon requests.
Integration Points
Server Main
Used in server_main.py beacon handlers:
from server.session_manager import SessionManager
session_mgr = SessionManager()
# Check-in handler
session_id = await session_mgr.create_session(payload, db)
# Task pull handler
session = await session_mgr.get_session(session_id)
if not session:
return None # Invalid session
await session_mgr.update_last_seen(session_id, db)
CLI Commands
Used in cli/session_commands.py:
# List active sessions
sessions = await session_mgr.list_sessions()
for session in sessions:
if session.active:
print_session(session)
# Kill session
await session_mgr.deactivate_session(session_id, db)
Testing
Self-Test Suite
Run built-in tests:
python -m server.session_manager
Test Coverage:
create_session generates valid UUID
get_session returns correct state
get_session returns None for unknown ID
update_last_seen advances timestamp
list_sessions sorts by last_seen descending
deactivate_session sets active flag
restore_from_db only loads active sessions
Output:
Running session_manager self-test...
[OK] create_session
[OK] get_session
[OK] get_session returns None for unknown session_id
[OK] update_last_seen
[OK] list_sessions
[OK] deactivate_session
[OK] restore_from_db
All session_manager self-tests passed.
Logging
Structured logging with contextual fields:
logger.info( 'session created' ,
extra = { 'session_id' : session_id, 'hostname' : state.hostname})
Events:
Session created
Session deactivated
Sessions restored from DB
All active sessions held in memory. For 10,000 agents, SessionState objects consume ~2-3 MB RAM.
Single lock protects _sessions dict. Read operations like get_session are fast. High beacon rates may see contention.
Every mutation (create, update_last_seen, deactivate) writes to DB. Uses async I/O for non-blocking operations.
Server Main FastAPI beacon endpoint integration
Database Session persistence layer
CommandQueue Task management per session
Session Commands CLI session management