Crypto Agent - Stage 3: Secure Implementation¶
🎯 Goal: Learn production-grade security patterns
⏱️ Time: 3-4 hours
📍 You are here: Stage 3 of 3
✅ Security Rating: 9/10 - PRODUCTION-READY
Navigation¶
← Previous: Stage 2 - Improved | ↑ Up: Crypto Example Overview
👋 Welcome to Stage 3!¶
Congratulations on making it here! You've seen what's broken (Stage 1) and why partial fixes fail (Stage 2). Now it's time to see how to build secure systems the right way.
This is production-grade code. The patterns you'll learn here are used in real-world financial systems, healthcare applications, and enterprise platforms. After this stage, you'll have a template for building secure A2A agents.
✅ This is the destination. Stage 3 shows you how to build systems you can actually deploy.
🎯 What You'll Learn¶
By the end of this stage, you will understand:
- ✅ Asymmetric cryptography for authentication (Ed25519)
- ✅ Replay attack prevention (nonces + timestamps)
- ✅ Comprehensive input validation (8-layer approach)
- ✅ Rate limiting strategies (token bucket algorithm)
- ✅ Secure error handling and logging
- ✅ Defense in depth architecture
- ✅ Key management best practices
- ✅ Production deployment considerations
Skills You'll Gain¶
- 🔧 Implement Ed25519 signature verification
- 🔧 Build a nonce tracking system
- 🔧 Create comprehensive validation layers
- 🔧 Deploy rate limiting
- 🔧 Structure security modules
- 🔧 Use this as a template for your own agents
📊 The Complete Journey¶
Security Evolution¶
| Aspect | Stage 1 | Stage 2 | Stage 3 |
|---|---|---|---|
| Authentication | ❌ None | ⚠️ HMAC (shared) | ✅ Ed25519 (asymmetric) |
| Replay Protection | ❌ None | ❌ None | ✅ Nonces + timestamps |
| Input Validation | ❌ None | ⚠️ Basic | ✅ 8-layer validation |
| Rate Limiting | ❌ None | ❌ None | ✅ Token bucket |
| Encryption | ❌ HTTP | ❌ HTTP | ✅ HTTPS (TLS) |
| Key Management | ❌ None | ⚠️ Shared secret | ✅ Public/private keys |
| Audit Logging | ❌ None | ⚠️ Basic | ✅ Comprehensive |
| Error Handling | ❌ Unsafe | ⚠️ Better | ✅ Secure |
| Request Limits | ❌ None | ❌ None | ✅ Size + rate limits |
| Registry Security | N/A | ⚠️ Weak | ✅ Authenticated |
| Session Management | ❌ None | ❌ None | ✅ Secure sessions |
| Monitoring | ❌ None | ❌ None | ✅ Metrics + alerts |
Security Score: 0/10 → 4/10 → 9/10 ✅
🏗️ Architecture Overview¶
Stage 3 uses a modular security architecture:
┌─────────────────────────────────────────────────────────────┐
│ Crypto Price Agent │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Request Handler Layer │ │
│ │ • Receives HTTP requests │ │
│ │ • Routes to appropriate handler │ │
│ └────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Security Module (security/) │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ 1. Authentication (authentication.py) │ │ │
│ │ │ • Ed25519 signature verification │ │ │
│ │ │ • Public key validation │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ 2. Replay Protection (replay_protection.py) │ │ │
│ │ │ • Nonce tracking (Redis) │ │ │
│ │ │ • Timestamp validation │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ 3. Validation (validation.py) │ │ │
│ │ │ • Input sanitization (8 layers) │ │ │
│ │ │ • Schema validation │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ 4. Rate Limiting (rate_limit.py) │ │ │
│ │ │ • Token bucket algorithm │ │ │
│ │ │ • Per-client limits │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ 5. Audit Logging (audit.py) │ │ │
│ │ │ • Structured logging │ │ │
│ │ │ • Security events │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Business Logic Layer │ │
│ │ • Price queries │ │
│ │ • Data formatting │ │
│ │ • Response generation │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
Key Principle: Security checks happen BEFORE business logic. If security fails, business logic never runs.
🚀 Quick Start (15 Minutes)¶
Prerequisites¶
# Python 3.8+
python3 --version
# Install dependencies
pip install cryptography # For Ed25519
pip install redis # For nonce tracking
pip install pydantic # For validation
pip install fastapi # Web framework
pip install uvicorn # ASGI server
Or use the requirements file:
Step 1: Start Redis (For Nonce Tracking)¶
Option A: Docker
Option B: Local Redis
# macOS
brew install redis
brew services start redis
# Ubuntu
sudo apt install redis-server
sudo systemctl start redis
Option C: Mock Mode (Testing Only)
Step 2: Generate Keys¶
Stage 3 uses public/private key pairs (not shared secrets!):
Output:
🔐 Generating Ed25519 Key Pairs...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Server Keys Generated:
Private: server_private.key (keep secret!)
Public: server_public.key
✅ Client Keys Generated:
Private: client_private.key (keep secret!)
Public: client_public.key
🔒 Store private keys securely!
📢 Share public keys with counterparties
Key Facts:
• Private keys sign messages
• Public keys verify signatures
• Each party has their own key pair
• No shared secrets!
🔐 Important: Keep private keys secret! Only share public keys.
Step 3: Start the Secure Server¶
Terminal 1:
Expected Output:
🚀 Starting Cryptocurrency Price Agent (Stage 3 - Production)
════════════════════════════════════════════════════════════════
🔒 Security Configuration:
✅ Ed25519 Authentication
✅ Replay Protection (nonces + timestamps)
✅ 8-Layer Input Validation
✅ Rate Limiting (100 req/min per client)
✅ Comprehensive Audit Logging
✅ TLS/HTTPS Ready
✅ Secure Error Handling
📊 Security Rating: 9/10 (Production Ready)
🔧 Technical Details:
• Nonce Storage: Redis (localhost:6379)
• Nonce TTL: 5 minutes
• Max Request Size: 10KB
• Rate Limit: Token bucket algorithm
• Signature Algorithm: Ed25519
🔑 Loaded Keys:
• Server Public Key: [first 16 chars]...
• Authorized Clients: 1 registered
📡 Server Endpoints:
• POST /query - Price queries
• GET /health - Health check
• GET /metrics - Prometheus metrics
✅ All security systems initialized
🚀 Server ready on http://localhost:8080
Waiting for authenticated requests...
Step 4: Run the Secure Client¶
Terminal 2:
Expected Output:
════════════════════════════════════════════════════════════════
Cryptocurrency Query Client (Stage 3 - Secure)
════════════════════════════════════════════════════════════════
🔐 Security Features:
✅ Ed25519 Request Signing
✅ Automatic Nonce Generation
✅ Timestamp Management
✅ Response Verification
🔑 Loaded Keys:
• Client Private Key: ✓
• Server Public Key: ✓
📡 Connected to: http://localhost:8080
Commands:
• Query: "What's the price of Bitcoin?"
• Health: "health"
• Metrics: "metrics"
• Quit: "quit"
Enter your query:
Step 5: Try a Secure Query¶
Response:
🔐 Building secure request...
• Generated nonce: a3d2c9f1-4b3e-4c8f-9a1b-2d3e4f5a6b7c
• Timestamp: 2024-12-19T15:30:45Z
• Signing with Ed25519...
✅ Request signed
📤 Sending authenticated request...
✅ Signature verified by server
✅ Replay protection passed
✅ Rate limit: OK (45/100 requests remaining)
🤖 Agent Response:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
The current price of Ethereum (ETH) is $2,245.50
📊 Market Data:
• 24h Change: +3.2%
• 24h Volume: $15.2B
• Market Cap: $270B
• Last Updated: 2024-12-19 15:30:45 UTC
🔒 Security Info:
• Request authenticated via Ed25519
• Nonce: a3d2c9f1... (one-time use)
• Server response time: 45ms
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🎉 Success! You just made a production-grade secure A2A request!
🔐 Security Deep Dive¶
Let's understand how each security layer works.
Layer 1: Ed25519 Authentication¶
What Changed from Stage 2: - Stage 2: HMAC with shared secret - Stage 3: Ed25519 with public/private keys
Why Ed25519? - ✅ Each party has their own key pair - ✅ No shared secrets to leak - ✅ Can revoke individual clients - ✅ Scales to thousands of clients - ✅ Industry standard (SSH, TLS, etc.)
How It Works:
Client Side:
# authentication.py (client)
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
import json
class SecureClient:
def __init__(self, private_key_path):
# Load client's private key
with open(private_key_path, 'rb') as f:
self.private_key = Ed25519PrivateKey.from_private_bytes(f.read())
def sign_request(self, request_data):
"""Sign request with private key"""
# 1. Create canonical message
message = json.dumps(request_data, sort_keys=True)
# 2. Sign with private key
signature = self.private_key.sign(message.encode())
# 3. Return base64-encoded signature
return base64.b64encode(signature).decode()
Server Side:
# authentication.py (server)
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
class AuthenticationManager:
def __init__(self):
# Load authorized client public keys
self.authorized_keys = self.load_authorized_keys()
def verify_signature(self, request_data, signature, client_id):
"""Verify request signature"""
# 1. Get client's public key
public_key = self.authorized_keys.get(client_id)
if not public_key:
return False, "Unknown client"
# 2. Recreate canonical message
message = json.dumps(request_data, sort_keys=True)
# 3. Verify signature
try:
public_key.verify(
base64.b64decode(signature),
message.encode()
)
return True, "Signature valid"
except Exception as e:
return False, f"Invalid signature: {e}"
Key Management:
# Keys are stored in separate files
authorized_clients/
├── client-123_public.key # Client 123's public key
├── client-456_public.key # Client 456's public key
└── client-789_public.key # Client 789's public key
# Each client has their own private key (never shared!)
client-123/
└── private.key # Client 123's private key (secret!)
Benefits: - Leak of one client's key doesn't affect others - Can revoke individual clients - No key distribution problem - Cryptographically secure
💡 Lesson: Public-key cryptography solves the shared secret problem elegantly.
Layer 2: Replay Protection¶
What It Prevents: Attackers replaying captured valid requests
Two-Part Strategy:
Part 1: Nonces (Number Used Once)
# replay_protection.py
import redis
import uuid
class ReplayProtection:
def __init__(self, redis_client):
self.redis = redis_client
self.nonce_ttl = 300 # 5 minutes
def check_nonce(self, nonce):
"""Check if nonce has been used before"""
# Redis key for this nonce
key = f"nonce:{nonce}"
# Try to set the nonce (atomic operation)
# NX = only set if doesn't exist
# EX = expire after ttl seconds
was_set = self.redis.set(
key,
"used",
nx=True, # Only if not exists
ex=self.nonce_ttl # Expire after 5 min
)
if not was_set:
# Nonce already exists = replay attack!
return False, "Nonce already used (replay attack detected)"
return True, "Nonce is fresh"
def generate_nonce(self):
"""Generate cryptographically random nonce"""
return str(uuid.uuid4())
Part 2: Timestamp Validation
def check_timestamp(self, timestamp):
"""Ensure request is recent"""
import time
now = time.time()
request_time = float(timestamp)
# Check age
age = abs(now - request_time)
# Reject if older than 5 minutes
if age > 300:
return False, f"Request too old ({age}s > 300s)"
# Reject if in future (clock skew tolerance: 60s)
if request_time > now + 60:
return False, "Request timestamp in future"
return True, "Timestamp valid"
Request Format:
{
"client_id": "client-123",
"query": "What's the price of BTC?",
"timestamp": 1703001045.234,
"nonce": "a3d2c9f1-4b3e-4c8f-9a1b-2d3e4f5a6b7c"
}
Complete Validation:
def validate_request(self, request):
# 1. Verify signature
valid, msg = self.auth.verify_signature(
request,
request['signature'],
request['client_id']
)
if not valid:
return False, msg
# 2. Check timestamp
valid, msg = self.check_timestamp(request['timestamp'])
if not valid:
return False, msg
# 3. Check nonce (LAST - only if everything else is valid)
valid, msg = self.check_nonce(request['nonce'])
if not valid:
return False, msg
return True, "Request valid"
Why This Works:
Replay Attack Attempt:
# Attacker captures valid request
captured = {
"client_id": "client-123",
"query": "Transfer $100",
"timestamp": 1703001045.234,
"nonce": "a3d2c9f1-...",
"signature": "valid_sig..."
}
# First replay (immediately)
server.handle(captured) # ❌ Nonce already used!
# Second replay (10 minutes later)
server.handle(captured) # ❌ Timestamp too old!
# Modified replay (new nonce)
captured['nonce'] = "new-nonce"
server.handle(captured) # ❌ Signature doesn't match!
Benefits: - ✅ Prevents replay attacks completely - ✅ Automatic cleanup (nonces expire) - ✅ No state needed beyond 5 minutes - ✅ Works in distributed systems (Redis)
💡 Lesson: Replay protection requires BOTH nonces and timestamps. One alone isn't enough.
Layer 3: 8-Layer Input Validation¶
Philosophy: Defense in depth - multiple validation layers
The 8 Layers:
# validation.py
class ComprehensiveValidator:
# Layer 1: Type Validation
def validate_types(self, request):
"""Ensure correct data types"""
if not isinstance(request.get('query'), str):
return False, "Query must be string"
if not isinstance(request.get('timestamp'), (int, float)):
return False, "Timestamp must be numeric"
return True, "Types valid"
# Layer 2: Size Validation
def validate_sizes(self, request):
"""Check size limits"""
query = request.get('query', '')
if len(query) > 500:
return False, "Query too long (max 500)"
if len(query) < 1:
return False, "Query too short (min 1)"
# Check total request size
import sys
size = sys.getsizeof(str(request))
if size > 10240: # 10KB
return False, f"Request too large ({size} > 10KB)"
return True, "Sizes valid"
# Layer 3: Format Validation
def validate_format(self, request):
"""Check required fields and format"""
required = ['client_id', 'query', 'timestamp', 'nonce']
for field in required:
if field not in request:
return False, f"Missing required field: {field}"
# Validate UUID format for nonce
import uuid
try:
uuid.UUID(request['nonce'])
except ValueError:
return False, "Invalid nonce format"
return True, "Format valid"
# Layer 4: Character Validation
def validate_characters(self, query):
"""Check for dangerous characters"""
# Whitelist approach
import re
allowed_pattern = r'^[a-zA-Z0-9\s\?\.\,\!\-]+$'
if not re.match(allowed_pattern, query):
return False, "Query contains invalid characters"
return True, "Characters valid"
# Layer 5: Content Validation
def validate_content(self, query):
"""Check for known attack patterns"""
# SQL injection patterns
sql_patterns = ['DROP', 'DELETE', 'UPDATE', 'INSERT', '--', ';']
query_upper = query.upper()
for pattern in sql_patterns:
if pattern in query_upper:
return False, f"Suspicious pattern detected: {pattern}"
# Command injection patterns
cmd_patterns = ['|', '&', '$', '`', ';', '\n']
for pattern in cmd_patterns:
if pattern in query:
return False, f"Dangerous character: {pattern}"
return True, "Content valid"
# Layer 6: Semantic Validation
def validate_semantics(self, query):
"""Validate query makes sense"""
# Must mention a coin
coins = ['BTC', 'ETH', 'SOL', 'ADA', 'DOGE', 'BITCOIN', 'ETHEREUM']
query_upper = query.upper()
has_coin = any(coin in query_upper for coin in coins)
if not has_coin:
return False, "Query must mention a cryptocurrency"
# Must have price-related words
price_words = ['PRICE', 'COST', 'VALUE', 'WORTH', 'TRADING']
has_price_word = any(word in query_upper for word in price_words)
if not has_price_word:
return False, "Query must ask about price"
return True, "Semantics valid"
# Layer 7: Business Logic Validation
def validate_business_logic(self, query, client_id):
"""Check business rules"""
# Check if client has permission
if not self.client_has_permission(client_id, 'price_query'):
return False, "Client not authorized for price queries"
# Check if requested coin is supported
mentioned_coins = self.extract_coins(query)
for coin in mentioned_coins:
if coin not in self.SUPPORTED_COINS:
return False, f"Unsupported coin: {coin}"
return True, "Business logic valid"
# Layer 8: Rate Limit Check
def validate_rate_limit(self, client_id):
"""Check if client has exceeded rate limit"""
current_rate = self.rate_limiter.get_current_rate(client_id)
limit = self.rate_limiter.get_limit(client_id)
if current_rate >= limit:
return False, f"Rate limit exceeded ({current_rate}/{limit})"
return True, "Rate limit OK"
Validation Order:
Request → Type → Size → Format → Characters → Content → Semantics → Business → Rate → Process
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
Fast Fast Fast Medium Medium Slow Slow Slow
checks checks checks checks checks checks checks checks
Why 8 Layers? - Each layer catches different types of attacks - Fast checks first (reject bad requests quickly) - Expensive checks last (only for valid-looking requests) - If one layer fails, others still protect
Example Attack Blocked:
# Sophisticated attack that bypasses some layers
malicious = {
"query": "What is the price of BTC" + " " * 400, # ✓ Valid chars
"timestamp": time.time(), # ✓ Valid timestamp
"nonce": str(uuid.uuid4()), # ✓ Valid nonce
}
# Layer 1: ✓ Types correct
# Layer 2: ✓ Size OK (500 chars)
# Layer 3: ✓ Format OK
# Layer 4: ✓ Characters OK
# Layer 5: ✓ No SQL/cmd injection
# Layer 6: ❌ BLOCKED! Semantics invalid (excessive spaces)
💡 Lesson: One validation layer isn't enough. Defense in depth catches sophisticated attacks.
Layer 4: Rate Limiting¶
Algorithm: Token Bucket
How It Works:
# rate_limit.py
import time
import redis
class TokenBucketRateLimiter:
def __init__(self, redis_client):
self.redis = redis_client
def check_rate_limit(self, client_id, max_tokens=100, refill_rate=100):
"""
Token bucket algorithm
max_tokens: Bucket capacity (burst allowance)
refill_rate: Tokens per minute
"""
key = f"rate_limit:{client_id}"
now = time.time()
# Get current bucket state
pipe = self.redis.pipeline()
pipe.hmget(key, 'tokens', 'last_refill')
pipe.expire(key, 3600) # Expire after 1 hour of inactivity
result = pipe.execute()[0]
if result[0] is None:
# First request - initialize bucket
tokens = max_tokens - 1 # Use one token
last_refill = now
else:
# Calculate refill
tokens = float(result[0])
last_refill = float(result[1])
# Time since last refill
time_passed = now - last_refill
# Add tokens (refill_rate per minute)
tokens_to_add = (time_passed / 60.0) * refill_rate
tokens = min(max_tokens, tokens + tokens_to_add)
# Use one token
tokens -= 1
# Check if request allowed
if tokens < 0:
# No tokens left - rate limited!
return False, 0, max_tokens
# Update bucket state
self.redis.hmset(key, {
'tokens': tokens,
'last_refill': now
})
return True, int(tokens), max_tokens
Visual Representation:
Bucket (100 tokens max):
┌─────────────────────────────────────┐
│ 🪙🪙🪙🪙🪙🪙🪙🪙🪙🪙 (45 tokens) │ ← Current state
├─────────────────────────────────────┤
│ Refills at: 100 tokens/minute │
│ Used by: Each request = 1 token │
└─────────────────────────────────────┘
Request arrives:
✅ tokens >= 1 → Allow (tokens - 1)
❌ tokens < 1 → Reject (rate limited)
Time passes:
Every minute: tokens += 100 (up to max)
Usage Example:
# In request handler
allowed, remaining, limit = rate_limiter.check_rate_limit(
client_id="client-123",
max_tokens=100,
refill_rate=100
)
if not allowed:
return {
"error": "Rate limit exceeded",
"retry_after": 60 # seconds
}, 429
# Add rate limit headers
response.headers['X-RateLimit-Limit'] = str(limit)
response.headers['X-RateLimit-Remaining'] = str(remaining)
Benefits: - ✅ Allows bursts (up to 100 requests at once) - ✅ Sustainable rate (100 req/min long-term) - ✅ Fair to all clients - ✅ Automatic recovery - ✅ Distributed (Redis-based)
💡 Lesson: Rate limiting protects your infrastructure and ensures fair usage.
Layer 5: Secure Error Handling¶
Principle: Never leak information through errors
Implementation:
# error_handling.py
import logging
import uuid
class SecureErrorHandler:
def __init__(self):
self.logger = logging.getLogger('security')
def handle_error(self, error, context):
"""
Log detailed error internally,
return generic error to client
"""
# Generate error ID for correlation
error_id = str(uuid.uuid4())
# Log detailed error internally
self.logger.error(
f"Error ID: {error_id}",
extra={
'error_id': error_id,
'error_type': type(error).__name__,
'error_message': str(error),
'context': context,
'traceback': traceback.format_exc()
}
)
# Return generic error to client
return {
'error': self.get_generic_message(error),
'error_id': error_id, # For support requests
'timestamp': time.time()
}
def get_generic_message(self, error):
"""Map internal errors to generic messages"""
if isinstance(error, ValidationError):
return "Invalid request format"
elif isinstance(error, AuthenticationError):
return "Authentication failed"
elif isinstance(error, RateLimitError):
return "Rate limit exceeded"
else:
return "Internal server error"
Before (Stage 1):
{
"error": "KeyError: 'INVALIDCOIN' at line 150 in crypto_server.py",
"traceback": "File /home/user/crypto_server.py...",
"system": "Ubuntu 22.04",
"python": "3.11.2"
}
After (Stage 3):
{
"error": "Invalid request format",
"error_id": "a3d2c9f1-4b3e-4c8f-9a1b-2d3e4f5a6b7c",
"timestamp": 1703001045.234
}
Detailed Log (Internal Only):
ERROR [2024-12-19 15:30:45] security
error_id: a3d2c9f1-4b3e-4c8f-9a1b-2d3e4f5a6b7c
error_type: KeyError
error_message: 'INVALIDCOIN'
client_id: client-123
request_path: /query
user_agent: SecureClient/1.0
ip_address: 192.168.1.100
traceback: [full traceback here]
Benefits: - ✅ Users get helpful but safe messages - ✅ Admins get detailed logs - ✅ Error ID links user reports to logs - ✅ No information disclosure - ✅ Debuggable and auditable
Layer 6: Comprehensive Audit Logging¶
What to Log:
# audit.py
class AuditLogger:
def log_request(self, request, response, duration):
"""Log every request"""
self.logger.info('request', extra={
# Who
'client_id': request.client_id,
'ip_address': request.ip,
# What
'endpoint': request.path,
'method': request.method,
'query': self.sanitize_for_log(request.query),
# When
'timestamp': request.timestamp,
# How
'signature_valid': request.signature_valid,
'nonce': request.nonce,
# Result
'status_code': response.status_code,
'response_time_ms': duration * 1000,
'rate_limit_remaining': response.rate_limit_remaining,
# Context
'user_agent': request.user_agent,
'request_id': request.id
})
def log_security_event(self, event_type, details):
"""Log security events"""
self.logger.warning('security_event', extra={
'event_type': event_type,
'severity': self.get_severity(event_type),
'details': details,
'timestamp': time.time()
})
def sanitize_for_log(self, data):
"""Remove sensitive data before logging"""
# Never log:
# - Private keys
# - Signatures (only log "present/absent")
# - Full tokens
# - Passwords
# - Credit card numbers
# - SSNs
pass
Log Examples:
Normal Request:
{
"level": "INFO",
"event": "request",
"client_id": "client-123",
"endpoint": "/query",
"query": "What's the price of BTC?",
"status": 200,
"response_time_ms": 45,
"timestamp": "2024-12-19T15:30:45Z"
}
Security Event:
{
"level": "WARNING",
"event": "security_event",
"event_type": "replay_attack_detected",
"severity": "HIGH",
"client_id": "client-123",
"nonce": "a3d2c9f1-...",
"details": "Nonce already used",
"timestamp": "2024-12-19T15:30:45Z"
}
Rate Limit Event:
{
"level": "WARNING",
"event": "rate_limit_exceeded",
"client_id": "client-456",
"current_rate": 105,
"limit": 100,
"timestamp": "2024-12-19T15:30:45Z"
}
Benefits: - ✅ Full audit trail - ✅ Detect attacks in real-time - ✅ Investigate incidents - ✅ Prove compliance - ✅ Monitor system health
💡 Lesson: Comprehensive logging is your security camera system.
🏗️ Modular Security Architecture¶
Stage 3 uses a modular design for maintainability:
security/
├── __init__.py
├── authentication.py # Ed25519 signature verification
├── replay_protection.py # Nonce tracking + timestamps
├── validation.py # 8-layer input validation
├── rate_limit.py # Token bucket rate limiting
├── error_handling.py # Secure error responses
├── audit.py # Comprehensive logging
├── keys/ # Key storage
│ ├── server_private.key
│ ├── server_public.key
│ └── authorized_clients/
│ ├── client-123_public.key
│ └── client-456_public.key
└── tests/
├── test_authentication.py
├── test_replay_protection.py
├── test_validation.py
└── test_rate_limit.py
Benefits: - ✅ Each module has one responsibility - ✅ Easy to test individually - ✅ Easy to update one layer - ✅ Reusable across projects - ✅ Clear separation of concerns
Using the Modules:
# secure_crypto_server.py
from security.authentication import AuthenticationManager
from security.replay_protection import ReplayProtection
from security.validation import ComprehensiveValidator
from security.rate_limit import TokenBucketRateLimiter
from security.audit import AuditLogger
from security.error_handling import SecureErrorHandler
class SecureCryptoAgent:
def __init__(self):
# Initialize security modules
self.auth = AuthenticationManager()
self.replay = ReplayProtection(redis_client)
self.validator = ComprehensiveValidator()
self.rate_limiter = TokenBucketRateLimiter(redis_client)
self.audit = AuditLogger()
self.error_handler = SecureErrorHandler()
def handle_request(self, request):
start_time = time.time()
try:
# Security checks (in order)
# 1. Authenticate
valid, msg = self.auth.verify_signature(request)
if not valid:
self.audit.log_security_event('auth_failed', {'reason': msg})
return {'error': msg}, 401
# 2. Check replay
valid, msg = self.replay.check_request(request)
if not valid:
self.audit.log_security_event('replay_detected', {'reason': msg})
return {'error': msg}, 403
# 3. Validate input
valid, msg = self.validator.validate_all(request)
if not valid:
return {'error': msg}, 400
# 4. Check rate limit
allowed, remaining, limit = self.rate_limiter.check_rate_limit(
request['client_id']
)
if not allowed:
return {'error': 'Rate limit exceeded'}, 429
# All security checks passed - process request
response = self.process_query(request['query'])
# Add security headers
response['rate_limit'] = {
'remaining': remaining,
'limit': limit
}
# Log successful request
duration = time.time() - start_time
self.audit.log_request(request, response, duration)
return response, 200
except Exception as e:
# Handle errors securely
return self.error_handler.handle_error(e, request), 500
💪 Hands-On Exercises¶
Exercise 1: Key Management (30 min)¶
Task: Generate keys for 3 clients and configure the server to accept them.
Steps: 1. Generate 3 client key pairs 2. Copy client public keys to server/authorized_clients/ 3. Test that each client can authenticate 4. Try to authenticate with an unauthorized key (should fail)
Deliverable: Working multi-client setup
Exercise 2: Replay Attack Prevention Demo (45 min)¶
Task: Prove that replay attacks are blocked in Stage 3.
Steps: 1. Capture a valid request (including signature) 2. Try to replay it immediately (should fail - nonce used) 3. Try to replay with a new nonce (should fail - signature invalid) 4. Try to replay with old timestamp (should fail - too old)
Code Skeleton:
# Test replay protection
import requests
import time
# Make legitimate request
response1 = client.make_request("What's the price of BTC?")
captured_request = response1.request_data
# Attempt replays
print("Replay 1: Immediate (same nonce)")
response2 = requests.post(url, json=captured_request)
# Should see: "Nonce already used"
print("Replay 2: New nonce")
captured_request['nonce'] = generate_nonce()
response3 = requests.post(url, json=captured_request)
# Should see: "Invalid signature"
print("Replay 3: Old timestamp")
time.sleep(600) # Wait 10 minutes
response4 = requests.post(url, json=captured_request)
# Should see: "Request too old"
Deliverable: Proof that all replay attempts fail.
Exercise 3: Rate Limiting Test (30 min)¶
Task: Trigger rate limiting and observe behavior.
Script:
# rate_limit_test.py
import time
# Send requests rapidly
for i in range(150): # More than limit (100)
response = client.make_request("What's the price of BTC?")
print(f"Request {i+1}: {response.status_code} - "
f"Remaining: {response.headers['X-RateLimit-Remaining']}")
if response.status_code == 429:
print(f"Rate limited after {i+1} requests!")
print(f"Retry-After: {response.json()['retry_after']}s")
break
# Wait and try again
print("Waiting 60 seconds for token refill...")
time.sleep(60)
response = client.make_request("What's the price of BTC?")
print(f"After wait: {response.status_code}")
Expected Output:
Request 1: 200 - Remaining: 99
Request 2: 200 - Remaining: 98
...
Request 100: 200 - Remaining: 0
Request 101: 429 - Remaining: 0
Rate limited after 101 requests!
Retry-After: 60s
Waiting 60 seconds for token refill...
After wait: 200
Deliverable: Proof that rate limiting works as expected.
Exercise 4: Input Validation Bypass Attempts (1 hour)¶
Task: Try to bypass each of the 8 validation layers.
Test Cases:
test_cases = [
# Layer 1: Type validation
{"query": 123}, # Should fail - wrong type
# Layer 2: Size validation
{"query": "A" * 1000}, # Should fail - too long
# Layer 3: Format validation
{"query": "BTC price?"}, # Should fail - missing nonce
# Layer 4: Character validation
{"query": "BTC price<script>"}, # Should fail - invalid chars
# Layer 5: Content validation
{"query": "BTC; DROP TABLE"}, # Should fail - SQL injection
# Layer 6: Semantic validation
{"query": "Hello there"}, # Should fail - no coin mentioned
# Layer 7: Business logic validation
{"query": "Price of INVALIDCOIN"}, # Should fail - unsupported coin
# Layer 8: Rate limit
# (Already tested in Exercise 3)
]
for i, test in enumerate(test_cases):
response = client.make_request(test['query'])
print(f"Test {i+1}: {response.status_code} - {response.json()['error']}")
Deliverable: Documentation of which layer blocks each attack.
Exercise 5: Security Module Reuse (2 hours)¶
Task: Use the security modules to secure a different agent (e.g., weather agent).
Steps: 1. Copy the security/ directory to a new project 2. Create a simple weather agent 3. Apply the same security layers 4. Test that security works
This proves the modules are reusable!
✅ Stage 3 Completion Checklist¶
Congratulations! You've completed the most advanced stage. Let's verify your understanding:
Security Patterns¶
- Understand Ed25519 vs HMAC differences
- Can explain how replay protection works
- Know all 8 validation layers
- Understand token bucket algorithm
- Can implement secure error handling
- Know what to log and what not to log
Implementation Skills¶
- Can generate and manage key pairs
- Can configure multi-client authentication
- Can set up Redis for nonce tracking
- Can implement rate limiting
- Can structure security modules
- Can use Stage 3 as a template
Security Mindset¶
- Understand defense in depth
- Know why each layer matters
- Can identify when security is sufficient
- Appreciate modular security design
- Ready to build production systems
- Can mentor others on security
Practical Experience¶
- Ran all security tests
- Attempted bypass attacks (failed!)
- Monitored audit logs
- Understood rate limiting behavior
- Compared all 3 stages
- Ready to deploy secure agents
🎓 Key Takeaways¶
The Journey¶
Stage 1: "Security? What security?"
Stage 2: "Some security is better than none!" (Wrong!)
Stage 3: "Defense in depth with proper patterns" (Right!)
Critical Lessons¶
1. Asymmetric Cryptography Solves Scale
Public/private keys eliminate the shared secret problem. This is how real systems work.
2. Security is Layered
Multiple independent layers. If one fails, others protect. No single point of failure.
3. Replay Protection is Essential
Authentication without replay protection is incomplete. Always use nonces + timestamps.
4. Validation is Comprehensive
8 layers because attacks are sophisticated. One validation check isn't enough.
5. Modular Design is Maintainable
Security modules that are independent, testable, and reusable. Don't reinvent the wheel.
6. Monitoring is Critical
Comprehensive logging enables detection, investigation, and compliance. Log everything (securely).
7. Production-Ready Means Complete
All security layers implemented. No "we'll add that later." Secure by default.
🚀 Production Deployment Considerations¶
Ready to deploy? Here's your checklist:
Infrastructure¶
- TLS/HTTPS: Use Let's Encrypt or commercial certificates
- Redis: Deploy Redis Cluster for high availability
- Load Balancer: Distribute traffic across multiple instances
- Monitoring: Prometheus + Grafana for metrics
- Logging: ELK Stack or CloudWatch for log aggregation
- Secrets Management: HashiCorp Vault or AWS Secrets Manager
Security¶
- Key Rotation: Plan for rotating keys every 90 days
- Backup: Encrypted backups of keys and configuration
- Incident Response: Plan for handling security incidents
- Penetration Testing: Third-party security audit
- Compliance: GDPR, HIPAA, PCI-DSS as needed
- DDoS Protection: CloudFlare or AWS Shield
Operations¶
- Health Checks: Liveness and readiness probes
- Auto-scaling: Based on CPU/memory/request rate
- Alerts: PagerDuty or OpsGenie for critical alerts
- Runbooks: Documented procedures for common issues
- Disaster Recovery: Backup and restore procedures
- Update Process: Rolling updates with rollback plan
Testing¶
- Unit Tests: 80%+ code coverage
- Integration Tests: Test security modules together
- Load Tests: Verify performance under load
- Security Tests: Automated vulnerability scanning
- Chaos Engineering: Test failure scenarios
- Performance Tests: Response time and throughput
📚 Additional Resources¶
Deep Dives¶
Ed25519 Cryptography: - Ed25519 Specification (RFC 8032) - Why Ed25519 for SSH Keys - Cryptography Best Practices
Rate Limiting Algorithms: - Token Bucket Algorithm Explained - Rate Limiting Strategies (Google Cloud) - Distributed Rate Limiting
Security Best Practices: - OWASP Top 10 - OWASP API Security Project - CWE Top 25
Related Documentation¶
Code Files¶
❓ FAQ¶
"Is Stage 3 really production-ready?"¶
Yes! Stage 3 implements industry-standard patterns used in: - Banking systems - Healthcare applications - Enterprise platforms - Government services
However, you should still: - Conduct security audits - Perform penetration testing - Monitor in production - Have an incident response plan
"Why 9/10 instead of 10/10?"¶
Perfect security doesn't exist. The remaining 1 point accounts for: - Unknown vulnerabilities - Implementation bugs - Configuration errors - Zero-day exploits - Human factors
Stage 3 is "excellent" but vigilance is always required.
"Can I simplify Stage 3 for my use case?"¶
Only if you understand the trade-offs!
Low-risk internal tools might be okay with: - HMAC instead of Ed25519 - Simpler validation - Higher rate limits
But never remove: - Authentication - Replay protection - Input validation - Audit logging
"How do I know if I need all 8 validation layers?"¶
Ask: 1. What's at stake? (Money, health data, personal info = need all 8) 2. Who are the users? (Public internet = need all 8) 3. What are regulations? (GDPR, HIPAA, PCI-DSS = need all 8)
If any answer is "high risk," keep all 8 layers.
"What's the performance impact?"¶
Stage 3 adds ~20-50ms latency: - Ed25519 verification: ~5ms - Nonce check (Redis): ~2ms - 8-layer validation: ~5-10ms - Rate limit check: ~2ms - Audit logging: ~5ms
Worth it? Absolutely. Security overhead is tiny compared to breach costs.
"Can I use Stage 3 modules in other languages?"¶
The patterns translate directly: - Python: Use this code! - JavaScript/TypeScript: Port the modules - Go: Similar patterns exist - Java: Spring Security has equivalent - Rust: Actix-web + similar crates
The security principles are language-agnostic.
🎯 What's Next?¶
You've Mastered:¶
- ✅ Production-grade security patterns
- ✅ Asymmetric cryptography
- ✅ Defense in depth
- ✅ Modular security architecture
- ✅ Deployment considerations
- ✅ How to build secure systems
Where to Go from Here¶
Build Your Own Secure Agent: 1. Choose a domain (weather, news, analytics, etc.) 2. Copy the security/ modules from Stage 3 3. Apply the patterns you've learned 4. Test thoroughly 5. Deploy with confidence!
Explore Other Examples: - Credit Report Example - File upload security + PII - Task Collaboration Example - Session management - MCP Integration - Combine A2A + MCP
Deepen Your Knowledge: - A2A Security Best Practices - Authentication Tags - Threat Modeling
Share Your Knowledge: - Teach others using these stages - Contribute improvements to the docs - Share your implementations - Help others build secure systems
🎉 Congratulations!¶
You've completed the entire 3-stage security journey! You now have:
Knowledge: - Deep understanding of security patterns - Knowledge of industry standards - Awareness of common pitfalls - Appreciation for defense in depth
Skills: - Can implement production security - Can use cryptographic libraries correctly - Can structure secure systems - Can deploy with confidence
Judgment: - Can evaluate security claims - Can identify incomplete security - Can make informed trade-offs - Can mentor others
You're ready to build secure multi-agent systems! 🚀
This is a significant achievement. Many developers never reach this level of security understanding. You've put in the work, and it shows.
Document Version: 1.0
Stage: 3 of 3 (Production-Ready)
Last Updated: December 2024
Maintained By: Robert Fischer (robert@fischer3.net)
Code Location: /examples/a2a_crypto_example/security/
Use this as your template! Stage 3 is designed to be copied and adapted for your own secure A2A agents.
🔒 Security is a journey, not a destination. Stay vigilant, keep learning, and always assume attackers are trying. With these patterns, you're well-equipped to defend! 💪
Now go build something secure! 🎯