Infrastructure·Security & Secrets·Advanced

Withdrawal Guard

Build an automated withdrawal monitoring guard service that continuously watches for any unauthorized or anomalous cryptocurrency withdrawal attempts from connected exchange accounts, immediately triggering multi-channel alerts and optionally halting all trading activity when suspicious withdrawal activity is algorithmically detected.

infrastructuresecurity

Infrastructure Security: Blocking Unauthorized Withdrawals

This notebook explores fundamental concepts and practical implementations for enhancing infrastructure security, specifically focusing on mechanisms to prevent and detect unauthorized financial withdrawals or data exfiltrations. In today's digital landscape, protecting assets from malicious actors is paramount. This guide provides a hands-on approach to building a robust defense system.

We will cover the following key concepts:

ConceptDescriptionImplementation Strategy
Account State ManagementMaintaining a secure and consistent representation of user accounts and their balances.Using dictionaries to model user accounts and create_account function to initialize them.
Withdrawal SimulationSimulating financial transactions to test security measures.perform_withdrawal function with checks for sufficient funds.
Anomaly DetectionIdentifying unusual patterns or behaviors that may indicate a security breach.Simple rule-based detection for unusually large withdrawals, multiple failed attempts, or new location-based withdrawals.
Withdrawal BlockingImplementing mechanisms to prevent suspicious transactions from completing.block_withdrawal function to prevent further processing of a transaction.
Audit LoggingRecording all significant events for forensic analysis and compliance.generate_audit_log function to store transaction and security event details.
Rate LimitingControlling the frequency of withdrawal attempts to prevent brute-force attacks.Using deque to track recent attempts and check_rate_limit function to enforce limits.
Geographic FencingRestricting transactions based on location data to mitigate risks from unknown or suspicious origins.check_geographic_fencing function comparing current location with allowed/trusted locations.
Retry MechanismsImplementing safe retry logic with exponential backoff for external service calls.try_with_exponential_backoff decorator.

Dependency Installation

This section installs all necessary Python packages. Run this cell to ensure all dependencies are met.

[22]
!pip install pandas numpy
Requirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2)
Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (2.0.2)
Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas) (2026.2)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)

Library Imports

This section imports all necessary libraries. Standard Python libraries are imported first, followed by third-party libraries.

[23]
import collections
import datetime
import functools
import logging
import random
import time
import uuid
from collections import deque

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

Core Functions

This section defines the core functions that implement the security mechanisms. Each function is presented with a detailed markdown header explaining its purpose and specifications.

Function Name: create_account

This function initializes a new user account with a given account_id and an initial balance. It sets up the account's initial state, including a unique ID, current balance, creation timestamp, and an empty list for transaction history. This function serves as the foundation for managing account states securely.

Parameters:

  • account_id (str): A unique identifier for the account.
  • initial_balance (float): The starting balance for the account.

Returns:

  • dict: A dictionary representing the newly created account's state.
[24]
def create_account(account_id: str, initial_balance: float) -> dict:
    """
    Initializes a new user account with a given ID and initial balance.

    Parameters
    ----------
    account_id : str
        A unique identifier for the account.
    initial_balance : float
        The starting balance for the account.

    Returns
    -------
    dict
        A dictionary representing the newly created account's state.

    Examples
    --------
    >>> account = create_account("user123", 1000.0)
    >>> account['balance']
    1000.0
    """
    if not isinstance(account_id, str) or not account_id:
        logger.error("Invalid account_id provided. Must be a non-empty string.")
        raise ValueError("Account ID must be a non-empty string.")
    if not isinstance(initial_balance, (int, float)) or initial_balance < 0:
        logger.error("Invalid initial_balance provided. Must be a non-negative number.")
        raise ValueError("Initial balance must be a non-negative number.")

    account_state = {
        "account_id": account_id,
        "balance": initial_balance,
        "created_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
        "transaction_history": [],  # List of transaction dictionaries
        "failed_attempts": deque(maxlen=5), # Track recent failed attempts for rate limiting
        "trusted_locations": ['New York', 'London'], # Example trusted locations
        "last_known_location": None # For geographic fencing
    }
    logger.info(f"Account '{account_id}' created with initial balance {initial_balance}.")
    return account_state

Function Name: perform_withdrawal

This function simulates a withdrawal from a user account. It takes the current account state, the withdrawal amount, and transaction details as input. Before processing the withdrawal, it performs several critical checks:

  • Sufficient Funds Check: Ensures the account has enough balance for the withdrawal.
  • Transaction Logging: Records the transaction details, whether successful or failed, into the account's history.

This function is a core component for testing the security measures by simulating realistic financial operations.

Parameters:

  • account_state (dict): The current state of the user account.
  • amount (float): The amount to be withdrawn.
  • transaction_id (str, optional): A unique identifier for the transaction. If not provided, a UUID will be generated.
  • location (str, optional): The geographic location from which the withdrawal is initiated.

Returns:

  • dict: The updated account state after attempting the withdrawal.
  • bool: True if the withdrawal was successful, False otherwise.
[25]
def perform_withdrawal(account_state: dict, amount: float, transaction_id: str = None, location: str = None) -> tuple[dict, bool]:
    """
    Simulates a withdrawal from an account, including checks for sufficient funds and logging.

    Parameters
    ----------
    account_state : dict
        The current state dictionary of the account.
    amount : float
        The amount of money to withdraw.
    transaction_id : str, optional
        A unique identifier for the transaction. If None, a UUID will be generated.
    location : str, optional
        The geographic location from which the withdrawal is initiated.

    Returns
    -------
    tuple[dict, bool]
        The updated account state and a boolean indicating if the withdrawal was successful.

    Examples
    --------
    >>> account = create_account("user456", 500.0)
    >>> updated_account, success = perform_withdrawal(account, 100.0, transaction_id="tx_1")
    >>> success
    True
    >>> updated_account['balance']
    400.0
    """
    if transaction_id is None:
        transaction_id = str(uuid.uuid4())

    transaction_record = {
        "transaction_id": transaction_id,
        "type": "withdrawal",
        "amount": amount,
        "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
        "location": location,
        "status": "pending"
    }

    if not isinstance(amount, (int, float)) or amount <= 0:
        logger.warning(f"[{transaction_id}] Invalid withdrawal amount: {amount}. Must be a positive number.")
        transaction_record["status"] = "failed: invalid amount"
        account_state["transaction_history"].append(transaction_record)
        return account_state, False

    if account_state["balance"] < amount:
        logger.warning(f"[{transaction_id}] Withdrawal failed for account '{account_state['account_id']}': Insufficient funds. Requested: {amount}, Available: {account_state['balance']}.")
        transaction_record["status"] = "failed: insufficient funds"
        account_state["transaction_history"].append(transaction_record)
        account_state["failed_attempts"].append({"timestamp": transaction_record["timestamp"], "type": "insufficient_funds"})
        return account_state, False

    # Simulate potential delays or external system calls
    time.sleep(random.uniform(0.05, 0.2))

    account_state["balance"] -= amount
    transaction_record["status"] = "success"
    account_state["transaction_history"].append(transaction_record)
    account_state["last_known_location"] = location # Update last known location

    logger.info(f"[{transaction_id}] Withdrawal successful for account '{account_state['account_id']}'. Amount: {amount}, New balance: {account_state['balance']}.")
    return account_state, True

Function Name: check_rate_limit

This function implements a rate-limiting mechanism for withdrawal attempts. It uses a deque to track recent failed attempts and determines if an account has exceeded a predefined number of attempts within a specified time window. If the limit is exceeded, it prevents further attempts for a cooldown period, thereby mitigating brute-force attacks or suspicious repetitive actions.

Parameters:

  • account_state (dict): The current state of the user account, containing a failed_attempts deque.
  • max_attempts (int): The maximum number of failed attempts allowed within the time_window_seconds.
  • time_window_seconds (int): The time window (in seconds) during which max_attempts are counted.
  • cooldown_seconds (int): The duration (in seconds) for which attempts are blocked after the limit is exceeded.

Returns:

  • tuple[dict, bool]: The updated account state and a boolean indicating True if the attempt is allowed, False if it's rate-limited.
[26]
def check_rate_limit(account_state: dict, max_attempts: int = 3, time_window_seconds: int = 300, cooldown_seconds: int = 60) -> tuple[dict, bool]:
    """
    Checks if an account has exceeded the rate limit for failed attempts and applies a cooldown if necessary.

    Parameters
    ----------
    account_state : dict
        The current state dictionary of the account.
    max_attempts : int, optional
        The maximum number of failed attempts allowed within the time window.
        Defaults to 3.
    time_window_seconds : int, optional
        The time window (in seconds) for counting failed attempts. Defaults to 300 (5 minutes).
    cooldown_seconds : int, optional
        The duration (in seconds) for which attempts are blocked after exceeding the limit.
        Defaults to 60 (1 minute).

    Returns
    -------
    tuple[dict, bool]
        The updated account state and a boolean indicating if the attempt is allowed.

    Examples
    --------
    >>> account = create_account("user789", 1000.0)
    >>> # Simulate failed attempts
    >>> account['failed_attempts'].append({'timestamp': (datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(seconds=10)).isoformat(), 'type': 'auth_fail'})
    >>> account['failed_attempts'].append({'timestamp': (datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(seconds=5)).isoformat(), 'type': 'auth_fail'})
    >>> updated_account, allowed = check_rate_limit(account, max_attempts=2, time_window_seconds=60)
    >>> allowed # Should be False if limit exceeded
    False
    """
    current_time = datetime.datetime.now(datetime.timezone.utc)
    recent_failed_attempts = deque([
        attempt for attempt in account_state["failed_attempts"]
        if (current_time - datetime.datetime.fromisoformat(attempt["timestamp"]).replace(tzinfo=datetime.timezone.utc)).total_seconds() < time_window_seconds
    ], maxlen=account_state["failed_attempts"].maxlen)

    account_state["failed_attempts"] = recent_failed_attempts # Update deque to remove old attempts

    if len(recent_failed_attempts) >= max_attempts:
        last_attempt_time = datetime.datetime.fromisoformat(recent_failed_attempts[-1]["timestamp"]).replace(tzinfo=datetime.timezone.utc)
        if (current_time - last_attempt_time).total_seconds() < cooldown_seconds:
            logger.warning(f"Account '{account_state['account_id']}' is rate-limited. Too many failed attempts recently.")
            return account_state, False
        else:
            # Cooldown period has passed, clear attempts for a fresh start
            logger.info(f"Account '{account_state['account_id']}' cooldown period expired. Clearing failed attempts.")
            account_state["failed_attempts"].clear()

    logger.debug(f"Account '{account_state['account_id']}' is within rate limits. Failed attempts: {len(recent_failed_attempts)}.")
    return account_state, True

Function Name: check_geographic_fencing

This function assesses if a withdrawal request originates from a trusted geographic location. It compares the transaction's location with a list of trusted_locations stored in the account_state. This mechanism helps prevent unauthorized withdrawals from unusual or high-risk geographical areas.

Parameters:

  • account_state (dict): The current state of the user account, containing a list of trusted_locations.
  • current_location (str): The geographic location from which the withdrawal is being attempted.

Returns:

  • tuple[dict, bool]: The updated account state and a boolean indicating True if the current_location is trusted or unknown, False if it's explicitly untrusted.
[27]
def check_geographic_fencing(account_state: dict, current_location: str) -> tuple[dict, bool]:
    """
    Checks if the current withdrawal location is within the account's trusted geographic areas.

    Parameters
    ----------
    account_state : dict
        The current state dictionary of the account.
    current_location : str
        The geographic location from which the withdrawal is initiated.

    Returns
    -------
    tuple[dict, bool]
        The updated account state and a boolean indicating if the location is trusted (True) or untrusted (False).

    Examples
    --------
    >>> account = create_account("user_geo", 1000.0)
    >>> account['trusted_locations'] = ['New York', 'London']
    >>> updated_account, allowed = check_geographic_fencing(account, "New York")
    >>> allowed
    True
    >>> updated_account, allowed = check_geographic_fencing(account, "Moscow")
    >>> allowed
    False
    """
    if current_location is None or current_location.strip() == "":
        logger.warning(f"Account '{account_state['account_id']}': Withdrawal attempt from unknown location. Allowing by default but logging.")
        return account_state, True # Allow if location is unknown, but log for review

    if current_location not in account_state.get("trusted_locations", []):
        logger.warning(f"Account '{account_state['account_id']}': Withdrawal attempt from untrusted location: {current_location}.")
        return account_state, False

    logger.info(f"Account '{account_state['account_id']}': Withdrawal from trusted location: {current_location}.")
    return account_state, True

Function Name: block_withdrawal

This function acts as a final gatekeeper, determining whether a withdrawal should be definitively blocked. It aggregates the outcomes of various security checks (e.g., rate limiting, geographic fencing, anomaly detection) and makes a decision. If a withdrawal is blocked, it's logged as such, and the account state reflects this action. This function is crucial for preventing transactions deemed high-risk.

Parameters:

  • account_state (dict): The current state of the user account.
  • transaction_details (dict): A dictionary containing details of the transaction being evaluated.
  • is_suspicious (bool): A flag indicating if preliminary checks have marked the transaction as suspicious.
  • reason (str, optional): A descriptive reason for blocking the withdrawal, if applicable.

Returns:

  • tuple[dict, bool]: The updated account state and a boolean indicating True if the withdrawal was blocked, False otherwise.
[28]
def block_withdrawal(account_state: dict, transaction_details: dict, is_suspicious: bool, reason: str = "") -> tuple[dict, bool]:
    """
    Decides whether to block a withdrawal based on suspicious flags and security analysis.

    Parameters
    ----------
    account_state : dict
        The current state dictionary of the account.
    transaction_details : dict
        A dictionary containing details of the transaction to be evaluated.
    is_suspicious : bool
        True if preliminary checks have flagged the transaction as suspicious.
    reason : str, optional
        A descriptive reason for blocking the withdrawal.

    Returns
    -------
    tuple[dict, bool]
        The updated account state and a boolean indicating if the withdrawal was blocked.

    Examples
    --------
    >>> account = create_account("user_block", 1000.0)
    >>> tx_details = {"amount": 500, "transaction_id": "tx_block1", "location": "Tokyo"}
    >>> updated_account, blocked = block_withdrawal(account, tx_details, True, reason="Untrusted location")
    >>> blocked
    True
    """
    transaction_id = transaction_details.get("transaction_id", "N/A")

    if is_suspicious:
        logger.warning(f"[{transaction_id}] Withdrawal for account '{account_state['account_id']}' BLOCKED. Reason: {reason or 'Suspicious activity detected'}.")
        # Add blocked transaction to history with status 'blocked'
        transaction_details["status"] = "blocked"
        transaction_details["block_reason"] = reason or "Suspicious activity detected"
        transaction_details["timestamp"] = datetime.datetime.now(datetime.timezone.utc).isoformat()
        account_state["transaction_history"].append(transaction_details)
        account_state["failed_attempts"].append({"timestamp": transaction_details["timestamp"], "type": "blocked_withdrawal"})
        return account_state, True
    else:
        logger.info(f"[{transaction_id}] Withdrawal for account '{account_state['account_id']}' allowed to proceed (not suspicious).")
        return account_state, False

Function Name: detect_anomaly

This function performs rule-based anomaly detection on a given transaction. It checks for several indicators of suspicious activity, such as unusually large withdrawal amounts (compared to a threshold or historical data), multiple recent failed attempts, or withdrawals from new, untrusted locations. This function helps to identify potential fraudulent activities before they are fully processed.

Parameters:

  • account_state (dict): The current state of the user account.
  • transaction_details (dict): A dictionary containing details of the transaction being evaluated.
  • large_withdrawal_threshold (float, optional): A threshold amount above which a withdrawal is considered large. Defaults to 1000.0.
  • recent_failed_attempts_threshold (int, optional): The number of recent failed attempts that trigger an anomaly. Defaults to 3.
  • time_window_for_attempts (int, optional): Time window in seconds for recent_failed_attempts_threshold. Defaults to 300 seconds (5 minutes).

Returns:

  • tuple[bool, str]: A boolean indicating True if an anomaly is detected, and a string describing the reason for the anomaly.
[29]
def detect_anomaly(account_state: dict, transaction_details: dict,
                   large_withdrawal_threshold: float = 1000.0,
                   recent_failed_attempts_threshold: int = 3,
                   time_window_for_attempts: int = 300) -> tuple[bool, str]:
    """
    Detects anomalies in a withdrawal attempt based on predefined rules.

    Parameters
    ----------
    account_state : dict
        The current state dictionary of the account.
    transaction_details : dict
        A dictionary containing details of the transaction to be evaluated.
    large_withdrawal_threshold : float, optional
        Amount above which a withdrawal is considered large. Defaults to 1000.0.
    recent_failed_attempts_threshold : int, optional
        Number of recent failed attempts to trigger an anomaly. Defaults to 3.
    time_window_for_attempts : int, optional
        Time window in seconds for counting recent failed attempts. Defaults to 300.

    Returns
    -------
    tuple[bool, str]
        A boolean indicating True if an anomaly is detected, and a string describing the reason.

    Examples
    --------
    >>> account = create_account("user_anomaly", 5000.0)
    >>> tx_details = {"amount": 1500, "transaction_id": "tx_anomaly1", "location": "New York"}
    >>> is_anomaly, reason = detect_anomaly(account, tx_details)
    >>> is_anomaly
    True
    >>> reason
    'Large withdrawal amount (1500.0) exceeding threshold (1000.0)'
    """
    amount = transaction_details.get("amount", 0.0)
    location = transaction_details.get("location", None)
    transaction_id = transaction_details.get("transaction_id", "N/A")

    # Rule 1: Unusually large withdrawal amount
    if amount > large_withdrawal_threshold:
        logger.warning(f"[{transaction_id}] Anomaly detected for account '{account_state['account_id']}': Large withdrawal amount ({amount}) exceeding threshold ({large_withdrawal_threshold}).")
        return True, f"Large withdrawal amount ({amount}) exceeding threshold ({large_withdrawal_threshold})"

    # Rule 2: Multiple failed attempts recently
    current_time = datetime.datetime.now(datetime.timezone.utc)
    recent_failed_attempts = [
        attempt for attempt in account_state.get("failed_attempts", deque())
        if (current_time - datetime.datetime.fromisoformat(attempt["timestamp"]).replace(tzinfo=datetime.timezone.utc)).total_seconds() < time_window_for_attempts
    ]
    if len(recent_failed_attempts) >= recent_failed_attempts_threshold:
        logger.warning(f"[{transaction_id}] Anomaly detected for account '{account_state['account_id']}': Multiple failed attempts ({len(recent_failed_attempts)}) within {time_window_for_attempts} seconds.")
        return True, f"Multiple failed attempts ({len(recent_failed_attempts)}) recently"

    # Rule 3: Withdrawal from a new/untrusted location
    if location and location not in account_state.get("trusted_locations", []):
        logger.warning(f"[{transaction_id}] Anomaly detected for account '{account_state['account_id']}': Withdrawal from untrusted location: {location}.")
        return True, f"Withdrawal from untrusted location: {location}"

    logger.info(f"[{transaction_id}] No anomalies detected for account '{account_state['account_id']}'.")
    return False, "No anomaly detected"

Function Name: generate_audit_log

This function is responsible for creating a comprehensive audit log entry for significant events within the system. It captures details such as the timestamp, event type, associated account, and any relevant data. This log is crucial for security monitoring, forensic analysis, and compliance purposes, providing an immutable record of all actions.

Parameters:

  • event_type (str): A string describing the type of event (e.g., 'withdrawal_success', 'withdrawal_blocked', 'login_failed').
  • account_id (str): The ID of the account related to the event.
  • data (dict, optional): A dictionary containing additional details pertinent to the event (e.g., amount, location, reason for block). Defaults to an empty dictionary.

Returns:

  • dict: A dictionary representing the audit log entry.
