README
¶
Checkpoint Signing Example
This example demonstrates how to use HMAC-SHA256 checkpoint signing to ensure checkpoint integrity and prevent tampering.
Overview
Checkpoint signing provides cryptographic verification that checkpoints haven't been tampered with. This is critical for:
- Security: Prevent privilege escalation through checkpoint modification
- Integrity: Detect unauthorized state manipulation
- Compliance: Maintain audit trails with tamper-evident storage
Features Demonstrated
- Basic Signing: Sign checkpoints on save, verify on load
- Tampering Detection: Automatic detection of modified checkpoints
- Key Management: How different keys produce different signatures
- Production Integration: Using signed checkpoints in graph workflows
How It Works
Signing Process
When a checkpoint is saved with signing enabled:
- A deterministic representation of the checkpoint is created
- HMAC-SHA256 signature is computed using the signing key
- Signature is stored with the checkpoint
Verification Process
When a checkpoint is loaded with signing enabled:
- Expected signature is recomputed from checkpoint data
- Stored signature is compared using constant-time comparison
- Load fails if signatures don't match (tampering detected)
Signed Fields
The signature covers:
- RunID
- Superstep
- Version
- Timestamp
- State (all keys and values, sorted) - includes message history via "messages" key
- CompletedNodes (sorted)
- PausedNodes (sorted)
Not signed: Signature field itself (circular dependency)
Usage
Basic Setup
// Generate secure signing key (32+ bytes recommended)
signingKey := make([]byte, 32)
rand.Read(signingKey)
// Create checkpointer with signing enabled
checkpointer := checkpoint.NewInMemoryCheckpointer(
checkpoint.WithSigning(signingKey),
)
Graph Workflow with Signing
compiled, err := workflow.Compile(ctx,
graph.WithModel(model),
)
result, err := graph.Last(compiled.Run(ctx, messages,
graph.WithCheckpointer(checkpointer),
graph.WithRunID("secure-run"),
))
Manual Signature Verification
// Sign checkpoint
signature, err := checkpoint.SignCheckpoint(cp, signingKey)
cp.Signature = signature
// Verify checkpoint
err := checkpoint.VerifyCheckpoint(cp, signingKey)
if err != nil {
// Tampering detected!
}
Running the Example
cd examples/checkpoint_signing
go run main.go
Expected Output
=== Checkpoint Signing Example ===
1. Basic Checkpoint Signing and Verification
✓ Generated signing key (32 bytes)
✓ Checkpoint saved with signature
✓ Checkpoint loaded and verified (signature: 32 bytes)
State: map[counter:42 status:running]
2. Tampering Detection
✓ Saved checkpoint with balance: 1000
⚠ Simulating tampering attack
✓ Tampering detected! Error: checkpoint signature verification failed
3. Wrong Key Detection
✓ Saved checkpoint with Key #1
⚠ Attempting to load with Key #2 (wrong key)
✓ Verification would fail: signature mismatch
4. Production Graph Execution with Signed Checkpoints
✓ Generated production signing key
✓ Starting workflow (runID: production-run)
✓ Workflow completed
✓ Checkpoint signed and verified (32 bytes signature)
✓ Manual signature verification successful
Security Considerations
Key Management
- Generate Secure Keys: Use
crypto/randto generate keys (minimum 32 bytes) - Store Securely: Never commit keys to version control
- Rotate Regularly: Implement key rotation policies
- Environment Variables: Load keys from secure environment variables
signingKey := []byte(os.Getenv("CHECKPOINT_SIGNING_KEY"))
if len(signingKey) < 32 {
log.Fatal("Invalid signing key: must be at least 32 bytes")
}
Production Best Practices
- Use Persistent Storage: Combine with DynamoDB/SQL checkpointers for durability
- Monitor Verification Failures: Alert on signature mismatches (potential attacks)
- Audit Logging: Log all checkpoint save/load operations
- Key Backup: Maintain secure backups of signing keys
Threat Model
Protects Against:
- Unauthorized checkpoint modification
- Privilege escalation via state tampering
- Replay attacks with old checkpoints (via timestamp signing)
Does NOT Protect Against:
- Key theft (if attacker has the key, they can sign checkpoints)
- Deletion attacks (signing doesn't prevent checkpoint deletion)
- Runtime memory tampering (signing only protects stored checkpoints)
Implementation Details
HMAC-SHA256
- Algorithm: HMAC (Hash-based Message Authentication Code) with SHA-256
- Output: 32-byte signature
- Security: Provides cryptographic authentication and integrity
Deterministic Encoding
To ensure consistent signatures across platforms:
- Uses
binary.BigEndianfor integer encoding - Sorts all map keys and string slices
- Validates timestamps and supersteps are non-negative
Timing Attack Resistance
Uses hmac.Equal() for constant-time signature comparison to prevent timing attacks.
Related Examples
- Checkpointing - Basic checkpoint usage without signing
- Time Travel - Loading checkpoints from specific supersteps
References
- SECURITY.md - Security considerations and threat model
- Checkpoint Package - API documentation
- HMAC RFC 2104 - HMAC specification
Click to show internal directories.
Click to hide internal directories.