# How the Open Notebook Credential Model Encrypts API Keys for SurrealDB Storage

> Learn how Open Notebook's Credential model encrypts API keys with AES-CBC for SurrealDB, keeping secrets secure yet accessible.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-07-01

---

**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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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:

```python
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:

```python

# 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:

```python
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 `Credential` model in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) overrides `_prepare_save_data` to encrypt API keys before they reach SurrealDB storage.
- The `_from_db_row` method automatically decrypts credentials when loading from the database, re-wrapping them in `SecretStr` objects.
- Encryption utilities in [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py) use Fernet-style AES-CBC encryption via `encrypt_value` and `decrypt_value`.
- Pydantic's `SecretStr` type ensures that plaintext secrets are never accidentally logged or displayed, even during in-memory operations.
- The system requires the `ENCRYPTION_KEY` environment 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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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.