[30]
def generate_audit_log(event_type: str, account_id: str, data: dict = None) -> dict:
    """
    Generates a comprehensive audit log entry for a given event.

    Parameters
    ----------
    event_type : str
        The type of event (e.g., 'withdrawal_success', 'withdrawal_blocked').
    account_id : str
        The ID of the account related to the event.
    data : dict, optional
        Additional details pertinent to the event (e.g., amount, location, reason).
        Defaults to an empty dictionary.

    Returns
    -------
    dict
        A dictionary representing the audit log entry.

    Examples
    --------
    >>> log_entry = generate_audit_log("withdrawal_success", "user123", {"amount": 100, "new_balance": 900})
    >>> log_entry['event_type']
    'withdrawal_success'
    """
    if data is None:
        data = {}

    log_entry = {
        "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
        "event_id": str(uuid.uuid4()),
        "event_type": event_type,
        "account_id": account_id,
        "data": data
    }
    logger.info(f"AUDIT: [Account: {account_id}] Event: {event_type} - Details: {data}")
    return log_entry

Function Name: try_with_exponential_backoff

This decorator implements a retry mechanism with exponential backoff and jitter for functions that might experience transient failures (e.g., network issues, temporary service unavailability). It significantly improves the robustness and reliability of operations by automatically retrying them with increasing delays, preventing overwhelming the external services.

Parameters:

  • max_retries (int): The maximum number of times to retry the decorated function. Defaults to 3.
  • base_delay (float): The base delay in seconds for the exponential backoff. Defaults to 1.0.
  • max_delay (float): The maximum delay in seconds between retries. Defaults to 60.0.
  • errors (tuple): A tuple of exception types to catch and retry on. Defaults to (requests.exceptions.RequestException,) (assuming requests is used for external calls, though it's not explicitly imported/used in this notebook's current scope). For this notebook, it will be generic Exception.

Returns:

  • Callable: The decorated function with retry logic.
