# How Open Notebook Manages and Encrypts API Keys in SurrealDB

> Learn how Open Notebook manages and encrypts API keys in SurrealDB using Fernet encryption for secure credential storage and transparent decryption.

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

---

**Open Notebook encrypts API keys using Fernet symmetric encryption before storing them in SurrealDB, decrypting them transparently when credentials are retrieved using an environment-derived encryption key.**

Open Notebook is an open-source application that securely stores AI provider credentials in SurrealDB. Understanding how the system manages and encrypts API keys in SurrealDB is crucial for maintaining security at rest. This article examines the implementation details found in the `lfnovo/open-notebook` repository, covering the encryption workflow from the domain model to the database layer.

## The Encryption Architecture

### Fernet Symmetric Encryption

At the core of the security layer is **Fernet symmetric encryption**, implemented in [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py). The system uses a secret derived from the `OPEN_NOTEBOOK_ENCRYPTION_KEY` environment variable. This secret is hashed using SHA-256 and base64-url-encoded to create a valid Fernet key, ensuring that even simple passphrase strings can serve as secure encryption roots.

### The Credential Domain Model

The `Credential` class in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) serves as the Pydantic domain model. It wraps sensitive API keys in **Pydantic `SecretStr`** types, preventing accidental logging or serialization of plaintext values. When a credential is persisted, the model handles the encryption boundary automatically, ensuring that only encrypted tokens reach the database.

## Storing Encrypted API Keys

When you save a credential, the `api_key` field undergoes encryption before reaching SurrealDB. In [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) (lines 34-42), the `_prepare_save_data` method extracts the secret value and passes it to [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py), where the `encrypt_value` function generates a URL-safe Base64 Fernet token.

```python
from open_notebook.domain.credential import Credential
from pydantic import SecretStr

cred = Credential(
    name="My OpenAI Key",
    provider="openai",
    modalities=["language", "embedding"],
    api_key=SecretStr("sk-my-super-secret-key"),
)

# `save()` encrypts the key and persists the record in SurrealDB.

await cred.save()

```

The generic repository layer in [`open_notebook/database/repository.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/database/repository.py) executes the SurrealQL commands, storing the encrypted token in the `api_key` column of the `credential` table alongside provider-specific configuration data.

## Retrieving and Decrypting API Keys

Upon retrieval via `Credential.get` or `Credential.get_all`, the system automatically decrypts the stored token. The `_from_db_row` helper (lines 56-68 in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py)) calls `decrypt_value` from the encryption utility, which reverses the Fernet encryption process. The resulting plaintext is wrapped back into a `SecretStr` before being returned to the caller.

```python
from open_notebook.domain.credential import Credential

# Load by SurrealDB record ID (e.g., "credential:abc123")

cred = await Credential.get("credential:abc123")

# `api_key` is a SecretStr containing the plaintext value.

plain_key = cred.api_key.get_secret_value()
print("Decrypted key:", plain_key)

```

Downstream code in [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) and [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py) accesses these decrypted credentials without needing to understand the underlying encryption layer.

## Environment Configuration and Key Management

The encryption system implements **lazy key loading** to improve service resilience. Rather than reading `OPEN_NOTEBOOK_ENCRYPTION_KEY` at import time, the system loads the environment variable only when the first encryption or decryption call occurs (lines 90-102 in [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py)). This prevents the application from crashing during startup if the variable is temporarily missing.

If the encryption key changes or is incorrect, the system provides clear error boundaries. When `decrypt_value` encounters a value that appears to be a Fernet token but fails decryption (lines 88-95 in [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py)), it raises a descriptive error guiding the operator to verify the `OPEN_NOTEBOOK_ENCRYPTION_KEY` setting. This safety mechanism supports key rotation workflows by alerting operators when existing encrypted records require re-encryption.

## Low-Level Encryption Utilities

For advanced use cases, the encryption module exposes direct utility functions:

```python
from open_notebook.utils.encryption import encrypt_value, decrypt_value

cipher = encrypt_value("my-plain-api-key")
print("Encrypted token:", cipher)

plain = decrypt_value(cipher)
print("Decrypted back:", plain)

```

These functions handle the Fernet instance lifecycle and key derivation automatically, ensuring consistent encryption behavior across the application.

## Summary

- **At-rest encryption**: All API keys are stored as Fernet tokens in SurrealDB, protecting against database dump exposure.
- **Transparent decryption**: The `Credential` domain model automatically decrypts keys when retrieving records, returning them as `SecretStr` objects.
- **Lazy key loading**: The encryption key is read from `OPEN_NOTEBOOK_ENCRYPTION_KEY` only when needed, preventing startup failures.
- **Key rotation safety**: Clear error messages indicate when decryption fails due to key mismatches, enabling operational key rotation.
- **Repository pattern**: The generic database layer in [`open_notebook/database/repository.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/database/repository.py) handles SurrealQL execution while the domain model manages encryption logic.

## Frequently Asked Questions

### What encryption algorithm does Open Notebook use for API keys?

Open Notebook uses **Fernet symmetric encryption** from the Python `cryptography` library. The algorithm provides authenticated encryption, ensuring that data cannot be read or modified without the correct key. The encryption key is derived from the `OPEN_NOTEBOOK_ENCRYPTION_KEY` environment variable using SHA-256 hashing and base64-url-encoding.

### How does Open Notebook handle missing or changed encryption keys?

The system implements **lazy key loading**, reading the environment variable only when the first encryption or decryption operation occurs. If the key is missing or incorrect at runtime, operations fail with clear error messages indicating that `OPEN_NOTEBOOK_ENCRYPTION_KEY` should be checked. This prevents silent failures and aids debugging during deployment or key rotation.

### Can I rotate the encryption key for existing API credentials?

**Key rotation requires re-encryption**. If you change `OPEN_NOTEBOOK_ENCRYPTION_KEY`, existing encrypted tokens in the database will fail decryption with a specific error message. You must retrieve the credentials using the old key, then save them again with the new key to re-encrypt the data. The error handling in [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py) (lines 88-95) explicitly guides operators through this scenario.

### Where does Open Notebook store the encryption key?

Open Notebook **does not store the encryption key in the database**. The key exists only as an environment variable (`OPEN_NOTEBOOK_ENCRYPTION_KEY`) on the application server. This design follows security best practices by separating the encryption key from the encrypted data, ensuring that database access alone does not compromise the API keys.