API Key Management

OpenWork provides secure storage and validation for API keys from multiple AI service providers. The system uses machine-specific encryption to protect sensitive credentials while maintaining usability across different platforms.

Supported Providers

Provider Service Validation Endpoint
anthropic Anthropic Claude https://api.anthropic.com/v1/messages
openai OpenAI GPT https://api.openai.com/v1/models
google Google Gemini https://generativelanguage.googleapis.com/v1/models
groq Groq Llama https://api.groq.com/openapi.json
custom Custom endpoint User-defined endpoint

Implementation

The API key system is implemented with AES-256-GCM encryption and machine-specific key derivation.

Source: /Users/nateb/openwork-repo/apps/desktop/src/main/store/secureStorage.ts

Encryption Architecture

/**
 * Secure storage using electron-store with custom AES-256-GCM encryption.
 *
 * This implementation derives an encryption key from machine-specific values
 * (hostname, platform, user home directory, app path) to avoid macOS Keychain
 * prompts while still providing reasonable security for API keys.
 *
 * Security considerations:
 * - Keys are encrypted at rest using AES-256-GCM
 * - Encryption key is derived from machine-specific data (not stored)
 * - Less secure than Keychain (key derivation could be reverse-engineered)
 * - Suitable for API keys that can be rotated if compromised
 */

Key Derivation

function getDerivedKey(): Buffer {
  // Combine machine-specific values to create a unique identifier
  const machineData = [
    os.platform(),
    os.homedir(),
    os.userInfo().username,
    app.getPath('userData'),
    'ai.accomplish.desktop', // App identifier
  ].join(':');

  const salt = getSalt();

  // Use PBKDF2 to derive a 256-bit key
  _derivedKey = crypto.pbkdf2Sync(
    machineData,
    salt,
    100000, // iterations
    32, // key length (256 bits)
    'sha256'
  );

  return _derivedKey;
}

Storage Format

Encrypted values are stored as base64 strings with the format:

iv:authTag:ciphertext

API Methods

Core Operations

// Store an API key securely
export function storeApiKey(provider: string, apiKey: string): void

// Retrieve an API key
export function getApiKey(provider: string): string | null

// Delete an API key
export function deleteApiKey(provider: string): boolean

Batch Operations

// Get all API keys for all providers
export async function getAllApiKeys(): Promise<Record<ApiKeyProvider, string | null>>

// Check if any API key is stored
export async function hasAnyApiKey(): Promise<boolean>

// List all stored credentials
export function listStoredCredentials(): Array<{ account: string; password: string }>

Utility Functions

// Clear all secure storage (used during fresh install cleanup)
export function clearSecureStorage(): void

Security Implementation

Multi-Layer Protection

  1. Encryption at Rest: All API keys are encrypted using AES-256-GCM
  2. Machine-Specific Keys: Encryption keys are derived from unique machine identifiers
  3. Salt Management: Each installation generates a unique salt for key derivation
  4. Secure Storage: Data persisted using electron-store with encrypted values

Key Derivation Process

graph TD
    A[Machine Data] --> B[Combine Values]
    B --> C[Add Installation Salt]
    C --> D[PBKDF2 with 100k iterations]
    D --> E[256-bit Encryption Key]
    E --> F[AES-256-GCM Encryption]
    F --> G[Store: iv:authTag:ciphertext]

Security Considerations

Strengths

  • AES-256-GCM provides strong encryption
  • 100k PBKDF2 iterations prevent brute-force attacks
  • Machine-specific keys prevent data portability between devices
  • Base64 encoding prevents file system encoding issues

Limitations

  • Less secure than OS Keychain integration
  • Key derivation could potentially be reverse-engineered
  • Requires users to re-enter keys after system reinstalls

Validation System

Provider-Specific Validation

Each provider has a dedicated validation endpoint:

// Anthropic validation
async function validateAnthropicKey(apiKey: string): Promise<boolean> {
  const response = await fetchWithTimeout(
    'https://api.anthropic.com/v1/messages',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': apiKey,
        'anthropic-version': '2023-06-01',
      },
      body: JSON.stringify({
        model: 'claude-3-haiku-20240307',
        max_tokens: 10,
        messages: [{ role: 'user', content: 'test' }],
      }),
    },
    API_KEY_VALIDATION_TIMEOUT_MS
  );

  return response.ok;
}

Validation Workflow

  1. User Input: API key entered through UI
  2. Secure Storage: Key encrypted and stored
  3. Validation: Test request to provider API
  4. Result: Success or failure feedback to user
  5. Fallback: Handle rate limits, invalid keys, network errors

Usage Patterns

Basic Operations

// Store a key
storeApiKey('anthropic', 'sk-ant-api03-...');

// Retrieve a key
const key = getApiKey('anthropic');
if (key) {
  // Use the key for API calls
}

// Delete a key
deleteApiKey('anthropic');

Batch Management

// Get all configured providers
const allKeys = await getAllApiKeys();
const activeProviders = Object.entries(allKeys)
  .filter(([_, key]) => key !== null)
  .map(([provider, _]) => provider);

// Check for any configured keys
if (await hasAnyApiKey()) {
  // App is ready to use
} else {
  // Show onboarding flow
}

Error Handling

Common Scenarios

Decryption Failure

function decryptValue(encryptedData: string): string | null {
  try {
    // Attempt decryption
    return decryptedValue;
  } catch {
    // Don't log error details to avoid leaking sensitive context
    return null;
  }
}

Validation Timeouts

const API_KEY_VALIDATION_TIMEOUT_MS = 15000;

Storage Access Issues

  • Handle cases where storage becomes unavailable
  • Graceful degradation when encryption fails
  • User-friendly error messages

Platform Differences

macOS

  • Uses standard Node.js crypto (no Keychain integration)
  • Machine-specific data includes platform and user directory
  • No additional authentication prompts

Windows

  • Same encryption implementation as macOS
  • Machine-specific data includes Windows-specific paths
  • Compatible with Windows Credential Manager (though not used)

Linux

  • Uses standard Node.js crypto
  • Machine-specific data includes Linux-specific paths
  • Compatible with various Linux keyring implementations (though not used)

Migration and Backup

Data Format

  • Encrypted values stored in electron-store configuration files
  • Salt generated per installation
  • No migration path between different machines

Fresh Install Cleanup

// During fresh install, old data is cleared
if (process.env.CLEAN_START === '1') {
  const userDataPath = app.getPath('userData');
  if (fs.existsSync(userDataPath)) {
    fs.rmSync(userDataPath, { recursive: true, force: true });
  }
  // Note: Secure storage (API keys, auth tokens) is stored in electron-store
  // which lives in userData, so it gets cleared with the directory above
}

Performance Considerations

Lazy Initialization

let _secureStore: Store<SecureStorageSchema> | null = null;
let _derivedKey: Buffer | null = null;

function getSecureStore(): Store<SecureStorageSchema> {
  if (!_secureStore) {
    _secureStore = new Store<SecureStorageSchema>({
      name: getStoreName(),
      defaults: { values: {} },
    });
  }
  return _secureStore;
}

Cache Management

  • Derived keys are cached in memory for performance
  • Stores are initialized on first access
  • Clear cache on storage reset

Security Best Practices

  1. Never log raw API keys in application logs
  2. Validate keys before storing to ensure they work
  3. Use timeouts for validation requests
  4. Handle errors gracefully without exposing sensitive information
  5. Consider key rotation for production deployments
  6. Document security boundaries for users and developers

The API key management system provides a good balance between security and usability, ensuring sensitive credentials are protected while maintaining a smooth user experience.


Back to top

OpenWork Documentation - Community documentation for accomplish-ai/openwork