[31]
def try_with_exponential_backoff(max_retries: int = 3, base_delay: float = 1.0, max_delay: float = 60.0, errors: tuple = (Exception,)): # Changed default errors for broader applicability
    """
    A decorator that retries a function with exponential backoff and jitter.

    Parameters
    ----------
    max_retries : int, optional
        The maximum number of times to retry the decorated function. Defaults to 3.
    base_delay : float, optional
        The base delay in seconds for exponential backoff. Defaults to 1.0.
    max_delay : float, optional
        The maximum delay in seconds between retries. Defaults to 60.0.
    errors : tuple, optional
        A tuple of exception types to catch and retry on. Defaults to (Exception,).

    Returns
    -------
    Callable
        The decorated function with retry logic.

    Examples
    --------
    >>> @try_with_exponential_backoff(max_retries=2, base_delay=0.1, errors=(ValueError,))
    >>> def flaky_function():
    >>>     if random.random() < 0.7: # 70% chance of failure
    >>>         raise ValueError("Simulated transient error")
    >>>     return "Success"
    >>> # Example usage:
    >>> # result = flaky_function()
    >>> # print(result) # Might print 'Success' or raise ValueError after retries
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(max_retries + 1):
                try:
                    return func(*args, **kwargs)
                except errors as e:
                    if i == max_retries:
                        logger.error(f"Function '{func.__name__}' failed after {max_retries} retries: {e}")
                        raise

                    # Exponential backoff with jitter
                    delay = min(max_delay, base_delay * (2 ** i) + random.uniform(0, base_delay))
                    logger.warning(f"Function '{func.__name__}' failed with '{e}'. Retrying in {delay:.2f} seconds (attempt {i+1}/{max_retries}).")
                    time.sleep(delay)
            return func(*args, **kwargs) # Should not be reached, but for type hinting
        return wrapper
    return decorator

Demonstration and Visualization

This section demonstrates the integrated security mechanisms through simulations and visualizes their impact. We will simulate various withdrawal scenarios, including legitimate transactions, large withdrawals, attempts from untrusted locations, and rate-limited attempts. The goal is to observe how the implemented functions (rate limiting, geographic fencing, anomaly detection, withdrawal blocking) interact to protect the accounts.

We will track key metrics such as successful withdrawals, blocked withdrawals, anomaly detections, and failed attempts. Visualizations will help in understanding the system's performance and the effectiveness of each security layer.

Function: process_withdrawal_flow

This function orchestrates the entire security workflow for processing withdrawal requests. It integrates the previously defined core functions such as rate limiting, geographic fencing, anomaly detection, and withdrawal blocking. It provides a structured way to apply all security checks in a specific order and make a final decision on whether to approve or block a withdrawal.

Parameters:

  • account_state (dict): The current state of the user account.
  • amount (float): The amount to be withdrawn.
  • location (str): The geographic location from which the withdrawal is initiated.
  • all_audit_logs (list): A mutable list to collect all generated audit log entries.
  • large_withdrawal_threshold (float, optional): Threshold for large withdrawals. Defaults to 1000.0.
  • max_failed_attempts (int, optional): Max failed attempts for rate limiting. Defaults to 3.
  • failed_attempts_window (int, optional): Time window for failed attempts. Defaults to 300 seconds.
  • cooldown_period (int, optional): Cooldown period after rate limit. Defaults to 60 seconds.

Returns:

  • tuple[dict, bool, str]: The updated account state, a boolean indicating success, and a message describing the outcome.
[32]
def process_withdrawal_flow(account_state: dict, amount: float, location: str, all_audit_logs: list,
                            large_withdrawal_threshold: float = 1000.0,
                            max_failed_attempts: int = 3,
                            failed_attempts_window: int = 300,
                            cooldown_period: int = 60) -> tuple[dict, bool, str]:
    """
    Orchestrates the security workflow for processing withdrawal requests using standalone functions.
    """
    transaction_id = str(uuid.uuid4())
    transaction_details = {"amount": amount, "transaction_id": transaction_id, "location": location}
    account_id = account_state['account_id']

    all_audit_logs.append(generate_audit_log("withdrawal_request", account_id, transaction_details))

    # Step 1: Rate Limiting Check
    account_state, allowed_by_rate_limit = check_rate_limit(account_state,
                                                             max_attempts=max_failed_attempts,
                                                             time_window_seconds=failed_attempts_window,
                                                             cooldown_seconds=cooldown_period)
    if not allowed_by_rate_limit:
        reason = "Rate limit exceeded"
        account_state, _ = block_withdrawal(account_state, transaction_details, True, reason)
        all_audit_logs.append(generate_audit_log("withdrawal_blocked", account_id, {**transaction_details, "reason": reason}))
        return account_state, False, reason

    # Step 2: Geographic Fencing Check
    account_state, allowed_by_geo = check_geographic_fencing(account_state, location)
    if not allowed_by_geo:
        reason = f"Untrusted location: {location}"
        account_state, _ = block_withdrawal(account_state, transaction_details, True, reason)
        all_audit_logs.append(generate_audit_log("withdrawal_blocked", account_id, {**transaction_details, "reason": reason}))
        return account_state, False, reason

    # Step 3: Anomaly Detection
    is_anomaly, anomaly_reason = detect_anomaly(account_state, transaction_details,
                                                large_withdrawal_threshold=large_withdrawal_threshold,
                                                recent_failed_attempts_threshold=max_failed_attempts,
                                                time_window_for_attempts=failed_attempts_window)
    if is_anomaly:
        # Decide whether to block based on anomaly
        # For this demo, we will block all detected anomalies
        account_state, _ = block_withdrawal(account_state, transaction_details, True, anomaly_reason)
        all_audit_logs.append(generate_audit_log("withdrawal_blocked", account_id, {**transaction_details, "reason": anomaly_reason}))
        return account_state, False, anomaly_reason

    # Step 4: Perform Withdrawal (if all checks pass)
    updated_account, success = perform_withdrawal(account_state, amount, transaction_id, location)
    if success:
        all_audit_logs.append(generate_audit_log("withdrawal_success", account_id, {**transaction_details, "new_balance": updated_account['balance']}))
        return updated_account, True, "Withdrawal successful"
    else:
        # This else block captures failures from perform_withdrawal itself (e.g., insufficient funds)
        last_tx_status = updated_account['transaction_history'][-1]['status'] if updated_account['transaction_history'] else 'unknown_error'
        reason = f"Withdrawal failed: {last_tx_status}"
        all_audit_logs.append(generate_audit_log("withdrawal_failed", account_id, {**transaction_details, "reason": reason}))
        return updated_account, False, reason

Simulation Setup

We will set up a simulation environment to test the SecurityProcessor with different scenarios. This involves:

  1. Initializing Accounts: Create several accounts with varying balances and trusted locations.
  2. Instantiating Security Processor: Configure the security processor with desired thresholds.
  3. Simulating Transactions: Execute a series of withdrawals, including:
    • Normal, legitimate withdrawals.
    • Large withdrawals to trigger anomaly detection.
    • Withdrawals from untrusted locations.
    • Repeated failed attempts to trigger rate limiting.
    • Withdrawals that might trigger multiple security layers.

After running the simulations, we will collect and analyze the audit logs to understand the effectiveness of each security control.

[33]
# Initialize Accounts
accounts = {
    "user_legit": create_account("user_legit", 5000.0),
    "user_high_risk": create_account("user_high_risk", 7500.0),
    "user_rate_limit": create_account("user_rate_limit", 2000.0),
    "user_geo": create_account("user_geo", 3000.0)
}

accounts["user_legit"]['trusted_locations'] = ['New York', 'London', 'Paris']
accounts["user_high_risk"]['trusted_locations'] = ['London', 'Zurich']
accounts["user_rate_limit"]['trusted_locations'] = ['Berlin']
accounts["user_geo"]['trusted_locations'] = ['Tokyo', 'Kyoto']

# Global list to collect audit logs
all_audit_logs = []

# Define simulation thresholds
SIM_LARGE_WITHDRAWAL_THRESHOLD = 1200.0
SIM_MAX_FAILED_ATTEMPTS = 3
SIM_FAILED_ATTEMPTS_WINDOW = 60
SIM_COOLDOWN_PERIOD = 10

print("\n--- Starting Transaction Simulation ---")

# Scenario 1: Legitimate withdrawal
print("\n--- user_legit: Legitimate withdrawal ---")
accounts["user_legit"], success, msg = process_withdrawal_flow(
    accounts["user_legit"], 100.0, "New York", all_audit_logs,
    large_withdrawal_threshold=SIM_LARGE_WITHDRAWAL_THRESHOLD,
    max_failed_attempts=SIM_MAX_FAILED_ATTEMPTS,
    failed_attempts_window=SIM_FAILED_ATTEMPTS_WINDOW,
    cooldown_period=SIM_COOLDOWN_PERIOD
)
print(f"Result: {msg}, Success: {success}, Balance: {accounts['user_legit']['balance']:.2f}")

# Scenario 2: Large withdrawal (Anomaly Detection)
print("\n--- user_high_risk: Large withdrawal ---")
accounts["user_high_risk"], success, msg = process_withdrawal_flow(
    accounts["user_high_risk"], 1500.0, "London", all_audit_logs,
    large_withdrawal_threshold=SIM_LARGE_WITHDRAWAL_THRESHOLD,
    max_failed_attempts=SIM_MAX_FAILED_ATTEMPTS,
    failed_attempts_window=SIM_FAILED_ATTEMPTS_WINDOW,
    cooldown_period=SIM_COOLDOWN_PERIOD
)
print(f"Result: {msg}, Success: {success}, Balance: {accounts['user_high_risk']['balance']:.2f}")

# Scenario 3: Untrusted geographic location
print("\n--- user_geo: Untrusted location ---")
accounts["user_geo"], success, msg = process_withdrawal_flow(
    accounts["user_geo"], 500.0, "Beijing", all_audit_logs,
    large_withdrawal_threshold=SIM_LARGE_WITHDRAWAL_THRESHOLD,
    max_failed_attempts=SIM_MAX_FAILED_ATTEMPTS,
    failed_attempts_window=SIM_FAILED_ATTEMPTS_WINDOW,
    cooldown_period=SIM_COOLDOWN_PERIOD
)
print(f"Result: {msg}, Success: {success}, Balance: {accounts['user_geo']['balance']:.2f}")

# Scenario 4: Rate Limiting (multiple failed attempts)
print("\n--- user_rate_limit: Rate limit attempts ---")
for _ in range(4):
    # Simulate failed withdrawal (e.g., insufficient funds deliberately)
    accounts["user_rate_limit"], success, msg = process_withdrawal_flow(
        accounts["user_rate_limit"], 5000.0, "Berlin", all_audit_logs,
        large_withdrawal_threshold=SIM_LARGE_WITHDRAWAL_THRESHOLD,
        max_failed_attempts=SIM_MAX_FAILED_ATTEMPTS,
        failed_attempts_window=SIM_FAILED_ATTEMPTS_WINDOW,
        cooldown_period=SIM_COOLDOWN_PERIOD
    )
    print(f"Result: {msg}, Success: {success}, Failed attempts: {len(accounts['user_rate_limit']['failed_attempts'])}")
    time.sleep(1) # Simulate some time between attempts

# Attempt after rate limit is active
accounts["user_rate_limit"], success, msg = process_withdrawal_flow(
    accounts["user_rate_limit"], 100.0, "Berlin", all_audit_logs,
    large_withdrawal_threshold=SIM_LARGE_WITHDRAWAL_THRESHOLD,
    max_failed_attempts=SIM_MAX_FAILED_ATTEMPTS,
    failed_attempts_window=SIM_FAILED_ATTEMPTS_WINDOW,
    cooldown_period=SIM_COOLDOWN_PERIOD
)
print(f"Result: {msg}, Success: {success}, Failed attempts: {len(accounts['user_rate_limit']['failed_attempts'])}")

# Scenario 5: Mixed scenario - Legit from trusted, then untrusted, then large
print("\n--- user_legit: Mixed scenarios ---")
accounts["user_legit"], success, msg = process_withdrawal_flow(
    accounts["user_legit"], 200.0, "Paris", all_audit_logs,
    large_withdrawal_threshold=SIM_LARGE_WITHDRAWAL_THRESHOLD,
    max_failed_attempts=SIM_MAX_FAILED_ATTEMPTS,
    failed_attempts_window=SIM_FAILED_ATTEMPTS_WINDOW,
    cooldown_period=SIM_COOLDOWN_PERIOD
)
print(f"Result: {msg}, Success: {success}, Balance: {accounts['user_legit']['balance']:.2f}")

accounts["user_legit"], success, msg = process_withdrawal_flow(
    accounts["user_legit"], 300.0, "Cairo", all_audit_logs,
    large_withdrawal_threshold=SIM_LARGE_WITHDRAWAL_THRESHOLD,
    max_failed_attempts=SIM_MAX_FAILED_ATTEMPTS,
    failed_attempts_window=SIM_FAILED_ATTEMPTS_WINDOW,
    cooldown_period=SIM_COOLDOWN_PERIOD
) # Untrusted
print(f"Result: {msg}, Success: {success}, Balance: {accounts['user_legit']['balance']:.2f}")

accounts["user_legit"], success, msg = process_withdrawal_flow(
    accounts["user_legit"], 1300.0, "New York", all_audit_logs,
    large_withdrawal_threshold=SIM_LARGE_WITHDRAWAL_THRESHOLD,
    max_failed_attempts=SIM_MAX_FAILED_ATTEMPTS,
    failed_attempts_window=SIM_FAILED_ATTEMPTS_WINDOW,
    cooldown_period=SIM_COOLDOWN_PERIOD
) # Large, but trusted location
print(f"Result: {msg}, Success: {success}, Balance: {accounts['user_legit']['balance']:.2f}")

print("\n--- Simulation Complete ---")

# Display some audit logs
print("\n--- Sample Audit Logs ---")
for log in all_audit_logs[:5]: # Display first 5 logs
    print(f"Event: {log['event_type']} | Account: {log['account_id']} | Data: {log['data']}")
WARNING:__main__:[878285e1-0a2d-48b3-bc57-2e5eec79cf0d] Anomaly detected for account 'user_high_risk': Large withdrawal amount (1500.0) exceeding threshold (1200.0).
WARNING:__main__:[878285e1-0a2d-48b3-bc57-2e5eec79cf0d] Withdrawal for account 'user_high_risk' BLOCKED. Reason: Large withdrawal amount (1500.0) exceeding threshold (1200.0).
WARNING:__main__:Account 'user_geo': Withdrawal attempt from untrusted location: Beijing.
WARNING:__main__:[28a84eaa-260d-4e1d-82e1-610479f15ae2] Withdrawal for account 'user_geo' BLOCKED. Reason: Untrusted location: Beijing.
WARNING:__main__:[102bebd8-d76b-4a32-aac4-957dd85ac310] Anomaly detected for account 'user_rate_limit': Large withdrawal amount (5000.0) exceeding threshold (1200.0).
WARNING:__main__:[102bebd8-d76b-4a32-aac4-957dd85ac310] Withdrawal for account 'user_rate_limit' BLOCKED. Reason: Large withdrawal amount (5000.0) exceeding threshold (1200.0).

--- Starting Transaction Simulation ---

--- user_legit: Legitimate withdrawal ---
Result: Withdrawal successful, Success: True, Balance: 4900.00

--- user_high_risk: Large withdrawal ---
Result: Large withdrawal amount (1500.0) exceeding threshold (1200.0), Success: False, Balance: 7500.00

--- user_geo: Untrusted location ---
Result: Untrusted location: Beijing, Success: False, Balance: 3000.00

--- user_rate_limit: Rate limit attempts ---
Result: Large withdrawal amount (5000.0) exceeding threshold (1200.0), Success: False, Failed attempts: 1
WARNING:__main__:[628f2c8c-cf6b-4c3a-a192-e87c3a2138be] Anomaly detected for account 'user_rate_limit': Large withdrawal amount (5000.0) exceeding threshold (1200.0).
WARNING:__main__:[628f2c8c-cf6b-4c3a-a192-e87c3a2138be] Withdrawal for account 'user_rate_limit' BLOCKED. Reason: Large withdrawal amount (5000.0) exceeding threshold (1200.0).
Result: Large withdrawal amount (5000.0) exceeding threshold (1200.0), Success: False, Failed attempts: 2
WARNING:__main__:[8070cdb6-be14-4055-a645-1a664ebba47e] Anomaly detected for account 'user_rate_limit': Large withdrawal amount (5000.0) exceeding threshold (1200.0).
WARNING:__main__:[8070cdb6-be14-4055-a645-1a664ebba47e] Withdrawal for account 'user_rate_limit' BLOCKED. Reason: Large withdrawal amount (5000.0) exceeding threshold (1200.0).
Result: Large withdrawal amount (5000.0) exceeding threshold (1200.0), Success: False, Failed attempts: 3
WARNING:__main__:Account 'user_rate_limit' is rate-limited. Too many failed attempts recently.
WARNING:__main__:[ed4f85b1-2a05-48f0-b489-879f109a5beb] Withdrawal for account 'user_rate_limit' BLOCKED. Reason: Rate limit exceeded.
Result: Rate limit exceeded, Success: False, Failed attempts: 4
WARNING:__main__:Account 'user_rate_limit' is rate-limited. Too many failed attempts recently.
WARNING:__main__:[ee25988e-bf33-4d96-9192-82db97725bc2] Withdrawal for account 'user_rate_limit' BLOCKED. Reason: Rate limit exceeded.
WARNING:__main__:Account 'user_legit': Withdrawal attempt from untrusted location: Cairo.
WARNING:__main__:[3b48bb89-4eb8-4d58-ad16-ae4ffb25283c] Withdrawal for account 'user_legit' BLOCKED. Reason: Untrusted location: Cairo.
WARNING:__main__:[22157610-cfaa-4acf-be31-22d9204564ed] Anomaly detected for account 'user_legit': Large withdrawal amount (1300.0) exceeding threshold (1200.0).
WARNING:__main__:[22157610-cfaa-4acf-be31-22d9204564ed] Withdrawal for account 'user_legit' BLOCKED. Reason: Large withdrawal amount (1300.0) exceeding threshold (1200.0).
Result: Rate limit exceeded, Success: False, Failed attempts: 5

--- user_legit: Mixed scenarios ---
Result: Withdrawal successful, Success: True, Balance: 4700.00
Result: Untrusted location: Cairo, Success: False, Balance: 4700.00
Result: Large withdrawal amount (1300.0) exceeding threshold (1200.0), Success: False, Balance: 4700.00

--- Simulation Complete ---

--- Sample Audit Logs ---
Event: withdrawal_request | Account: user_legit | Data: {'amount': 100.0, 'transaction_id': 'b122274c-da6c-4237-aad2-f006a9c2608f', 'location': 'New York'}
Event: withdrawal_success | Account: user_legit | Data: {'amount': 100.0, 'transaction_id': 'b122274c-da6c-4237-aad2-f006a9c2608f', 'location': 'New York', 'new_balance': 4900.0}
Event: withdrawal_request | Account: user_high_risk | Data: {'amount': 1500.0, 'transaction_id': '878285e1-0a2d-48b3-bc57-2e5eec79cf0d', 'location': 'London', 'status': 'blocked', 'block_reason': 'Large withdrawal amount (1500.0) exceeding threshold (1200.0)', 'timestamp': '2026-06-10T10:28:21.048968+00:00'}
Event: withdrawal_blocked | Account: user_high_risk | Data: {'amount': 1500.0, 'transaction_id': '878285e1-0a2d-48b3-bc57-2e5eec79cf0d', 'location': 'London', 'status': 'blocked', 'block_reason': 'Large withdrawal amount (1500.0) exceeding threshold (1200.0)', 'timestamp': '2026-06-10T10:28:21.048968+00:00', 'reason': 'Large withdrawal amount (1500.0) exceeding threshold (1200.0)'}
Event: withdrawal_request | Account: user_geo | Data: {'amount': 500.0, 'transaction_id': '28a84eaa-260d-4e1d-82e1-610479f15ae2', 'location': 'Beijing', 'status': 'blocked', 'block_reason': 'Untrusted location: Beijing', 'timestamp': '2026-06-10T10:28:21.056360+00:00'}

Audit Log Analysis and Visualization

To better understand the security events and the system's responses, we will analyze the audit_logs collected during the simulation. This involves:

  1. Data Structuring: Converting the list of audit log dictionaries into a Pandas DataFrame for efficient querying and manipulation.
  2. Metric Extraction: Calculating key metrics such as the number of successful withdrawals, blocked withdrawals, and reasons for blocking.
  3. Visualization: Creating plots to represent the distribution of event types, reasons for blocking, and potentially trends over time. These visualizations will provide insights into which security measures are most frequently triggered and the overall security posture.
[34]
# Convert audit logs to DataFrame
audit_df = pd.DataFrame(all_audit_logs)

# Extract relevant information from 'data' column for easier analysis
audit_df['amount'] = audit_df['data'].apply(lambda x: x.get('amount'))
audit_df['location'] = audit_df['data'].apply(lambda x: x.get('location'))
audit_df['reason'] = audit_df['data'].apply(lambda x: x.get('reason'))
audit_df['block_reason'] = audit_df['data'].apply(lambda x: x.get('block_reason'))
audit_df['new_balance'] = audit_df['data'].apply(lambda x: x.get('new_balance'))

# Convert timestamp to datetime objects
audit_df['timestamp'] = pd.to_datetime(audit_df['timestamp'])

# Display basic information about the audit log DataFrame
print("\n--- Audit Log DataFrame Info ---")
print(audit_df.info())
print("\n--- Audit Log Head ---")
print(audit_df.head())

# Calculate overall statistics
total_requests = len(audit_df)
successful_withdrawals = audit_df[audit_df['event_type'] == 'withdrawal_success'].shape[0]
blocked_withdrawals = audit_df[audit_df['event_type'] == 'withdrawal_blocked'].shape[0]
failed_withdrawals = audit_df[audit_df['event_type'] == 'withdrawal_failed'].shape[0]

print(f"\nTotal Withdrawal Requests: {total_requests}")
print(f"Successful Withdrawals: {successful_withdrawals}")
print(f"Blocked Withdrawals: {blocked_withdrawals}")
print(f"Failed Withdrawals (e.g., insufficient funds): {failed_withdrawals}")

# Analyze reasons for blocked withdrawals
blocked_reasons = audit_df[audit_df['event_type'] == 'withdrawal_blocked']['reason'].value_counts()
print("\nReasons for Blocked Withdrawals:")
print(blocked_reasons)

# Analyze event types
event_type_counts = audit_df['event_type'].value_counts()
print("\nEvent Type Counts:")
print(event_type_counts)

--- Audit Log DataFrame Info ---
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 22 entries, 0 to 21
Data columns (total 10 columns):
 #   Column        Non-Null Count  Dtype              
---  ------        --------------  -----              
 0   timestamp     22 non-null     datetime64[ns, UTC]
 1   event_id      22 non-null     object             
 2   event_type    22 non-null     object             
 3   account_id    22 non-null     object             
 4   data          22 non-null     object             
 5   amount        22 non-null     float64            
 6   location      22 non-null     object             
 7   reason        9 non-null      object             
 8   block_reason  18 non-null     object             
 9   new_balance   2 non-null      float64            
dtypes: datetime64[ns, UTC](1), float64(2), object(7)
memory usage: 1.8+ KB
None

--- Audit Log Head ---
                         timestamp                              event_id  \
0 2026-06-10 10:28:20.870787+00:00  f742bb87-7f92-4b0b-a169-4c8baa369d2c   
1 2026-06-10 10:28:21.042718+00:00  445372fe-301c-44a1-ba2b-c68965495225   
2 2026-06-10 10:28:21.043412+00:00  552e488a-1dca-4d0e-8a99-cf5f3ac4b5f3   
3 2026-06-10 10:28:21.048993+00:00  2be34e21-d3ae-459d-af83-bfde63578962   
4 2026-06-10 10:28:21.049524+00:00  1cf56926-8ad3-4841-9d46-d9e46a23d42a   

           event_type      account_id  \
0  withdrawal_request      user_legit   
1  withdrawal_success      user_legit   
2  withdrawal_request  user_high_risk   
3  withdrawal_blocked  user_high_risk   
4  withdrawal_request        user_geo   

                                                data  amount  location  \
0  {'amount': 100.0, 'transaction_id': 'b122274c-...   100.0  New York   
1  {'amount': 100.0, 'transaction_id': 'b122274c-...   100.0  New York   
2  {'amount': 1500.0, 'transaction_id': '878285e1...  1500.0    London   
3  {'amount': 1500.0, 'transaction_id': '878285e1...  1500.0    London   
4  {'amount': 500.0, 'transaction_id': '28a84eaa-...   500.0   Beijing   

                                              reason  \
0                                               None   
1                                               None   
2                                               None   
3  Large withdrawal amount (1500.0) exceeding thr...   
4                                               None   

                                        block_reason  new_balance  
0                                               None          NaN  
1                                               None       4900.0  
2  Large withdrawal amount (1500.0) exceeding thr...          NaN  
3  Large withdrawal amount (1500.0) exceeding thr...          NaN  
4                        Untrusted location: Beijing          NaN  

Total Withdrawal Requests: 22
Successful Withdrawals: 2
Blocked Withdrawals: 9
Failed Withdrawals (e.g., insufficient funds): 0

Reasons for Blocked Withdrawals:
reason
Large withdrawal amount (5000.0) exceeding threshold (1200.0)    3
Rate limit exceeded                                              2
Untrusted location: Beijing                                      1
Large withdrawal amount (1500.0) exceeding threshold (1200.0)    1
Untrusted location: Cairo                                        1
Large withdrawal amount (1300.0) exceeding threshold (1200.0)    1
Name: count, dtype: int64

Event Type Counts:
event_type
withdrawal_request    11
withdrawal_blocked     9
withdrawal_success     2
Name: count, dtype: int64

Visualization 1: Distribution of Event Types

This bar chart shows the total count of each event type recorded in the audit logs, including withdrawal requests, successful withdrawals, and blocked withdrawals. This visualization provides a high-level overview of the system's activity and the frequency of security interventions.

[35]
plt.figure(figsize=(10, 6))
sns.barplot(x=event_type_counts.index, y=event_type_counts.values, palette='viridis')
plt.title('Distribution of Withdrawal Event Types')
plt.xlabel('Event Type')
plt.ylabel('Count')
plt.grid(axis='y', linestyle='--', alpha=0.7)
plt.tight_layout()
plt.show()
/tmp/ipykernel_822/2286260485.py:2: FutureWarning: 

Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.

  sns.barplot(x=event_type_counts.index, y=event_type_counts.values, palette='viridis')
cell output

Visualization 2: Reasons for Blocked Withdrawals

This visualization details the different reasons why withdrawals were blocked. Understanding the primary blocking reasons helps in identifying the most active security mechanisms and potential areas for fine-tuning or further investigation.

[36]
plt.figure(figsize=(12, 7))
sns.barplot(x=blocked_reasons.values, y=blocked_reasons.index, palette='magma')
plt.title('Reasons for Blocked Withdrawals')
plt.xlabel('Count')
plt.ylabel('Reason for Blocking')
plt.grid(axis='x', linestyle='--', alpha=0.7)
plt.tight_layout()
plt.show()
/tmp/ipykernel_822/1362340307.py:2: FutureWarning: 

Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect.

  sns.barplot(x=blocked_reasons.values, y=blocked_reasons.index, palette='magma')
cell output

Production Considerations

Deploying an infrastructure security system like the one demonstrated requires careful consideration of various factors to ensure robustness, scalability, and maintainability in a production environment. This section outlines key best practices and considerations.

AspectDescription
ScalabilityChallenge: As transaction volume grows, the in-memory account_state and audit_logs will not scale. Solution: Persist account states in a high-performance, scalable database (e.g., PostgreSQL, Cassandra, DynamoDB). Audit logs should be streamed to a robust logging system (e.g., ELK Stack, Splunk, cloud-native logging services like Google Cloud Logging or AWS CloudWatch Logs) for analysis and long-term storage. Consider distributed caching (e.g., Redis) for frequently accessed account data to reduce database load.
Real-time MonitoringChallenge: Detecting and responding to threats quickly. Solution: Implement real-time dashboards (e.g., Grafana, custom web UI) that visualize key security metrics (e.g., number of blocked transactions, anomaly types, rate-limiting triggers). Set up alerts (e.g., PagerDuty, Slack, email) for critical security events. Integrate with Security Information and Event Management (SIEM) systems for centralized security operations.
Advanced Anomaly DetectionChallenge: Simple rule-based detection can be brittle and prone to false positives/negatives. Solution: Implement Machine Learning (ML) models (e.g., Isolation Forests, One-Class SVMs, Deep Learning-based anomaly detection) that learn from historical transaction patterns. These models can identify more subtle and sophisticated attack vectors. Incorporate behavioral analytics to profile normal user activity and flag deviations.
Threat Intelligence IntegrationChallenge: Proactive defense against known threats. Solution: Integrate with external threat intelligence feeds (e.g., IP blacklists, known fraud patterns, compromised credentials lists). Automatically update security rules and models based on the latest threat landscape.
Access Control & Least PrivilegeChallenge: Unauthorized access to sensitive systems. Solution: Implement strict Role-Based Access Control (RBAC) for all components. Ensure that each service and user has only the minimum necessary permissions to perform their function (principle of least privilege). Regularly audit access policies and revoke unnecessary permissions.
Secure CommunicationChallenge: Data interception or tampering during transit. Solution: Encrypt all data in transit using TLS/SSL. Ensure APIs and microservices communicate over secure channels. Use mTLS (mutual TLS) for service-to-service authentication where appropriate.
Regular Audits & Penetration TestingChallenge: Overlooking vulnerabilities. Solution: Conduct regular security audits, code reviews, and penetration testing (ethical hacking) by independent security experts. Use automated security scanning tools (SAST, DAST) in the CI/CD pipeline.
Disaster Recovery & BackupChallenge: Data loss or system downtime due to unforeseen events. Solution: Implement robust backup and disaster recovery strategies for all critical data and systems. Regularly test recovery procedures to ensure they function as expected. Utilize multi-region deployments for high availability and resilience.
Compliance & RegulationsChallenge: Adhering to legal and industry standards (e.g., GDPR, CCPA, PCI DSS). Solution: Ensure all data handling, logging, and security measures comply with relevant regulations. Maintain comprehensive documentation of security policies and procedures. Regularly review and update compliance strategies as regulations evolve.
Incident Response PlanChallenge: Responding effectively to a security breach. Solution: Develop and regularly practice a detailed incident response plan. Define roles, responsibilities, communication protocols, and technical steps for detection, containment, eradication, recovery, and post-incident analysis.

Conclusion

This notebook has demonstrated the implementation and effectiveness of several key infrastructure security mechanisms designed to block unauthorized withdrawals. We've covered:

  • Account State Management: Securely managing user balances and transaction histories.
  • Rate Limiting: Preventing brute-force or high-frequency fraudulent attempts.
  • Geographic Fencing: Restricting transactions from untrusted locations.
  • Anomaly Detection: Identifying suspicious withdrawal patterns based on predefined rules.
  • Withdrawal Blocking: A final decision point to stop high-risk transactions.
  • Audit Logging: Maintaining immutable records for all security-relevant events.
  • Retry Mechanisms: Ensuring robustness for external interactions (though not directly demonstrated in the SecurityProcessor flow).

The simulation highlighted how these layered defenses work in concert to protect user accounts. While rule-based systems provide a strong foundation, the 'Production Considerations' emphasized the need for advanced techniques like ML-driven anomaly detection, real-time monitoring, and robust infrastructure to scale and secure these systems in a real-world environment. Effective infrastructure security is an ongoing process that requires continuous monitoring, adaptation, and improvement to counter evolving threats.