Overview
Sessions are the fundamental unit of agent tracking in the C2 framework. Each agent receives a unique session ID during CHECKIN, which is used to identify and route all subsequent communications. Sessions persist in the database and survive server restarts.Session Lifecycle
A session progresses through the following states:- Creation: Agent sends CHECKIN, server assigns a UUID session ID
- Active: Agent beacons regularly, executes tasks, reports results
- Inactive: Agent stops beaconing (network loss, system shutdown, etc.)
- Deactivated: Operator explicitly kills the session
- Terminated: Agent receives TERMINATE signal and exits
- Session Creation
- Last Seen Update
- Session Deactivation
server/session_manager.py
Session State
TheSessionState dataclass holds all session metadata:
server/session_manager.py
session_id: Unique UUID assigned by serverhostname: System hostname from agentusername: Current user running the agentos: Operating system information (name, release, version)agent_ver: Agent version string (e.g., “1.0.0”)first_seen: Unix timestamp when session was createdlast_seen: Unix timestamp of most recent beaconjitter_pct: Beacon interval jitter percentage (0-100)active: Boolean flag indicating if session is active
All timestamps are stored as Unix epoch floats (seconds since 1970-01-01 UTC) to simplify arithmetic and avoid timezone issues.
SessionManager
TheSessionManager class provides the API for managing sessions:
server/session_manager.py
- In-Memory State: Sessions are stored in a Python dict for O(1) lookups
- Database Sync: Every mutation is immediately persisted to SQLite
- Thread Safety: All operations use an async lock to prevent race conditions
- Restore on Startup: Active sessions are loaded from DB when server starts
Core Operations
Create Session
Called when an agent sends a CHECKIN message:SessionState, stores in memory and database, and returns the session ID to the agent.
Get Session
Retrieve session metadata by ID:SessionState object or None if not found.
Update Last Seen
Called on every beacon to track agent activity:server/server_main.py
last_seen timestamp to the current time.
List Sessions
Retrieve all sessions ordered by recency:server/session_manager.py
SessionState objects sorted by last_seen (most recent first).
Deactivate Session
Mark a session as inactive (triggered by operator via CLI):active = False in memory and database. The agent receives a TERMINATE signal on its next beacon.
CHECKIN Handler
The server’s CHECKIN handler creates new sessions:server/server_main.py
session_id, which the agent stores and includes in all subsequent messages.
Session Validation
Every beacon handler validates the session before processing:server/server_main.py
- Check if
session_idexists in memory (session_mgr.get_session()) - Check if session is active in database (
db.get_session()) - If inactive, send TERMINATE signal to agent
- If active, update
last_seentimestamp and continue processing
The server checks both in-memory state and database to ensure consistency. This handles edge cases where the in-memory state might be out of sync due to concurrent operations.
Agent-Side Session Tracking
The agent stores its session ID after successful CHECKIN:agent/beacon.py
session_id in every subsequent message (TASK_PULL, TASK_RESULT, etc.).
Logger Tagging:
After CHECKIN, the agent updates its logger to include the session ID in all log entries. This makes correlation easier when analyzing logs from multiple agents.
After CHECKIN, the agent updates its logger to include the session ID in all log entries. This makes correlation easier when analyzing logs from multiple agents.
Persistence and Recovery
Sessions are persisted in the SQLite database and restored on server startup:server/session_manager.py
- Server starts and calls
restore_from_db() - All active sessions are loaded from the database
- In-memory
_sessionsdict is populated - Agents continue beaconing with their existing session IDs
- Server recognizes the session IDs and resumes normal operation
Seamless Recovery:
Agents don’t need to re-checkin after server restarts. The session persistence mechanism ensures continuity without operator intervention.
Agents don’t need to re-checkin after server restarts. The session persistence mechanism ensures continuity without operator intervention.
Session Deactivation Flow
When an operator kills a session:- Operator CLI: Calls
api.kill_session(session_id) - API Handler: Calls
session_mgr.deactivate_session(session_id, db) - SessionManager: Sets
active = Falsein memory and database - Database: Updates
activecolumn to 0 - Next Beacon: Agent sends TASK_PULL
- Server Validation: Detects
active = Falsein database - TERMINATE Signal: Server responds with MSG_TERMINATE
- Agent Shutdown: Agent logs the termination and calls
sys.exit(0)
server/server_main.py
The TERMINATE signal is sent on the next beacon, not immediately. This means there’s a delay of up to one beacon interval before the agent shuts down.
Session Activity Tracking
Thelast_seen timestamp enables operators to identify inactive or lost agents:
- Green: Beaconed within last 2x beacon interval
- Yellow: Beaconed within last 10 minutes
- Red: No beacon for >10 minutes
Concurrency and Thread Safety
TheSessionManager uses an async lock to prevent race conditions:
server/session_manager.py
Why asyncio.Lock instead of threading.Lock?
The server uses FastAPI with uvicorn, which runs on an asyncio event loop. All I/O operations are async, so we use
The server uses FastAPI with uvicorn, which runs on an asyncio event loop. All I/O operations are async, so we use
asyncio.Lock for async-compatible mutual exclusion.Database Schema
Sessions are stored in thesessions table:
session_id: UUID as TEXT (primary key)hostname: Agent system hostnameusername: Agent process usernameos: OS information stringagent_ver: Agent versionfirst_seen: Unix timestamp (REAL) when session was createdlast_seen: Unix timestamp (REAL) of most recent beaconjitter_pct: Beacon jitter percentage (0-100)active: Boolean as INTEGER (1 = active, 0 = inactive)
SQLite doesn’t have native boolean or timestamp types, so we use INTEGER for booleans (0/1) and REAL for Unix timestamps (floating point seconds since epoch).
Best Practices
Monitoring Sessions
Operators should regularly check session status:Handling Lost Agents
If an agent stops beaconing:- Check network connectivity between agent and server
- Check if the agent process is still running
- Check agent logs for errors or exceptions
- Consider the
last_seentimestamp to determine if the agent is truly lost
Cleaning Up Old Sessions
Inactive sessions remain in the database indefinitely. Operators should periodically clean up old sessions:A future enhancement could add automatic session expiration based on inactivity threshold.