- _fernet() now returns MultiFernet built from EXTENSION_STORAGE_ENCRYPTION_KEYS
(falls back to SECRET_KEY). New encryptions use the first key; all keys are
tried on decryption, giving zero-downtime rotation.
- New CLI command: superset rotate-extension-storage-keys
Re-encrypts every is_encrypted row with the current (first) key via
MultiFernet.rotate(). Run after prepending a new key and restarting.
- EXTENSION_STORAGE_ENCRYPTION_KEYS added to config.py with rotation docs.
- Re-enable is_encrypted=true in notebook saves now that rotation is supported.
- Tests: key_rotation_roundtrip and multi_fernet_decrypts_old_key.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>