npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

genai-key-storage-lite

v0.1.4

Published

A secure API key storage module for Electron generative AI based applications using native OS credential stores.

Readme

GenAI Key Storage Lite

A secure API key storage module for generative AI-based Electron applications using native OS credential stores.

This module leverages Electron's safeStorage for OS-level encryption (macOS Keychain, Windows Credential Vault), ensuring that API keys are not stored in plaintext and are not directly exposed to the renderer process.

Features

  • Secure by Default: Encrypts keys using native OS credential stores via electron.safeStorage.
  • Strict Process Separation: Plaintext keys are never sent to the renderer process, preventing accidental exposure.
  • On-Demand Decryption: Keys are decrypted only when needed for an API call and are never cached in plaintext in memory.
  • Simple Integration: Provides clear, separated components for your application's main, renderer, and preload processes.
  • Built-in Provider Validation: Includes key format validators for popular AI providers (OpenAI, Anthropic, Gemini, Mistral).

Installation

npm install genai-key-storage-lite
# or
yarn add genai-key-storage-lite

How to Use

Integrating the module into your Electron application involves three steps.

1. Main Process Setup (main.ts)

In your main Electron process file, initialize ApiKeyServiceMain and register the IPC handlers it needs to communicate with the renderer process.

// your-electron-app/src/main.ts
import { app, BrowserWindow } from "electron";
import {
  ApiKeyServiceMain,
  registerSecureApiKeyIpc,
} from "genai-key-storage-lite";

// ... other imports

app.whenReady().then(() => {
  // 1. Initialize the main service with the app's user data path.
  //    This is where encrypted keys will be stored on disk.
  const apiKeyService = new ApiKeyServiceMain(app.getPath("userData"));

  // 2. Register the IPC handlers that the renderer will call.
  registerSecureApiKeyIpc(apiKeyService);

  // If you have other main-process services that need to use API keys,
  // you can pass the apiKeyService instance to them.
  // const myLLMService = new LLMServiceMain(apiKeyService);

  createWindow();
  // ... rest of your app startup logic
});

2. Preload Script Setup (preload.ts)

The preload script acts as a secure bridge between the sandboxed renderer process and the Node.js environment of the main process.

// your-electron-app/src/preload.ts
import { contextBridge } from "electron";
import { createApiKeyManagerBridge } from "genai-key-storage-lite/preload";

contextBridge.exposeInMainWorld("electronBridge", {
  // Expose the secure API key manager bridge under a namespace
  secureApiKeyManager: createApiKeyManagerBridge(),
  // ... you can expose other APIs here
});

To make TypeScript aware of the bridged API in your renderer code, create a type definition file (e.g., src/renderer.d.ts) and include it in your tsconfig.json:

// your-electron-app/src/renderer.d.ts
import type { IApiKeyManagerBridge } from "genai-key-storage-lite/renderer";

declare global {
  interface Window {
    electronBridge: {
      secureApiKeyManager: IApiKeyManagerBridge;
    };
  }
}

3. Renderer Process Setup & Usage (e.g., in a React Component)

Finally, you can use the ApiKeyServiceRenderer in your UI. It must be instantiated with the bridge object you exposed in the preload script.

// In a React component or service
import { ApiKeyServiceRenderer } from "genai-key-storage-lite/renderer";
import type { ApiProvider } from "genai-key-storage-lite"; // Common types are exported from the root
import React, { useState, useEffect } from "react";

// Instantiate the service by passing the bridged object from the window.
// It's best to do this once and share the instance (e.g., via React Context).
const apiKeyService = new ApiKeyServiceRenderer(
  window.electronBridge.secureApiKeyManager
);

const MySettingsComponent = () => {
  // Store a key
  const handleStoreKey = async (providerId: ApiProvider, key: string) => {
    // Client-side validation for instant feedback
    if (!apiKeyService.validateApiKeyFormat(providerId, key)) {
      alert("Invalid API key format!");
      return;
    }

    try {
      await apiKeyService.storeKey(providerId, key);
      alert(`${providerId} key stored successfully.`);
    } catch (error) {
      alert(`Failed to store key: ${error.message}`);
    }
  };

  // Get display information for a key (does not return the key itself)
  const checkKeyStatus = async (providerId: ApiProvider) => {
    try {
      const displayInfo = await apiKeyService.getApiKeyDisplayInfo(providerId);
      if (displayInfo.isStored) {
        console.log(
          `${providerId} key is stored. Last four chars:`,
          displayInfo.lastFourChars || "N/A"
        );
      } else {
        console.log(`${providerId} key is not stored.`);
      }
    } catch (error) {
      console.error("Failed to get key display info:", error.message);
    }
  };

  // Get all supported provider IDs for UI dropdowns etc.
  const availableProviders = apiKeyService.getAvailableProviders();
};

Advanced Usage

