Secure Storage
fleche supports securing filesystem-based caches (PickleFile and CloudpickleFile) using HMAC-SHA256 signatures. This protects against untrusted code execution (RCE) via tampered pickle files.
Security is controlled via the FLECHE_SECRET_KEY environment variable, which accepts a hex-encoded secret key.
[ ]:
import os
from pathlib import Path
import tempfile
from fleche.storage.pickle_file import PickleFile
# Set a strong hex-encoded secret key
os.environ["FLECHE_SECRET_KEY"] = "deadbeefcafe0123456789abcdef0123456789abcdef0123456789abcdef0123"
# Initialize a storage backend. It will automatically load the key from the environment.
temp_dir = tempfile.TemporaryDirectory()
storage = PickleFile.with_pickle(root=temp_dir.name)
# Save a value
value_to_cache = {"status": "secure", "data": [1, 2, 3]}
digest_key = storage.save(value_to_cache)
print(f"Saved data with digest key: {digest_key[:8]}...")
When we load the data, fleche automatically verifies the signature. If it matches, the data is deserialized.
[2]:
loaded_value = storage.load(digest_key)
print("Loaded securely:", loaded_value)
Loaded securely: {'status': 'secure', 'data': [1, 2, 3]}
Tampering Protection
If an attacker tampers with the file, the signature verification will fail, preventing the potentially malicious payload from being deserialized.
[3]:
file_path = Path(temp_dir.name) / digest_key
original_content = file_path.read_bytes()
# Simulate an attacker appending malicious code or modifying the data
tampered_content = b"malicious_payload" + original_content
file_path.write_bytes(tampered_content)
try:
storage.load(digest_key)
except KeyError as e:
print("Security Error Caught:", e)
Invalid signature for cache entry. Potential tampering or key mismatch.
Security Error Caught: ('c38b58619f23f9507daf70dc7ef009512035480102031e8345c7e95fa254dbeb', 'Value present but failed signature check.')
Key Rotation
To support key rotation without invalidating existing caches, you can provide a colon-separated list of keys. fleche will sign new entries with the first key, and verify using any of the keys.
[ ]:
# We roll out a new key, but keep the old one for reading
os.environ["FLECHE_SECRET_KEY"] = "aabbccdd0011223344556677aabbccdd0011223344556677aabbccdd00112233:deadbeefcafe0123456789abcdef0123456789abcdef0123456789abcdef0123"
storage_rotated = PickleFile.with_pickle(root=temp_dir.name)
# Restore the original (untampered) file to test reading with the old key
file_path.write_bytes(original_content)
# It successfully loads using the second key in the list!
rotated_loaded_value = storage_rotated.load(digest_key)
print("Loaded with rotated key:", rotated_loaded_value)