mirror of
https://github.com/beshu-tech/deltaglider.git
synced 2026-04-10 11:03:45 +02:00
security: Make encryption always-on with auto-cleanup
BREAKING CHANGES: - Encryption is now ALWAYS enabled (cannot be disabled) - Removed DG_CACHE_ENCRYPTION environment variable Security Enhancements: - Encryption is mandatory for all cache operations - Ephemeral encryption keys per process (forward secrecy) - Automatic deletion of corrupted cache files on decryption failures - Auto-cleanup on both decryption failures and SHA mismatches Changes: - Removed DG_CACHE_ENCRYPTION toggle from CLI and SDK - Updated EncryptedCache to auto-delete corrupted files - Simplified cache initialization (always wrapped with encryption) - DG_CACHE_ENCRYPTION_KEY remains optional for persistent keys Documentation: - Updated CLAUDE.md with encryption always-on behavior - Updated CHANGELOG.md with breaking changes - Clarified security model and auto-cleanup behavior Testing: - All 119 tests passing with encryption always-on - Type checking: 0 errors (mypy) - Linting: All checks passed (ruff) Rationale: - Zero-trust cache architecture requires encryption - Corrupted cache is security risk - auto-deletion prevents exploitation - Ephemeral keys provide maximum security by default - Users who need cross-process sharing can opt-in with persistent keys 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -164,9 +164,17 @@ class EncryptedCache(CachePort):
|
||||
decrypted_data = self._cipher.decrypt(encrypted_data)
|
||||
except Exception as e:
|
||||
# Fernet raises InvalidToken for tampering/wrong key
|
||||
# SECURITY: Auto-delete corrupted cache files
|
||||
try:
|
||||
encrypted_path.unlink(missing_ok=True)
|
||||
# Clean up mapping
|
||||
if key in self._plaintext_sha_map:
|
||||
del self._plaintext_sha_map[key]
|
||||
except Exception:
|
||||
pass # Best effort cleanup
|
||||
raise CacheCorruptionError(
|
||||
f"Decryption failed for {bucket}/{prefix}: {e}. "
|
||||
f"Cache may be corrupted or key mismatch."
|
||||
f"Corrupted cache deleted automatically."
|
||||
) from e
|
||||
|
||||
# Validate SHA of decrypted content
|
||||
@@ -174,9 +182,18 @@ class EncryptedCache(CachePort):
|
||||
|
||||
actual_sha = hashlib.sha256(decrypted_data).hexdigest()
|
||||
if actual_sha != expected_sha:
|
||||
# SECURITY: Auto-delete corrupted cache files
|
||||
try:
|
||||
encrypted_path.unlink(missing_ok=True)
|
||||
# Clean up mapping
|
||||
if key in self._plaintext_sha_map:
|
||||
del self._plaintext_sha_map[key]
|
||||
except Exception:
|
||||
pass # Best effort cleanup
|
||||
raise CacheCorruptionError(
|
||||
f"Decrypted content SHA mismatch for {bucket}/{prefix}: "
|
||||
f"expected {expected_sha}, got {actual_sha}"
|
||||
f"expected {expected_sha}, got {actual_sha}. "
|
||||
f"Corrupted cache deleted automatically."
|
||||
)
|
||||
|
||||
# Write decrypted content to temporary file
|
||||
|
||||
@@ -75,13 +75,9 @@ def create_service(
|
||||
# Filesystem-backed with Content-Addressed Storage
|
||||
base_cache = ContentAddressedCache(cache_dir, hasher)
|
||||
|
||||
# Apply encryption if enabled
|
||||
enable_encryption = os.environ.get("DG_CACHE_ENCRYPTION", "true").lower() == "true"
|
||||
cache: CachePort
|
||||
if enable_encryption:
|
||||
cache = EncryptedCache.from_env(base_cache)
|
||||
else:
|
||||
cache = base_cache
|
||||
# Always apply encryption with ephemeral keys (security hardening)
|
||||
# Encryption key is optional via DG_CACHE_ENCRYPTION_KEY (ephemeral if not set)
|
||||
cache: CachePort = EncryptedCache.from_env(base_cache)
|
||||
|
||||
clock = UtcClockAdapter()
|
||||
logger = StdLoggerAdapter(level=log_level)
|
||||
|
||||
@@ -1159,13 +1159,9 @@ def create_client(
|
||||
# Filesystem-backed with Content-Addressed Storage
|
||||
base_cache = ContentAddressedCache(cache_dir, hasher)
|
||||
|
||||
# Apply encryption if enabled (default: true)
|
||||
enable_encryption = os.environ.get("DG_CACHE_ENCRYPTION", "true").lower() == "true"
|
||||
cache: CachePort
|
||||
if enable_encryption:
|
||||
cache = EncryptedCache.from_env(base_cache)
|
||||
else:
|
||||
cache = base_cache
|
||||
# Always apply encryption with ephemeral keys (security hardening)
|
||||
# Encryption key is optional via DG_CACHE_ENCRYPTION_KEY (ephemeral if not set)
|
||||
cache: CachePort = EncryptedCache.from_env(base_cache)
|
||||
|
||||
clock = UtcClockAdapter()
|
||||
logger = StdLoggerAdapter(level=log_level)
|
||||
|
||||
Reference in New Issue
Block a user