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

@neotales/secrets

v0.0.0-alpha.0

Published

Cryptographically secure secret generation and log masking.

Readme

@neotales/secrets

Overview

@neotales/secrets generates cryptographically secure passwords and masks registered sensitive values from strings, logs, and other text output. It works in Deno, Node.js, Bun, and browsers that provide Web Crypto.

Installation

# Deno
deno add jsr:@neotales/secrets

# npm
npm install @neotales/secrets

Generate Secrets

generateSecret and secretGenerator use a cryptographically secure source and require an uppercase letter, lowercase letter, digit, and special character.

import { DefaultSecretGenerator, generateSecret } from "@neotales/secrets";

const password = generateSecret(20);

const databasePassword = new DefaultSecretGenerator()
  .addLower()
  .addUpper()
  .addDigits()
  .add("_-#@")
  .generate(32);

generateAsUint8Array returns a mutable buffer for applications that can clear the secret after use. Character pools must contain single-byte characters so the string and byte APIs have the same length and value.

import { DefaultSecretGenerator } from "@neotales/secrets";

const secret = new DefaultSecretGenerator().addDefaults().generateAsUint8Array(24);
try {
  // Use the secret.
} finally {
  secret.fill(0);
}

Use setValidator when the default password-policy validator does not fit your use case. The validator receives the generated byte buffer and must return true before a secret is returned.

Protect In-Memory Values

Secret encrypts UTF-8 text or bytes with AES-256-GCM and masks itself as ******* when converted to a string, serialized as JSON, or inspected in Node. It is designed to make sensitive values explicit and prevent accidental logging.

import { Secret } from "@neotales/secrets";

const token = await Secret.fromText(process.env.API_TOKEN!);

console.log(token); // *******
console.log(JSON.stringify({ token })); // {"token":"*******"}

await token.withText(async (value) => {
  await sendAuthenticatedRequest(value);
});

token.destroy();

For the default process-local key, createSecret is the shorter factory for either text or bytes. It intentionally accepts no key or IV options.

import { createSecret } from "@neotales/secrets";

const token = await createSecret("api-token");
const binary = await createSecret(new Uint8Array([1, 2, 3]));

The root package is portable and asynchronous because it uses the Web Crypto API, which is asynchronous and requires a secure browser context. Node.js, Deno, and Bun can use the synchronous ./node subpath, which uses node:crypto and AES-256-GCM.

import { Secret } from "@neotales/secrets/node";

const token = Secret.fromText("api-token");
request({ authorization: token.unprotectText() });

The default key is generated randomly on first use, retained only for the current process, and cannot be exported. For values that must be decrypted by a different process or after restart, create and manage an explicit key yourself.

import { Secret, SecretKey } from "@neotales/secrets";

const key = SecretKey.generate();
const secret = await Secret.fromText("api-token", { key });

const rawKey = key.exportRaw();
// Store rawKey in a KMS, OS keychain, or another appropriate secret manager.
rawKey.fill(0);

SecretKey.importRaw restores a previously stored 32-byte key. Exported raw keys are as sensitive as all values they protect and must never be logged.

Encrypt Streams

encryptStream and decryptStream use framed AES-256-GCM. Each frame is at most 64 KiB, uses a unique nonce, and is authenticated. A final authenticated frame detects truncation. The transforms work with browser ReadableStreams as well as runtime web streams.

import { decryptStream, encryptStream, SecretKey } from "@neotales/secrets";

const key = SecretKey.generate();
await source.pipeThrough(encryptStream({ key })).pipeTo(destination);
await encryptedSource.pipeThrough(decryptStream({ key })).pipeTo(plainDestination);

For synchronous chunk processing in Node.js, Deno, and Bun, use encryptChunks and decryptChunks from @neotales/secrets/node.

Cloudflare Workers

The root @neotales/secrets package works in Cloudflare Workers through the standard asynchronous Web Crypto and Web Streams APIs. Do not import @neotales/secrets/node in a Worker: that subpath is for the synchronous node:crypto APIs available in Node.js, Deno, and Bun.

The default key is retained only in the current Worker isolate. Cloudflare can evict an isolate at any time and can handle later requests in a different isolate, so data protected with the default key must not be expected to decrypt across requests, deployments, isolates, or restarts. A newly generated SecretKey has the same limitation.

For data that must be decrypted after an isolate changes, import an explicit 32-byte key from a Workers secret, a KMS, or another durable key-management system with SecretKey.importRaw. Store the key material separately from the ciphertext and never include it in a response, log, or client bundle.

Security Limitations

This module is not a secure-memory implementation or a substitute for key management. It reduces accidental disclosure, but does not protect against a compromised process, debugger, malicious same-origin browser script, or a memory dump that includes the process key.

  • JavaScript provides no portable way to pin memory or guarantee zeroization.
  • destroy() overwrites this module's writable buffers, but runtimes and native crypto implementations may create copies that cannot be cleared by JavaScript.
  • fromText() receives an immutable caller-owned string, and unprotectText() creates another immutable string. Neither can be cleared.
  • withBytes() clears the temporary byte buffer it creates after the callback, but callers can retain or copy that buffer.
  • An explicit SecretKey becomes unusable after destroy(), including for secrets and streams that reference it.
  • The process-local default key is intentionally not persistent. Restarting the process makes values protected with it unrecoverable.

Mask Secrets

Use an isolated DefaultSecretMasker when registrations must not be shared, or use secretMasker for process-wide logging integrations. Plain strings are trimmed, blank values are ignored, and every occurrence is masked. Regular expressions are made global automatically.

import { DefaultSecretMasker } from "@neotales/secrets";

const masker = new DefaultSecretMasker()
  .add("api-token-123")
  .add(/password=\w+/i)
  .addGenerator((secret) => secret.toUpperCase());

masker.mask("token=api-token-123 password=hunter2");
// "token=******* *******"

addGenerator registers transformed forms of string secrets, such as encoded or case-normalized forms. It applies to both existing and subsequently added secrets.

API

| Export | Description | | ------------------------------------- | ----------------------------------------------------- | | generateSecret(length, characters?) | Generate a default-policy secret. | | secretGenerator | Default configured DefaultSecretGenerator. | | DefaultSecretGenerator | Configurable secure secret generator. | | validate(bytes) | Default upper/lower/digit/special password validator. | | secretMasker | Process-wide DefaultSecretMasker. | | DefaultSecretMasker | String and regular-expression secret masker. | | Secret | Encrypted in-memory, log-masked value. | | SecretKey | Caller-managed AES-256-GCM key. | | encryptStream / decryptStream | Portable framed AES-256-GCM stream transforms. |

License

MIT