How Security Measures Protect API Keys and Sensitive Data in AI Hedge Fund

The AI Hedge Fund project implements a defense-in-depth strategy that keeps API keys out of source code, injects them at runtime via environment variables, and enforces active-status checks through repository and service layers.

The virattt/ai-hedge-fund repository handles sensitive financial data and third-party API credentials, making robust security architecture essential. The project employs multiple security measures to protect API keys and sensitive data, ensuring credentials never appear in version control while maintaining secure runtime access through layered abstraction.

Environment-Level Protection

Template-Based Configuration

The repository prevents accidental credential commits by using .env.example as a template. This file lists required variables with placeholder values like "your-api-key-here", prompting users to create their own local .env file that is excluded from version control.

Runtime Environment Injection

When the application requires third-party access, it reads variables at runtime using os.getenv. In src/tools/api.py, the get_prices function retrieves FINANCIAL_DATASETS_API_KEY from the environment and injects it into request headers without ever hardcoding the value.

import os
from src.tools.api import get_prices

def get_daily_prices(ticker, start, end):
    # The function reads FINANCIAL_DATASETS_API_KEY from the environment

    prices = get_prices(ticker, start, end)
    return prices

Source: src/tools/api.py – get_prices function

State-Based Key Retrieval

Secure Extraction from Request State

For backend operations, the request object carries an api_keys dictionary populated from the database. The helper function get_api_key_from_state in src/utils/api_key.py extracts specific keys without exposing them to broader scope.

from src.utils.api_key import get_api_key_from_state

def some_agent(state):
    # Pull the Financial Datasets key from the state payload

    api_key = get_api_key_from_state(state, "FINANCIAL_DATASETS_API_KEY")
    # Use the key to call the API (no secret appears in source)

    return fetch_some_data(api_key)

Source: src/utils/api_key.py

Database Security Architecture

Encrypted Storage Model

API keys are stored in the api_keys table defined in app/backend/database/models.py. The model uses a Text column type with placeholder support for encryption, ensuring keys are not stored in plain text when encryption is enabled.

Lifecycle Management Fields

The database schema includes is_active and last_used fields for operational control. These fields enable key rotation, revocation, and usage tracking without deleting records, maintaining comprehensive audit trails.

Service and Repository Safeguards

Active Key Filtering

The ApiKeyService in app/backend/services/api_key_service.py fetches only active keys and returns a clean {provider: key} mapping. The service layer never logs key values, preventing accidental exposure in application logs.

from app.backend.services.api_key_service import ApiKeyService
from app.backend.database.connection import get_db

def inject_keys_into_request(request):
    service = ApiKeyService(db=get_db())
    # Returns {"FINANCIAL_DATASETS_API_KEY": "...", "OPENAI_API_KEY": "..."}

    request.api_keys = service.get_api_keys_dict()

Source: app/backend/services/api_key_service.py

Soft Deletion and Uniqueness Enforcement

The ApiKeyRepository in app/backend/repositories/api_key_repository.py enforces uniqueness constraints and implements soft deletion via deactivate_api_key. All CRUD operations pass through this layer, centralizing validation and preventing duplicate or revoked key usage.

def get_api_key_by_provider(self, provider: str) -> Optional[ApiKey]:
    # Filters on is_active == True

    return self.db.query(ApiKey).filter(
        ApiKey.provider == provider,
        ApiKey.is_active == True
    ).first()

Source: app/backend/repositories/api_key_repository.py – get_api_key_by_provider

Operational Security Controls

Preventing Log Exposure

The codebase strictly avoids logging sensitive headers. In src/tools/api.py, only generic status messages like "Rate limited (429)" appear in print statements, ensuring API keys never leak through logging mechanisms.

Controlled Endpoint Access

Backend routes in app/backend/routes/hedge_fund.py and app/backend/routes/api_keys.py require the api_keys dictionary to be supplied by authenticated clients. The database service validates that only active, authorized keys are used for financial data operations.

Key Security Files

File Role in Security
.env.example Template for local secret configuration
src/utils/api_key.py Helper for secure state-based key extraction
src/tools/api.py Runtime environment injection
app/backend/services/api_key_service.py Active key filtering and clean mapping
app/backend/repositories/api_key_repository.py Uniqueness and soft-deletion enforcement
app/backend/database/models.py Encrypted storage schema with lifecycle fields

Summary

  • Environment variables prevent hardcoding secrets in version control
  • State-based retrieval isolates keys within request scope
  • Database encryption and lifecycle fields enable secure storage and rotation
  • Service and repository layers enforce active-status validation and prevent logging exposure
  • Endpoint controls ensure only authorized, active keys access financial data

Frequently Asked Questions

How does the project prevent API keys from being committed to Git?

The repository uses .env.example as a template with placeholder values, while the actual .env file containing secrets is excluded from version control. All runtime access uses os.getenv, ensuring no literal keys exist in source code.

What happens when an API key is revoked or expires?

The ApiKeyRepository implements soft deletion through deactivate_api_key, setting is_active to false. The ApiKeyService filters for active keys only, preventing revoked credentials from being injected into requests.

Are API keys encrypted in the database?

The ApiKey model in app/backend/database/models.py uses a Text column type with placeholder support for encryption. While the base implementation stores keys as text, the architecture supports transparent encryption at the database or application layer.

How does the application prevent API keys from appearing in logs?

The codebase strictly avoids logging request headers containing credentials. In src/tools/api.py, only generic status messages like "Rate limited (429)" are printed, and the ApiKeyService never logs the {provider: key} mapping it returns.

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 →