How the Open Notebook Credential Model Encrypts API Keys for SurrealDB Storage
The Credential model in Open Notebook automatically encrypts API keys using Fernet-style AES-CBC encryption before storing them in SurrealDB, ensuring plaintext secrets never touch the database while maintaining seamless round-trip functionality through SecretStr handling.
The Open Notebook project handles sensitive provider API keys through a secure domain model that integrates transparent encryption with SurrealDB persistence. By leveraging Pydantic's SecretStr type and custom serialization hooks, the Credential model ensures that encryption and decryption occur automatically during database operations. This architecture guarantees that raw API keys remain encrypted at rest without requiring manual intervention from application developers.
Encryption Architecture Overview
The encryption system operates transparently during the save and load lifecycle of credential records. Rather than storing plaintext secrets, the model intercepts data before it reaches SurrealDB and transforms it into ciphertext using symmetric encryption.
Automatic Encryption on Persistence
When a Credential instance is saved to SurrealDB, the model overrides the _prepare_save_data method to intercept the api_key field. Located in open_notebook/domain/credential.py at lines 34-39, this method extracts the raw secret value from the Pydantic SecretStr object using get_secret_value(), then passes it to the encrypt_value utility function.
The resulting ciphertext replaces the plaintext in the data payload sent to the database. This ensures that the SurrealDB disk storage contains only encrypted values, even if the database files are compromised.
Transparent Decryption on Retrieval
During record retrieval, the _from_db_row method (lines 79-84 in open_notebook/domain/credential.py) handles the reverse transformation. When SurrealDB returns a credential record, the raw encrypted string from the api_key column is passed to decrypt_value. The decrypted plaintext is then wrapped back into a SecretStr instance before being assigned to the model attribute.
This round-trip preservation means that application code always interacts with SecretStr objects, regardless of whether the credential was just created or loaded from persistent storage.
Core Encryption Implementation
The cryptographic operations reside in open_notebook/utils/encryption.py, which provides the encrypt_value and decrypt_value functions. These utilities implement Fernet-style AES-CBC encryption using a symmetric key derived from the ENCRYPTION_KEY environment variable.
The encryption scheme provides both confidentiality and integrity protection, ensuring that stored API keys cannot be read or tampered with withut the correct encryption key.
Practical Usage Examples
The following example demonstrates creating and persisting an encrypted credential:
from open_notebook.domain.credential import Credential
from pydantic import SecretStr
# Create a credential – the API key is wrapped in SecretStr
cred = Credential(
name="Prod-OpenAI",
provider="openai",
modalities=["language", "embedding"],
api_key=SecretStr("sk-my-super-secret-key"),
)
# Saving encrypts the key transparently before storage
await cred.save() # DB column `api_key` contains ciphertext, not plaintext
When loading credentials back from SurrealDB, decryption occurs automatically:
# Loading automatically decrypts the API key
loaded = await Credential.get(cred.id)
print(loaded.api_key.get_secret_value())
# Output: sk-my-super-secret-key
For low-level encryption tasks outside the model context, you can use the utility functions directly:
from open_notebook.utils.encryption import encrypt_value, decrypt_value
plain = "sk-my-super-secret-key"
cipher = encrypt_value(plain) # Returns bytes: b'gAAAAAB…'
assert decrypt_value(cipher) == plain
Security Considerations and Key Management
After calling save(), the original SecretStr instance is re-attached to the model instance. This design ensures that callers retain access to the unencrypted value in memory for immediate use, while the database holds only the encrypted payload.
The system relies on the ENCRYPTION_KEY environment variable for symmetric key derivation. If this variable is not set or is compromised, the encryption guarantees are void. Administrators must secure this key separately from the SurrealDB database files to maintain the security boundary.
Summary
- The
Credentialmodel inopen_notebook/domain/credential.pyoverrides_prepare_save_datato encrypt API keys before they reach SurrealDB storage. - The
_from_db_rowmethod automatically decrypts credentials when loading from the database, re-wrapping them inSecretStrobjects. - Encryption utilities in
open_notebook/utils/encryption.pyuse Fernet-style AES-CBC encryption viaencrypt_valueanddecrypt_value. - Pydantic's
SecretStrtype ensures that plaintext secrets are never accidentally logged or displayed, even during in-memory operations. - The system requires the
ENCRYPTION_KEYenvironment variable to derive the symmetric encryption key used for all credential storage.
Frequently Asked Questions
How does the Credential model handle API key encryption in Open Notebook?
The Credential model intercepts data during the save operation by overriding _prepare_save_data in open_notebook/domain/credential.py. It extracts the raw secret from the SecretStr field, encrypts it using encrypt_value, and stores the ciphertext in SurrealDB. When loading records via _from_db_row, the process reverses automatically to provide decrypted SecretStr objects to the application.
What encryption algorithm does Open Notebook use for storing credentials?
According to the source code in open_notebook/utils/encryption.py, Open Notebook uses Fernet-style AES-CBC encryption. This symmetric encryption scheme provides both confidentiality and integrity verification, ensuring that stored API keys cannot be read or modified without the correct encryption key.
Where is the encryption key stored for SurrealDB credential encryption?
The encryption key is derived from the ENCRYPTION_KEY environment variable at runtime. The application does not store this key in SurrealDB or persist it to disk; administrators must configure this environment variable separately. If the ENCRYPTION_KEY is lost, all encrypted credentials become permanently undecryptable.
How does the model ensure API keys remain secure in memory?
The Credential model uses Pydantic's SecretStr type for the api_key field, which prevents accidental exposure in logs, tracebacks, or string representations. After encryption and storage, the original SecretStr instance is re-attached to the model, ensuring that plaintext values remain masked while still accessible via get_secret_value() to authorized code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →