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

@credninja/vault

v1.0.0

Published

Encrypted local token vault for AI agents. AES-256-GCM, SQLite and file storage, auto-refresh

Readme

@credninja/vault

Local-first encrypted token vault for OAuth credentials.

AES-256-GCM encryption. PBKDF2-SHA256 key derivation (100,000 iterations). Zero cloud dependency. Works offline. Store OAuth tokens securely in a SQLite database or an encrypted JSON file.

No AWS. No KMS. No managed service required. Just a passphrase and a local file.


Installation

npm install @credninja/vault
# SQLite backend (recommended for production):
npm install better-sqlite3

Quick Start

import { createVault } from '@credninja/vault';

const vault = await createVault({
  passphrase: process.env.VAULT_PASSPHRASE!,  // Never stored, only used to derive key
  storage: 'sqlite',                           // or 'file'
  path: './cred-vault.db',                    // path to vault file
});

// Store tokens after OAuth flow
await vault.store({
  provider: 'google',
  userId: 'user-123',
  accessToken: tokens.access_token,
  refreshToken: tokens.refresh_token,
  expiresAt: new Date(Date.now() + tokens.expires_in * 1000),
  scopes: ['calendar.readonly', 'gmail.readonly'],
});

// Retrieve (decrypts automatically)
const creds = await vault.get({ provider: 'google', userId: 'user-123' });
console.log(creds?.accessToken); // ya29.A0AfH6...

// List all connections for a user
const connections = await vault.list({ userId: 'user-123' });

// Delete
await vault.delete({ provider: 'google', userId: 'user-123' });

Storage Backends

SQLite (Recommended)

Best for: production apps, multiple users, concurrent access.

const vault = await createVault({
  passphrase: process.env.VAULT_PASSPHRASE!,
  storage: 'sqlite',
  path: './vault.db',
});

Requires better-sqlite3 as a runtime dependency. Auto-creates the table on first use.

Encrypted JSON File

Best for: CLI tools, single-user scripts, zero-dep environments.

const vault = await createVault({
  passphrase: process.env.VAULT_PASSPHRASE!,
  storage: 'file',
  path: './vault.json',
});

No additional dependencies. Atomic writes (temp file + rename) prevent corruption. The JSON file stores ciphertext: each token value is individually AES-256-GCM encrypted.


Auto-Refresh

If you provide an OAuth adapter (compatible with @credninja/oauth), the vault will automatically refresh expired tokens when you call get():

import { createVault } from '@credninja/vault';
import { OAuthClient, GoogleAdapter } from '@credninja/oauth';

const vault = await createVault({ passphrase: '...', storage: 'sqlite', path: './vault.db' });

const adapter = new GoogleAdapter();

const creds = await vault.get({
  provider: 'google',
  userId: 'user-123',
  adapter,                                  // Enables auto-refresh
  clientId: process.env.GOOGLE_CLIENT_ID!,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
});
// If the token was expired, it's now refreshed and the new token is already persisted.
// creds.accessToken is guaranteed to be fresh (or null if refresh failed).

Security Model

Encryption

Every access token and refresh token is individually encrypted using AES-256-GCM before being written to disk. The encryption is authenticated: any tampering with the ciphertext will cause decryption to throw, never silently corrupt.

Key Derivation

Your passphrase is never stored. Instead, it's used to derive a 256-bit AES key via PBKDF2-SHA256 with 100,000 iterations:

key = PBKDF2-SHA256(passphrase, salt, iterations=100000, keyLength=32)

The salt is random (32 bytes) and stored in a .salt file alongside your vault. The salt is not secret. Only your passphrase needs to be kept private.

IV (Initialization Vector)

Each encryption operation uses a fresh random 16-byte IV (crypto.randomBytes(16)). This ensures that encrypting the same token twice produces different ciphertext, preventing pattern analysis.

What's on Disk

  • Ciphertext (hex-encoded)
  • IV (hex-encoded, 16 bytes)
  • GCM auth tag (hex-encoded, 16 bytes)
  • Salt (hex-encoded, 32 bytes, stored in .salt file)

What's never on disk: passphrase, derived key, plaintext tokens.

Threat Model

✅ Protects against: stolen vault file, compromised storage backend, unauthorized file access
✅ Guarantees: wrong passphrase throws (GCM auth tag verification), never returns garbage
⚠️ Does not protect against: compromise of the machine while the vault is open (key is in memory), keyloggers capturing the passphrase


API Reference

createVault(options)Promise<CredVault>

Factory function that creates and initializes a vault in one call.

| Option | Type | Description | |--------|------|-------------| | passphrase | string | Encryption passphrase. Never stored. | | storage | 'sqlite' \| 'file' | Backend type | | path | string | Path to vault file (.db or .json) |

vault.store(input)

Store (or update) credentials for a provider + userId.

vault.get(input)VaultEntry | null

Retrieve credentials. Returns null if not found. Auto-refreshes if expired + adapter provided.

vault.delete(input)

Remove credentials. Idempotent.

vault.list({ userId })VaultEntry[]

List all provider connections for a userId.


Custom Storage Backends

Implement the StorageBackend interface to add Redis, PostgreSQL, or any other store:

import type { StorageBackend } from '@credninja/vault';

class RedisBackend implements StorageBackend {
  async init() { /* connect */ }
  async store(row) { /* HSET */ }
  async get(provider, userId) { /* HGET */ }
  async delete(provider, userId) { /* HDEL */ }
  async list(userId) { /* HSCAN */ }
}

Cred Cloud (Coming Soon)

@credninja/vault is great for single-machine and CLI use. When you need:

  • Multi-tenant isolation across thousands of users
  • Managed token refresh (background daemon, no polling)
  • Agent identity verification (Ed25519 receipts)
  • Enterprise audit logging
  • TEE-grade secret storage

Managed cloud vault is coming. Join the waitlist.


License

MIT