How the Database-First Credential Provisioning Pattern Works in Open Notebook

The database-first credential provisioning pattern stores AI provider credentials in an encrypted database table and dynamically injects them into environment variables at runtime, falling back to existing environment variables when no database record is found.

Open Notebook implements a secure, flexible approach to managing AI service credentials through the key_provider.py module. Instead of relying solely on static environment variables, the system queries the database for provider credentials, decrypts them on demand, and provisions them into the process environment before instantiating AI models via the Esperanto library.

Architecture Overview

The pattern centers on a runtime provisioning layer that bridges the database and the AI factory. When a model needs initialization, the system first checks the credential table for the requested provider. If a record exists, the encrypted API key and configuration fields are decrypted and mapped to the specific environment variables expected by the underlying SDK (OpenAI, Azure, Vertex, etc.). This ensures that sensitive credentials remain encrypted at rest while remaining accessible to the application without manual configuration files.

Core Implementation in key_provider.py

The implementation resides in open_notebook/ai/key_provider.py and consists of a lookup layer, a configuration mapping, and specialized provisioning handlers for different provider types.

Database Lookup and Decryption

The _get_default_credential() function queries the Credential model via Credential.get_by_provider() (defined in open_notebook/domain/credential.py at lines 39–44). This method retrieves the first credential record matching the requested provider name and automatically decrypts the stored API key using the application's encryption key.


# From open_notebook/domain/credential.py

credential = await Credential.get_by_provider("openai")

# Returns decrypted credential with api_key accessible

Provider-to-Environment Mapping

PROVIDER_CONFIG serves as the single source of truth that links provider identifiers to their corresponding environment variable names. This dictionary handles the translation between Open Notebook's internal provider names (e.g., "openai") and the environment variables that the Esperanto library and underlying SDKs expect (e.g., OPENAI_API_KEY).

Simple Provider Provisioning

For providers requiring only an API key, _provision_simple_provider() (lines 13–42) handles the provisioning:

  1. Retrieves the credential from the database
  2. Sets the primary API key environment variable
  3. Optionally sets a base URL if specified in the credential record

# Simplified logic from key_provider.py

os.environ[env_var] = credential.api_key
if credential.api_base:
    os.environ[f"{prefix}_API_BASE"] = credential.api_base

Complex Provider Handlers

Specialized functions handle multi-variable providers:

  • _provision_vertex(): Extracts project, location, and credentials_path from the database record and sets VERTEX_PROJECT, VERTEX_LOCATION, and GOOGLE_APPLICATION_CREDENTIALS
  • _provision_azure(): Maps the credential to AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, and AZURE_OPENAI_API_VERSION
  • _provision_openai_compatible(): Handles generic OpenAI-compatible endpoints with custom base URLs

Runtime Execution Flow

The public entry point provision_provider_keys(provider) orchestrates the provisioning sequence:

  1. Normalization: Standardizes the provider name (e.g., converting "azure_openai" to "azure")
  2. Dispatch: Routes to the appropriate helper based on provider complexity
  3. Environment Injection: Writes decrypted values to os.environ
  4. Fallback: If no database record exists, the function preserves existing environment variables (lines 89–106), ensuring backward compatibility

This function is typically invoked in open_notebook/ai/models.py (lines 135–140) before model creation, or via API endpoints in api/routers/models.py (lines 504–534).


# Runtime usage pattern

await provision_provider_keys("openai")  # DB → env vars

model = await AIFactory.create_language(..., provider="openai")

Practical Code Examples

Provisioning a Single Provider at Runtime

Use provision_provider_keys() when initializing specific models to ensure credentials are loaded before instantiation:

from open_notebook.ai.key_provider import provision_provider_keys

async def init_model():
    # Load DB-stored key for OpenAI (if present)

    await provision_provider_keys("openai")
    # Now the environment contains OPENAI_API_KEY → model can be created

    model = await Model.create(
        name="gpt-4",
        provider="openai",
        credential=await Credential.get_by_provider("openai")[0].id,
    )
    return model

Bulk Provisioning at Startup

The provision_all_keys() function iterates over every entry in PROVIDER_CONFIG and calls provision_provider_keys() for each provider plus complex variants:

from open_notebook.ai.key_provider import provision_all_keys

async def startup():
    results = await provision_all_keys()
    # results is a dict like {"openai": True, "vertex": False, ...}

    print("Credential provisioning:", results)

Note: This approach is discouraged for per-request use because stale environment variables could persist after a credential is deleted from the database.

Azure OpenAI Configuration

Complex providers requiring multiple configuration parameters are handled automatically:

from open_notebook.ai.key_provider import provision_provider_keys

async def use_azure():
    await provision_provider_keys("azure")
    # Environment now contains:

    #   AZURE_OPENAI_API_KEY,

    #   AZURE_OPENAI_ENDPOINT,

    #   AZURE_OPENAI_API_VERSION, …

    model = await Model.create(name="gpt-35-turbo", provider="azure")
    return model

Summary

  • Database-First Pattern: Credentials are stored encrypted in the credential table and decrypted only at runtime via Credential.get_by_provider()
  • Environment Injection: The provision_provider_keys() function in open_notebook/ai/key_provider.py maps database records to environment variables expected by AI SDKs
  • Provider Flexibility: Simple providers use _provision_simple_provider() while complex ones (Azure, Vertex) use specialized handlers that set multiple environment variables
  • Backward Compatibility: If no database record exists, the system falls back to existing environment variables without error
  • Security: Credentials remain encrypted at rest and are only exposed in memory during the active process session

Frequently Asked Questions

What happens if no credential exists in the database for a provider?

If provision_provider_keys() cannot find a credential record for the requested provider, it silently falls back to the existing environment variables (lines 89–106 in key_provider.py). This preserves backward compatibility with traditional environment-based configuration while allowing seamless migration to database-stored credentials.

How are credentials secured in the database?

The Credential model in open_notebook/domain/credential.py stores API keys in an encrypted format. When retrieved via get_by_provider(), the model automatically decrypts the sensitive fields using the application's configured encryption key, ensuring that plaintext secrets never persist in the database.

Why inject credentials into environment variables instead of passing them directly to the AI factory?

The database-first credential provisioning pattern uses environment variables to maintain compatibility with the Esperanto library and underlying AI SDKs (OpenAI, Azure, etc.), which expect configuration through specific environment variable names. This abstraction allows Open Notebook to support multiple credential sources while leveraging existing SDK authentication mechanisms without modifying third-party library code.

When should I use provision_all_keys() versus provision_provider_keys()?

Use provision_provider_keys() for specific provider initialization to avoid polluting the environment with unused credentials. Use provision_all_keys() only during application startup to preload all configured providers, but avoid it in per-request contexts because environment variables persist across requests and could retain stale credentials after database deletion.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →