Skip to main content
  1. Documentation/
  2. Architecture/

Message Pipeline

Table of Contents
The message pipeline processes all agent communication through a four-stage chain.

Pipeline Stages
#

graph LR
    Raw[Raw Bytes] --> Magic[Magic Routing]
    Magic --> Crypto[Crypto Layer]
    Crypto --> Codec[Protocol Codec]
    Codec --> Handler[Message Handler]
    Handler --> Response[Response]
    Response --> Codec2[Codec Encode]
    Codec2 --> Crypto2[Crypto Encrypt]
    Crypto2 --> Wire[Wire Bytes]

1. Magic Routing
#

The first 4 bytes of every message identify the agent package. The pipeline uses these magic bytes to select the correct CryptoProvider and ProtocolCodec.

This allows multiple agent types to coexist on a single listener — each agent package registers unique magic bytes, and the pipeline routes accordingly.

2. Crypto Layer
#

The CryptoProvider handles:

  • Registration: Accept the agent’s public key, generate a server keypair, derive a shared session key via ECDH
  • Session messages: Decrypt inbound data and encrypt outbound responses using AES-256-GCM with the session key

Session tokens (16 bytes) identify the agent’s session. A null token (all zeros) indicates a registration message.

3. Protocol Codec
#

The ProtocolCodec translates between wire bytes and internal message dicts:

  • Decode: Wire bytes → Python dict
  • Encode: Python dict → Wire bytes

The dev agent codec uses JSON + zlib compression.

4. Message Handler
#

The handler processes the decoded message based on its type field:

TypeAction
registerCreate agent record, assign ID and session token
checkinUpdate last-seen timestamp, return queued tasks (and relay responses if applicable)
task_resultStore result, trigger credential extraction and file transfer processing
task_assignmentDeliver task payload to agent (server → agent)
beacon_configUpdate beacon interval and jitter
killTerminate the agent
survey_resultUpdate agent metadata (hostname, OS, IPs, etc.)
key_rotation_requestServer requests session key rotation
key_rotation_responseAgent acknowledges key rotation
relay_forwardForward traffic from interior agents via P2P relay; auto-discovers relay topology
plugin_messageRoute plugin-specific messages to registered handlers

Streaming Results
#

Tasks can stream results without completing — the task enters a running state and produces multiple results over several check-in cycles. This is used by long-running managed modules (keyloggers, port scanners) that send incremental output.

Relay Response Queueing
#

When agents relay traffic for interior agents, responses are buffered in the pipeline and returned to the relay agent on its next check-in. The relay agent then forwards them to the appropriate interior agent.

Wire Format
#

1
2
3
4
5
+--------+----------+------------------+
| Magic  | Session  | Payload          |
| 4 bytes| Token    | (variable)       |
|        | 16 bytes |                  |
+--------+----------+------------------+

Total header: 20 bytes fixed, followed by the encrypted and encoded payload.

Registration Sequence
#

sequenceDiagram
    participant Agent
    participant Listener
    participant Pipeline
    participant DB

    Agent->>Listener: magic + null_token + public_key + register_msg
    Listener->>Pipeline: on_message(raw_bytes)
    Pipeline->>Pipeline: Route by magic bytes
    Pipeline->>Pipeline: Generate keypair, derive session key
    Pipeline->>DB: Create agent record, store session key
    Pipeline->>Listener: magic + session_token + server_public_key + response
    Listener->>Agent: Response bytes

Check-in Sequence
#

sequenceDiagram
    participant Agent
    participant Pipeline
    participant DB

    Agent->>Pipeline: magic + session_token + encrypt(checkin)
    Pipeline->>Pipeline: Lookup session by token
    Pipeline->>Pipeline: Decrypt with session key
    Pipeline->>Pipeline: Decode with codec
    Pipeline->>DB: Update last_seen, fetch pending tasks
    Pipeline->>Agent: encrypt(codec(tasks))

Anti-Replay Protection
#

The dev agent includes a monotonic counter in every encrypted message. The server tracks the last seen counter per session and rejects messages with a counter value less than or equal to the previously seen value.