Skip to main content

Overview

Module: common/utils.py The C2 Framework uses a custom exception hierarchy to distinguish framework errors from standard Python exceptions. All framework modules raise these typed exceptions instead of built-in exceptions, making error handling more precise and preventing accidental catches of unrelated errors.

Exception Hierarchy

C2Error (base)
├── CryptoError
├── ProtocolError
├── TransportError
└── EnvironmentError

Base Exception

C2Error

class C2Error(Exception)
Base exception for all framework errors. Catch this in the beacon loop to handle any framework failure without crashing the agent process. Usage:
from common.utils import C2Error

try:
    # Framework operation
    pass
except C2Error as e:
    logger.error('framework error', extra={'reason': str(e)})
    # Handle gracefully without crashing

Specialized Exceptions

CryptoError

class CryptoError(C2Error)
Raised by common/crypto.py on any cryptographic failure. Covers:
  • AES-GCM authentication tag verification failure (tampered ciphertext)
  • Invalid key length
  • Invalid nonce length
  • Any internal cryptography library error
Raw exception messages from the cryptography library are logged server-side only for security.
Example:
from common.utils import CryptoError
from common.crypto import decrypt

try:
    plaintext = decrypt(ciphertext, nonce, key)
except CryptoError as e:
    logger.warning('decryption failed', extra={'reason': str(e)})
    # Could indicate tampering or wrong key

ProtocolError

class ProtocolError(C2Error)
Raised by common/message_format.py on any protocol-level failure. Covers:
  • Wrong magic bytes in envelope header
  • Unsupported protocol version
  • Truncated or malformed envelope
  • Invalid JSON in decrypted payload
  • Missing required fields in payload dict
Example:
from common.utils import ProtocolError
from common import message_format as mf

try:
    payload = mf.unpack(raw_body, session_key)
except ProtocolError as e:
    logger.warning('protocol error', extra={'reason': str(e)})
    return JSONResponse(status_code=400, content={'error': 'bad request'})

TransportError

class TransportError(C2Error):
    def __init__(self, message: str, status_code: int = None)
Raised by transport/http_transport.py on any network failure. Covers:
  • Connection refused or timed out
  • HTTP 4xx or 5xx response from server
  • Host not in ALLOWED_HOSTS (hard safety control)
  • TLS certificate verification failure
Attributes:
status_code
int | None
HTTP status code if the error came from an HTTP response
Example:
from common.utils import TransportError
from transport.http_transport import send_beacon

try:
    response = send_beacon(endpoint, packed_message)
except TransportError as e:
    logger.warning('transport error', extra={
        'reason': str(e),
        'status_code': e.status_code,
    })
    # Trigger backoff retry logic

EnvironmentError

class EnvironmentError(C2Error)
Raised by agent/environment_checks.py when the lab gate fails. Covers:
  • LAB_MODE environment variable not set to ‘1’
  • Target host not in ALLOWED_HOSTS
This exception causes the agent to exit immediately with sys.exit(1) to prevent accidental deployment outside the lab.
Example:
from common.utils import EnvironmentError
from agent.environment_checks import check_lab_environment

try:
    check_lab_environment()
except EnvironmentError as e:
    logger.error('environment check failed', extra={'reason': str(e)})
    sys.exit(1)

Error Handling Patterns

Beacon Loop Error Handling

from common.utils import C2Error, TransportError, ProtocolError

while True:
    try:
        # Send beacon
        response = _send(pull_payload, self._key)
        
    except TransportError as e:
        # Network failure - back off and retry
        logger.warning('transport error', extra={'reason': str(e)})
        self._backoff_sleep(reason=str(e))
        
    except ProtocolError as e:
        # Protocol error - log and continue
        logger.error('protocol error', extra={'reason': str(e)})
        
    except C2Error as e:
        # Any other framework error
        logger.error('framework error', extra={'reason': str(e)})

Server Request Handling

from common.utils import CryptoError, ProtocolError

try:
    session_key = get_session_key()
    payload = mf.unpack(raw_body, session_key)
    
except (ProtocolError, CryptoError) as e:
    logger.warning('unpack failed', extra={
        'error_type': type(e).__name__,
        'error_msg': str(e),
    })
    return JSONResponse(status_code=400, content={'error': 'bad request'})

Best Practices

Catch Specific Exceptions

Catch the most specific exception type possible for targeted error handling

Always Log Context

Include relevant context in structured logs (session_id, task_id, etc.)

Don't Expose Details

Return generic error messages to clients; log full details server-side only

Use C2Error for Catch-All

Catch C2Error as a fallback to handle any framework error gracefully

Build docs developers (and LLMs) love