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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →