How Does FreeLLMAPI Store Keys Securely? AES-256-GCM Encryption Explained

FreeLLMAPI encrypts API keys using AES-256-GCM before storing them in SQLite, ensuring plaintext credentials never touch the disk and are only decrypted transiently in memory during request processing.

FreeLLMAPI implements a defense-in-depth strategy to protect sensitive LLM credentials. According to the tashfeenahmed/freellmapi source code, the application never persists raw API keys in configuration files or environment dumps; instead, it relies on strong at-rest encryption and strict memory management.

At-Rest Encryption Architecture

The foundation of FreeLLMAPI’s security model is AES-256-GCM encryption applied to all sensitive material before database storage.

SQLite Storage Schema

In the api_keys table, credentials are split across three separate columns to prevent reconstruction without the master key:

  • key_encrypted — The AES-256-GCM ciphertext
  • key_iv — The random initialization vector (nonce)
  • key_auth_tag — The authentication tag for integrity verification

This separation ensures that even direct database access does not reveal usable credentials. The encryption helpers in server/src/lib/crypto.js handle the cryptographic operations, generating a random IV for every encryption operation and appending the auth tag to prevent tampering.

// Example: Storing a new key via the server API
import { encrypt } from './lib/crypto.js';
import { db } from './db';

function storeKey(prefix: string, plainKey: string) {
  const { encrypted, iv, authTag } = encrypt(plainKey);
  db.prepare(`
    INSERT INTO api_keys (platform, key_encrypted, key_iv, key_auth_tag)
    VALUES (?, ?, ?, ?)
  `).run(`${prefix}_`, encrypted, iv, authTag);
}

Core Cryptographic Implementation

The encrypt() and decrypt() functions in server/src/lib/crypto.js implement AES-256-GCM with authenticated encryption. Each operation requires the application-wide ENCRYPTION_KEY supplied via environment variable. This design ensures that the database remains encrypted at rest, and decryption only occurs within the running application context.

Proxy URL and Credential Protection

FreeLLMAPI extends the same encryption guarantees to proxy configurations, which often contain embedded credentials.

Encrypting Per-Key Proxy URLs

When users supply a per-key proxy URL, the system treats it with the same sensitivity as the API key itself. The server/src/lib/key-proxy.ts file exports encryptProxyUrl() and decryptProxyUrl() functions that reuse the AES-256-GCM pipeline. Database migrations in server/src/db/migrate/defaults.ts add dedicated columns for encrypted proxy storage, mirroring the key_encrypted, key_iv, and key_auth_tag pattern used for primary credentials.

Masking Keys for UI Display

To prevent accidental secret leakage in dashboards or logs, server/src/lib/key-proxy.ts exposes maskKey() (and maskProxyUrl()), which replaces the secret portion of the credential with asterisks. This guarantees that even administrative interfaces never render plaintext secrets.

import { maskKey } from './lib/key-proxy.ts';

// Returns "sk-***" instead of the full key
const displayed = maskKey('sk-abcdef1234567890');
console.log(displayed);

Runtime Decryption and Memory Safety

In-memory exposure is minimized to the exact duration of request processing.

Transient Decryption During Requests

When a request arrives, the server queries the encrypted columns from SQLite, invokes decrypt() from crypto.js using the runtime ENCRYPTION_KEY, and injects the plaintext into the request pipeline. Once the request completes, the variable falls out of scope and becomes eligible for garbage collection. This approach ensures that raw keys never exist in memory longer than necessary.

import { decrypt } from './lib/crypto.js';
import { db } from './db';

function getKeyForPlatform(prefix: string) {
  const row = db.prepare(`
    SELECT key_encrypted, key_iv, key_auth_tag
    FROM api_keys
    WHERE platform = ?
  `).get(`${prefix}_`);

  if (!row?.key_encrypted) return null;
  // Decrypted only at request time, never persisted
  return decrypt(row.key_encrypted, row.key_iv, row.key_auth_tag).trim();
}

CLI Environment Isolation

For command-line usage, FreeLLMAPI supports a unified environment variable FREELLMAPI_API_KEY. The CLI entry point in cli/src/index.ts reads this variable directly from process.env and passes it to the request handler without ever writing it to disk. This mode is designed for ephemeral operations where database storage is not required.


# Never written to any file; exists only in shell memory

export FREELLMAPI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx

npx freellmapi chat "Explain quantum tunneling"

Key Parsing and Validation

The server/src/lib/key-parser.ts module handles validation of <PREFIX>_API_KEY or <PREFIX>_KEY formats during ingestion. This ensures that only properly prefixed credentials enter the encryption pipeline, preventing malformed or accidental data from entering the secure storage layer.

Summary

  • AES-256-GCM encryption in server/src/lib/crypto.js protects all credentials before they reach the SQLite api_keys table.
  • Three-column separation (key_encrypted, key_iv, key_auth_tag) ensures database compromises do not expose plaintext keys.
  • Proxy URLs receive identical encryption treatment via server/src/lib/key-proxy.ts.
  • UI masking via maskKey() prevents secret leakage in administrative interfaces.
  • Runtime decryption limits plaintext exposure to the duration of individual requests.
  • CLI mode supports ephemeral usage via FREELLMAPI_API_KEY without persisting values.

Frequently Asked Questions

What encryption algorithm does FreeLLMAPI use for API key storage?

FreeLLMAPI uses AES-256-GCM (Galois/Counter Mode) authenticated encryption. This algorithm provides both confidentiality and integrity verification through the authentication tag stored in the key_auth_tag column. The implementation resides in server/src/lib/crypto.js and generates a random IV for every encryption operation to prevent pattern analysis.

Where are API keys stored in FreeLLMAPI?

API keys are stored in an SQLite database within the api_keys table, specifically across three columns: key_encrypted, key_iv, and key_auth_tag. The plaintext is never written to configuration files, environment dumps, or logs. Documentation in docs/clients.md and docs/api.md explicitly warns users against storing raw keys in JSON or ENV files.

How does FreeLLMAPI prevent API keys from appearing in logs or the UI?

The system uses masking functions defined in server/src/lib/key-proxy.ts. The maskKey() function (and maskProxyUrl() for proxy credentials) truncates sensitive portions of the string, displaying only the prefix followed by asterisks. This ensures that dashboard views, error messages, and log entries never contain recoverable secret material.

Can FreeLLMAPI work without storing keys in the database?

Yes. The CLI mode supports ephemeral key usage via the FREELLMAPI_API_KEY environment variable. As implemented in cli/src/index.ts, the CLI reads this variable directly from the process environment and transmits it to the API without persisting it to disk or database. This mode is ideal for CI/CD pipelines or temporary testing scenarios.

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 →