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

@centient/secrets

v0.9.0

Published

Cross-platform secrets vault with AES-256-GCM encryption and platform-native key storage

Downloads

211

Readme

@centient/secrets

Cross-platform secrets vault with AES-256-GCM encryption and platform-native key storage.

Daemons / long-running processes: see Session-backed vault (openVault) for the recommended API — single master-key unlock per session, in-memory cached reads, mtime-check coherence with the CLI, rollback protection via monotonic version + sidecar.

Installation

npm install @centient/secrets

Or with pnpm:

pnpm add @centient/secrets

Features

  • AES-256-GCM authenticated encryption for secrets at rest
  • Platform-native key storage (macOS Keychain, Linux secret-service)
  • Pluggable key providers (Keychain, 1Password, passphrase)
  • Credential vault with session management
  • Environment detection (CI, Docker, SSH, headless, agent)
  • Built-in CLI for interactive secret management

Quick Start

import { storeCredential, getCredential, deleteCredential } from "@centient/secrets";

// Store a credential
await storeCredential("my-service", "api-key", "sk-abc123");

// Retrieve it
const value = await getCredential("my-service", "api-key");

// Delete when no longer needed
await deleteCredential("my-service", "api-key");

Encryption Utilities

import { encrypt, decrypt } from "@centient/secrets";

const key = crypto.randomBytes(32);
const encrypted = encrypt("sensitive data", key);
const decrypted = decrypt(encrypted, key);

Platform Detection

import { isCIEnvironment, isDockerContainer, isAgentEnvironment } from "@centient/secrets";

if (isCIEnvironment()) {
  // Use environment variable fallback
}

Key Providers

| Provider | Platform | Description | |----------|----------|-------------| | KeychainProvider | macOS/Linux | Uses OS keychain (Keychain Access / secret-service) | | OnePasswordProvider | Any | Uses 1Password CLI for team secret sharing | | PassphraseProvider | Any (interactive TTY) | Derives the vault key from a typed passphrase via scrypt — no OS keychain required |

Provider auto-detection prefers OS-backed storage: 1Password, then Keychain, then passphrase as the last fallback. Set secrets.provider: "passphrase" in ~/.centient/config.json to select it explicitly.

Per-consumer vault keys

By default KeychainProvider targets a single shared Keychain item (service="centient-vault", account="vault-key"), so every consumer on a machine unlocks its vault with the same master key. Two complementary options let each consumer use its own key (issue #80). Both are additive — with no options the behaviour is byte-identical to before, and existing vaults keep opening.

Name your own Keychain item — the lightweight path. Pass keychain to openVault() (threaded into internal provider resolution) so your consumer's master key lives under its own Keychain item:

import { openVault } from "@centient/secrets";

// Encrypts/decrypts this vault under the "burnrate-vault" Keychain item
// instead of the global "centient-vault" item.
const vault = await openVault({ keychain: { service: "burnrate-vault" } });

Or construct the provider directly:

import { KeychainProvider } from "@centient/secrets";

const provider = new KeychainProvider({ service: "burnrate-vault", account: "k" });

Inject your own provider — full control, and the headless-testability path. Pass keyProvider and openVault() uses it verbatim, skipping internal resolution (config + auto-detection) entirely. This lets you drive openVault() in tests against a throwaway in-memory provider with no real Keychain:

import { openVault, type KeyProvider } from "@centient/secrets";

const stub: KeyProvider = {
  name: "keychain",
  getKey: () => myTestMasterKey, // 32-byte Buffer
  storeKey: () => true,
  deleteKey: () => true,
};

const vault = await openVault({ keyProvider: stub });

A custom provider can also wrap any backend (remote KMS, HSM, env-injected key) as long as it implements the KeyProvider interface.

Passphrase provider

For hosts without an OS keychain or 1Password CLI (e.g. a headless Linux box over SSH), the vault key is derived from a passphrase typed at an interactive terminal using scrypt (N=2^17, r=8, p=1, 32-byte key — ~128 MB memory cost per derivation, in line with current OWASP guidance). The passphrase and the derived key are never persisted. A sidecar file (vault.passphrase.json, mode 0600, beside the vault) stores only the salt, the KDF parameters, and an HMAC-SHA256 verifier used to detect a wrong passphrase without revealing the key.

Security tradeoffs vs OS-backed providers — choose deliberately:

  • Passphrase strength is the security ceiling. Keychain keys are random 256-bit values guarded by the OS; a passphrase-derived key is only as strong as the passphrase. The scrypt cost is the sole brake on brute force.
  • The sidecar enables offline guessing if exfiltrated. An attacker holding vault.passphrase.json (or the vault file) can test candidate passphrases offline at ~one guess per 128 MB-scrypt derivation. Use a long, high-entropy passphrase.
  • No human-presence guarantee. Unlike Keychain with Touch ID, typing a passphrase proves knowledge, not presence; it cannot satisfy policies that require fresh per-operation human auth.
  • Interactive TTY required — fails closed otherwise. In CI, agent, or other non-interactive contexts the provider refuses to prompt and unlock fails with an actionable error. Configure keychain/1Password for non-interactive use.
  • Unlock blocks the event loop. Key derivation is synchronous (~hundreds of ms); daemons should call openVault() once at startup, before entering their hot loop.

License

MIT