Developer Security Checklist

Use this checklist when writing new code or modifying existing code to ensure security best practices.

Before Writing Code

  • Understand the security implications of your changes
  • Identify affected security controls (check governance/config/security-control-mapping.yml)
  • Review relevant security documentation
  • Consider threat model for your changes

Input Validation

  • Validate all user inputs at boundaries
  • Sanitize inputs before processing
  • Use type-safe APIs (Rust's type system)
  • Reject invalid inputs early
  • Validate data from external sources (network, files, databases)

Examples:

#![allow(unused)]
fn main() {
// ✅ Good: Validate input
fn process_amount(amount: u64) -> Result<u64, Error> {
    if amount > MAX_AMOUNT {
        return Err(Error::AmountTooLarge);
    }
    Ok(amount)
}

// ❌ Bad: No validation
fn process_amount(amount: u64) -> u64 {
    amount // Could overflow
}
}

Authentication & Authorization

  • Implement proper authentication (if applicable)
  • Check authorization before sensitive operations
  • Use principle of least privilege
  • Verify permissions at every boundary
  • Don't trust client-side authorization checks

Examples:

#![allow(unused)]
fn main() {
// ✅ Good: Check authorization
fn transfer_funds(from: Account, to: Account, amount: u64) -> Result<(), Error> {
    if !from.has_permission(Permission::Transfer) {
        return Err(Error::Unauthorized);
    }
    // ... transfer logic
}

// ❌ Bad: No authorization check
fn transfer_funds(from: Account, to: Account, amount: u64) {
    // ... transfer logic without checking permissions
}
}

Cryptographic Operations

  • Use well-tested cryptographic libraries (secp256k1, bitcoin_hashes)
  • Never hardcode keys or secrets
  • Use cryptographically secure random number generation
  • Follow Bitcoin standards (BIP32, BIP39, BIP44)
  • Verify signatures completely
  • Use constant-time operations where needed (avoid timing attacks)

Examples:

#![allow(unused)]
fn main() {
// ✅ Good: Use secure random
use rand::rngs::OsRng;
let mut rng = OsRng;
let key = secp256k1::SecretKey::new(&mut rng);

// ❌ Bad: Insecure random
let key = secp256k1::SecretKey::from_slice(&[1, 2, 3, ...])?;
}

Consensus & Protocol

  • Implement consensus rules exactly as specified
  • Validate all protocol messages
  • Handle network errors gracefully
  • Prevent DoS attacks (rate limiting, resource limits)
  • Don't bypass consensus validation

Examples:

#![allow(unused)]
fn main() {
// ✅ Good: Validate consensus rules
fn validate_block(block: &Block) -> Result<(), ConsensusError> {
    if !block.verify_merkle_root() {
        return Err(ConsensusError::InvalidMerkleRoot);
    }
    // ... more validation
}

// ❌ Bad: Skip validation
fn validate_block(block: &Block) -> Result<(), ConsensusError> {
    Ok(()) // No validation!
}
}

Memory Safety

  • Prefer safe Rust code
  • Document and justify any unsafe code
  • Ensure proper resource cleanup (Drop trait)
  • Avoid memory leaks (use RAII patterns)
  • Check bounds before array/vector access

Examples:

#![allow(unused)]
fn main() {
// ✅ Good: Safe Rust
let value = vec.get(index).ok_or(Error::OutOfBounds)?;

// ❌ Bad: Unsafe indexing
let value = vec[index]; // Could panic
}

Error Handling

  • Don't leak sensitive information in errors
  • Use specific error types
  • Handle all error cases
  • Fail securely (default deny)
  • Log errors appropriately (no sensitive data)

Examples:

#![allow(unused)]
fn main() {
// ✅ Good: Generic error message
return Err(Error::AuthenticationFailed); // Doesn't reveal why

// ❌ Bad: Leaks information
return Err(Error::InvalidPassword("user123")); // Reveals username
}

Dependencies

  • Use minimal dependencies
  • Keep dependencies up-to-date
  • Pin consensus-critical dependencies to exact versions
  • Check for known vulnerabilities (cargo audit)
  • Review dependency licenses

Examples:

# ✅ Good: Pin critical dependencies
[dependencies]
secp256k1 = "=0.28.0"  # Exact version for consensus-critical

# ❌ Bad: Allow version ranges for critical code
[dependencies]
secp256k1 = "^0.28"  # Could break consensus

Testing

  • Write security-focused tests
  • Test edge cases and boundary conditions
  • Test error handling paths
  • Include fuzzing for consensus/protocol code
  • Test with malicious inputs
  • Achieve adequate test coverage

Examples:

#![allow(unused)]
fn main() {
#[test]
fn test_amount_overflow() {
    assert!(process_amount(u64::MAX).is_err());
}

#[test]
fn test_invalid_signature() {
    let invalid_sig = vec![0u8; 64];
    assert!(verify_signature(&invalid_sig).is_err());
}
}

Documentation

  • Document security assumptions
  • Document threat model considerations
  • Document security implications of design decisions
  • Update security documentation if adding new controls
  • Document configuration security requirements

Code Review

  • Request security review for security-sensitive code
  • Address security review feedback
  • Update security control mapping if needed
  • Ensure appropriate governance tier is selected

Post-Implementation

  • Verify security tests pass
  • Check for new security advisories
  • Update threat model if needed
  • Document any security trade-offs

Security Control Categories

Category A: Consensus Integrity

  • Genesis block implementation
  • SegWit witness verification
  • Taproot support
  • Script execution limits
  • UTXO set validation

Category B: Cryptographic

  • Maintainer key management
  • Emergency signature verification
  • Multisig threshold enforcement
  • Key derivation and storage

Category C: Governance

  • Tier classification logic
  • Database query implementation
  • Cross-layer file verification

Category D: Data Integrity

  • Audit log hash chain
  • OTS timestamping
  • State synchronization

Category E: Input Validation

  • GitHub webhook signature verification
  • Input sanitization
  • SQL injection prevention
  • API rate limiting

Resources