How to Implement Secure Credential Storage and Encryption in Fincept Terminal

Fincept Terminal uses a two-layer architecture combining OS-native secret services with per-account key scoping to protect API keys and tokens, delegating encryption to Windows Credential Manager, macOS Keychain, and a temporary XOR obfuscation on Linux.

The Fincept Terminal trading platform stores sensitive broker credentials through a hardened abstraction layer that avoids custom cryptography in favor of operating-system native secure storage. The SecureStorage class in fincept-qt/src/storage/secure/SecureStorage.h provides a unified API for persisting secrets, while AccountManager in fincept-qt/src/trading/AccountManager.h orchestrates credential lifecycles with per-account isolation. Together, these components ensure that API keys, access tokens, and user secrets remain encrypted at rest without exposing plain text to the application layer.

SecureStorage Architecture and Key Scoping

The credential system centers on two primary classes that separate storage mechanics from business logic.

SecureStorage exposes three core operations—store(), retrieve(), and remove()—implemented in fincept-qt/src/storage/secure/SecureStorage.cpp. These methods delegate to platform-native backends:

  • Windows: DPAPI-encrypted Credential Manager via CredWriteW
  • macOS: Keychain Services via SecKeychainAddGenericPassword
  • Linux: XOR-obfuscated QSettings (with planned libsecret migration)

AccountManager consumes this API to manage BrokerCredentials structures defined in fincept-qt/src/trading/BrokerInterface.h. It enforces per-account key scoping using the format account.<account_id>.<field>, preventing cross-account leakage. For example, an API key for account 12345 stores under account.12345.api_key.

Storing Credentials with AccountManager

To persist credentials, populate a BrokerCredentials object and invoke AccountManager::save_credentials().

// Create and populate credentials
BrokerCredentials creds;
creds.broker_id    = "alpaca";
creds.api_key      = "PK_TEST_KEY";
creds.api_secret   = "SECRET_VALUE";
creds.access_token = "jwt_token_123";
creds.user_id      = "user456";

// Persist to secure storage
AccountManager::instance().save_credentials(account_id, creds);

Under the hood, save_credentials() constructs keys like account.<id>.api_key and delegates to SecureStorage::store().

  • Windows: Calls CredWriteW with a CREDENTIALW structure (lines 65-81 in SecureStorage.cpp)
  • macOS: Invokes SecKeychainAddGenericPassword after deleting existing entries (lines 83-107)
  • Linux: XOR-obfuscates the payload and writes Base64 to QSettings under the secure/ namespace (lines 109-117)

All operations return a Result object, allowing error handling without exceptions. Failed writes trigger LOG_ERROR entries while leaving the application state intact.

Retrieving and Clearing Credentials

Loading credentials mirrors the storage pattern with automatic decryption on supported platforms.

// Load credentials for a specific account
BrokerCredentials creds = AccountManager::instance().load_credentials(account_id);

if (!creds.api_key.isEmpty()) {
    // Initialize broker client with decrypted credentials
}

On Windows and macOS, SecureStorage::retrieve() returns clear text after the OS automatically decrypts the secret. On Linux, the method decodes Base64 and reverses the XOR obfuscation (lines 55-64 in SecureStorage.cpp).

To remove credentials permanently:

AccountManager::instance().clear_credentials(account_id);

This calls SecureStorage::remove() for each stored field, deleting entries from the respective backend.

Platform-Specific Encryption Backends

Fincept Terminal leverages native OS security rather than implementing custom encryption algorithms.

Windows: Uses CredWriteW to store secrets in the Windows Credential Manager, automatically protected by DPAPI (Data Protection API). The implementation creates CREDENTIALW structures with the CRED_TYPE_GENERIC type.

macOS: Integrates with Keychain Services via SecKeychainAddGenericPassword. The code handles item updates by first deleting existing entries, ensuring atomic replacement of secrets.

Linux: Currently implements a deliberately weak XOR obfuscation only to prevent casual inspection of QSettings files. The source code at lines 112-117 of SecureStorage.cpp contains TODO markers indicating planned migration to libsecret or similar secret-service integration. Do not rely on the Linux implementation for production security without replacing this backend.

Migrating Legacy Credentials

Historical versions stored credentials under flat keys like broker.<broker_id>.api_key. The AccountManager constructor automatically invokes migrate_legacy_credentials() (lines 49-128 in AccountManager.cpp) to transition data to the scoped format.

The migration process:

  1. Checks a flag in SettingsRepository to prevent duplicate runs
  2. Iterates registered brokers from BrokerRegistry
  3. Reads legacy keys via SecureStorage::retrieve
  4. Creates new BrokerAccount instances with UUID-based IDs
  5. Re-saves credentials using save_credentials() under scoped keys
  6. Updates the migration flag

This runs once per installation and is safe to execute on every startup.

Extending Linux Storage with libsecret

To replace the XOR fallback with proper Linux encryption:

  1. Create SecureStorageLibsecret.cpp implementing the storage interface using libsecret APIs
  2. Guard with #elif defined(Q_OS_LINUX) in SecureStorage.cpp
  3. Replace the XOR block (lines 109-117) with calls to your new wrapper
  4. Update CMakeLists.txt in fincept-qt/src/storage/secure to link against libsecret-1

Maintain the Result return pattern to preserve error-handling contracts with AccountManager.

Summary

  • SecureStorage abstracts OS-native encryption via store(), retrieve(), and remove() in fincept-qt/src/storage/secure/SecureStorage.cpp
  • AccountManager provides per-account isolation using scoped keys like account.<id>.api_key
  • Windows and macOS use DPAPI and Keychain respectively; Linux currently uses XOR obfuscation requiring replacement
  • Legacy credentials automatically migrate to the new scoped format via migrate_legacy_credentials()
  • Always use the Result<T> pattern for error handling rather than exceptions

Frequently Asked Questions

How does Fincept Terminal encrypt stored API keys?

On Windows, Fincept Terminal uses the Data Protection API (DPAPI) through the Credential Manager. macOS leverages the Keychain Services framework. Linux currently applies XOR obfuscation as a placeholder, with the source code in SecureStorage.cpp marked for replacement with libsecret integration. No custom encryption algorithms are implemented.

Where are credentials physically stored on each operating system?

Windows stores secrets in the Credential Manager vault under generic credentials. macOS writes to the user Keychain as generic passwords. Linux persists Base64-encoded XOR data in QSettings files typically located in ~/.config/Fincept/ until the planned libsecret backend relocates them to the Secret Service.

Is the Linux XOR implementation secure for production use?

No. The XOR obfuscation in SecureStorage.cpp (lines 109-117) provides only casual obfuscation against file inspection. It lacks cryptographic security and should be replaced with a libsecret or D-Bus Secret Service implementation before deploying in production environments.

How do I add a new broker with custom credential fields?

Define the credential schema in your broker implementation of IBroker from fincept-qt/src/trading/BrokerInterface.h, specify the required fields in credential_fields, then collect values through AccountManagementDialog in fincept-qt/src/screens/equity_trading/AccountManagementDialog.cpp. Pass the populated BrokerCredentials struct to AccountManager::save_credentials() with a unique account ID to trigger automatic secure storage.

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 →