Migrating Guide: v2.0.0-pre12
This version introduces significant security improvements to Familia's identifier system, including verifiable identifiers with HMAC signatures, scoped identifier namespaces, and hardened external identifier derivation to prevent potential security vulnerabilities.
VerifiableIdentifier Feature
Overview
The new Familia::VerifiableIdentifier module allows applications to create and verify identifiers with embedded HMAC signatures. This enables stateless confirmation that an identifier was generated by your application, preventing forged IDs from malicious sources.
Basic Usage
class Customer < Familia::Horreum
feature :verifiable_identifier
# Required: UnsortedSet the HMAC secret (do this once in your app initialization)
# Generate with: SecureRandom.hex(64)
ENV['VERIFIABLE_ID_HMAC_SECRET'] = 'your_64_character_hex_secret'
end
# Generate a verifiable identifier
customer = Customer.new
verifiable_id = customer.generate_verifiable_id
# => "cust_1234567890abcdef_a1b2c3d4e5f6789..."
# Verify the identifier later (stateless verification)
if Customer.verified_identifier?(verifiable_id)
# Identifier is valid and was generated by this application
original_id = Customer.extract_identifier(verifiable_id)
customer = Customer.new(original_id)
else
# Identifier is forged or corrupted
raise SecurityError, "Invalid identifier"
end
Scoped VerifiableIdentifier
The new scope parameter enables cryptographically isolated identifier namespaces for multi-tenant, multi-domain, or multi-environment applications.
Before (Global Scope)
# All identifiers share the same cryptographic space
admin_id = admin.generate_verifiable_id
user_id = user.generate_verifiable_id
# Risk: Cross-contamination between different contexts
After (Scoped Namespaces)
# Production environment
prod_customer_id = customer.generate_verifiable_id(scope: 'production')
prod_admin_id = admin.generate_verifiable_id(scope: 'production:admin')
# Development environment
dev_customer_id = customer.generate_verifiable_id(scope: 'development')
# Multi-tenant application
tenant_a_id = user.generate_verifiable_id(scope: "tenant:#{tenant_a.id}")
tenant_b_id = user.generate_verifiable_id(scope: "tenant:#{tenant_b.id}")
# Verification requires matching scope
Customer.verified_identifier?(prod_customer_id, scope: 'production') # => true
Customer.verified_identifier?(prod_customer_id, scope: 'development') # => false
Scope Benefits:
- Multi-tenant isolation: Tenant A cannot forge identifiers for Tenant B
- Environment separation: Production IDs cannot be used in development
- Role-based security: Admin scopes separate from user scopes
- Full backward compatibility: Existing code without scopes continues to work
Key Management
Secure Secret Generation
# Generate a cryptographically secure HMAC secret
require 'securerandom'
secret = SecureRandom.hex(64) # 512-bit secret
puts "VERIFIABLE_ID_HMAC_SECRET=#{secret}"
Environment Configuration
# config/application.rb or equivalent
# UnsortedSet this BEFORE any VerifiableIdentifier usage
ENV['VERIFIABLE_ID_HMAC_SECRET'] = Rails.application.credentials.verifiable_id_secret
# Or configure programmatically
Familia::VerifiableIdentifier.hmac_secret = your_secret_string
ObjectIdentifier Feature Improvements
Method Renaming
Method names have been updated for clarity and consistency:
Before
customer = Customer.new
objid = customer.generate_objid # Unclear what this generates
extid = Customer.generate_extid(objid) # Less secure class method
After
customer = Customer.new
objid = customer.generate_object_identifier # Clear: generates object ID
extid = customer.derive_external_identifier # Clear: derives from objid, instance method
Migration:
- Replace
generate_objid→generate_object_identifier - Replace
generate_external_identifier→derive_external_identifier - Remove usage of
generate_extid(deprecated for security reasons)
Provenance Tracking
ObjectIdentifier now tracks which generator was used for each identifier:
class Customer < Familia::Horreum
feature :object_identifier
# Configure generator type
object_identifier_generator :uuid_v7 # or :uuid_v4, :hex, custom proc
end
customer = Customer.new
objid = customer.generate_object_identifier
# Provenance information available
puts customer.object_identifier_generator_type # => :uuid_v7
puts customer.objid_format # => :uuid (normalized format)
Benefits:
- Security auditing: Know which generator created each identifier
- Format normalization: Eliminates ambiguity between UUID and hex formats
- Migration support: Track mixed generator usage during transitions
ExternalIdentifier Security Hardening
Provenance Validation
ExternalIdentifier now validates that objid values come from the ObjectIdentifier feature before deriving external identifiers.
Before (Potential Security Risk)
# Could derive external IDs from any string, including malicious input
extid = customer.derive_external_identifier("malicious_input")
After (Hardened)
customer = Customer.new
customer.generate_object_identifier # Must generate objid first
# Only works with validated objid from ObjectIdentifier feature
extid = customer.derive_external_identifier # Secure: uses validated objid
Improved Security Model
External identifiers are now derived using the internal objid as a seed for a new random value, rather than directly deriving from objid.
Before
# Direct derivation could leak information about objid
extid = hash(objid) # Information leakage risk
After
# objid used as seed for new random value
extid = secure_hash(objid + additional_entropy) # No information leakage
Error Handling Improvements
External identifier now raises clear errors for invalid usage:
class Customer < Familia::Horreum
feature :external_identifier # Missing: object_identifier dependency
end
customer = Customer.new
# Raises ExternalIdentifierError instead of returning nil
customer.derive_external_identifier
# => Familia::ExternalIdentifierError: Model does not have an objid field
Migration Steps
1. Update Method Names
Replace deprecated method names in your codebase:
# Search and replace patterns:
grep -r "generate_objid" --include="*.rb" .
# Replace with: generate_object_identifier
grep -r "generate_external_identifier" --include="*.rb" .
# Replace with: derive_external_identifier
grep -r "generate_extid" --include="*.rb" .
# Remove usage - use derive_external_identifier instead
2. Add HMAC Secret for VerifiableIdentifier
If you plan to use VerifiableIdentifier:
# Generate secret
require 'securerandom'
secret = SecureRandom.hex(64)
# Add to your environment configuration
# .env, Rails credentials, or similar
VERIFIABLE_ID_HMAC_SECRET=your_generated_secret
# Verify configuration
puts ENV['VERIFIABLE_ID_HMAC_SECRET']&.length # Should be 128 characters
3. Update ExternalIdentifier Usage
Ensure proper dependency chain:
class YourModel < Familia::Horreum
# Required: ObjectIdentifier must come before ExternalIdentifier
feature :object_identifier
feature :external_identifier
# Configure generator if needed
object_identifier_generator :uuid_v7
end
# Usage pattern
model = YourModel.new
model.generate_object_identifier # Generate objid first
extid = model.derive_external_identifier # Then derive external ID
4. Review Security-Sensitive Code
Audit any code that processes identifiers from external sources:
# Before: Potentially unsafe
def process_identifier(external_id)
# Could process forged identifiers
model = Model.find_by_external_id(external_id)
end
# After: With verification
def process_identifier(verifiable_id)
# Verify identifier authenticity first
unless Model.verified_identifier?(verifiable_id)
raise SecurityError, "Invalid identifier"
end
original_id = Model.extract_identifier(verifiable_id)
model = Model.new(original_id)
end
Breaking Changes
generate_extidremoved - Use instance-levelderive_external_identifierinstead- ExternalIdentifier validation - Now raises
ExternalIdentifierErrorinstead of returningnilfor models without objid - Method names changed -
generate_objid→generate_object_identifier,generate_external_identifier→derive_external_identifier
New Security Capabilities
- Cryptographic identifier verification - Prevent forged IDs with HMAC signatures
- Scoped namespaces - Isolate identifiers by tenant, environment, or role
- Provenance tracking - Know which generator created each identifier
- Information leakage prevention - External IDs no longer directly expose internal IDs
- Input validation - Clear error messages for invalid operations
Testing Your Migration
# Test ObjectIdentifier changes
model = YourModel.new
objid = model.generate_object_identifier
extid = model.derive_external_identifier
puts "Generator: #{model.object_identifier_generator_type}"
# Test VerifiableIdentifier (if using)
vid = model.generate_verifiable_id
puts "Verifiable: #{YourModel.verified_identifier?(vid)}"
# Test scoped identifiers (if using)
scoped_vid = model.generate_verifiable_id(scope: 'production')
puts "Scoped valid: #{YourModel.verified_identifier?(scoped_vid, scope: 'production')}"
puts "Wrong scope: #{YourModel.verified_identifier?(scoped_vid, scope: 'development')}"