Using Keys in the Main Process (withDecryptedKey)

For scenarios where another main process module in your application needs to use an API key directly (e.g., to interact with a provider's SDK), ApiKeyServiceMain provides a secure method withDecryptedKey.

This method decrypts the key on-demand and provides it to a callback function, ensuring the plaintext key's scope is strictly limited.

// Example usage within another main process service:
// Assume 'apiKeyServiceMain' is the instance of ApiKeyServiceMain from step 1.

async function performLLMOperation(
  providerId: ApiProvider,
  prompt: string
): Promise<string> {
  return apiKeyServiceMain.withDecryptedKey(providerId, async (apiKey) => {
    // Here, 'apiKey' is the plaintext API key for the specified provider.
    // Use it with the provider's SDK directly.
    // const anthropicClient = new Anthropic({ apiKey });
    // const response = await anthropicClient.messages.create({ /* ... */ });
    // return response.content[0].text;

    // Placeholder implementation:
    console.log(
      `Processing "${prompt}" with ${providerId} key ending in ${apiKey.slice(
        -4
      )}`
    );
    return `Processed: ${prompt}`;
  });
}

// Call your function
try {
  const result = await performLLMOperation("anthropic", "Hello, world!");
  console.log("LLM response:", result);
} catch (error) {
  console.error("LLM operation failed:", error.message);
}

Key features of withDecryptedKey:

  • Callback Pattern: Takes a providerId and an asynchronous callback function that receives the decrypted API key.
  • On-Demand Decryption: The API key is decrypted only when needed.
  • Transient Access: The plaintext key is never cached by ApiKeyServiceMain. Its scope is limited to the callback's execution.
  • Main Process Only: This method is for use only within Electron's main process. The key is never sent to the renderer.

The module is divided into three main parts, following Electron's process model:

  1. src/common/: Contains code shared between the main and renderer processes.

    • types.ts: Defines core types like ApiProvider, IPC channel names, and payload structures.
    • errors.ts: Defines ApiKeyStorageError for consistent error handling.
    • providers/: Contains the IApiProviderValidator interface and implementations for specific services (e.g., OpenAIProvider.ts). The ProviderService manages these validators.
  2. src/main/: Contains the core logic that runs in Electron's main process.

    • ApiKeyServiceMain.ts: The heart of the secure storage system. It handles encryption/decryption using electron.safeStorage, persistence of encrypted keys to disk, and format validation.
    • ipc.ts: Exports a function registerSecureApiKeyIpc that sets up all the IPC handlers to connect the main service with the renderer.
  3. src/renderer/: Contains the client-side service used by UI components.

    • ApiKeyServiceRenderer.ts: Provides a clean, typed API for the UI to interact with the secure storage system via the preload bridge. It does not handle plaintext keys directly.
  4. src/preload/: Contains the bridge logic.

    • index.ts: Exports a function createApiKeyManagerBridge that creates the object to be exposed to the renderer process via contextBridge.

Contributing a New API Provider

This package includes validators for several common AI providers. If you wish to add support for a new provider, you'll need to contribute to the package itself. Here's how:

  1. Define Provider Type: Add the new provider ID (e.g., 'mynewai') to the ApiProvider union type in src/common/types.ts.

  2. Create Provider Validator: Create a new file, e.g., src/common/providers/MyNewAIProvider.ts, that implements the IApiProviderValidator interface.

    import { IApiProviderValidator } from "./ProviderInterface";
    import { ApiProvider } from "../types";
    
    export class MyNewAIProvider implements IApiProviderValidator {
      readonly providerId: ApiProvider = "mynewai";
    
      // Example: API keys for 'mynewai' must start with 'mna_'
      private readonly validationPattern = /^mna_[a-zA-Z0-9]{16}$/;
    
      validateApiKey(apiKey: string): boolean {
        return this.validationPattern.test(apiKey);
      }
    }
  3. Register Provider:

    • Export your new provider class in src/common/providers/index.ts.
    • In src/common/providers/ProviderService.ts, import your new provider and register it within the registerBuiltInProviders method.
    // In ProviderService.ts
    import { MyNewAIProvider } from "./MyNewAIProvider"; // Add import
    
    // ... inside registerBuiltInProviders method ...
    this.registerProvider(new MyNewAIProvider()); // Add this line

After making these changes, please submit a pull request to the project repository.

Security Considerations

  • Plaintext API keys are only held in the memory of the main process transiently when they are decrypted on-demand for immediate use. They are not cached in plaintext.
  • The renderer process never receives plaintext API keys from storage.
  • safeStorage relies on OS-level encryption (e.g., Keychain on macOS, Credential Vault on Windows). The security of the stored keys is tied to the security of the user's OS account.
  • Encrypted keys are stored on disk in the application's user data directory. Ensure this location is properly secured by OS file permissions.

License

The code is released under the MIT license.