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

@mqxym/secure-local-storage

v0.8.1

Published

Secure arbitrary objects in localStorage using AES-GCM envelope encryption with device-bound KEK (IndexedDB) or Argon2id-derived KEK (master password).

Readme

secure-local-storage

Secure arbitrary JSON data in localStorage using AES‑GCM‑256 envelope encryption.

  • Password‑less mode: a non‑extractable KEK is generated and stored in IndexedDB (origin‑bound). Data is automatically available on this device.
  • Master‑password mode: the KEK is derived from a user password via Argon2id (argon2-browser) with memory=64MiB, iterations=20, parallelism=1.

Built for browsers with the Web Crypto API. Bundled for ESM & CJS. Engine: Bun ≥ 1.2.22 (for builds & tests).

⚠️ This library secures data at rest in localStorage. It cannot protect against a compromised page runtime (XSS, devtools, malicious extensions). Always follow secure coding & CSP best practices.


Live Demo

The demo includes most functionality provided by the API: https://mqxym.github.io/secure-local-storage/storage-example.html

Install

bun add @mqxym/secure-local-storage
# or
npm i @mqxym/secure-local-storage

Quick start

import secureLocalStorage from "@mqxym/secure-local-storage";

const sls = secureLocalStorage( {storageKey: "my-sls-storage-name"}); // init (device mode by default)

await sls.setData({ value1: 123, nested: { a: "b" } });

const data = await sls.getData<{ value1: number; nested: { a: string } }>();
console.log(data.value1); // 123
data.clear();             // wipe decrypted copy from memory

// getData() returns a write-protected object.
// To modify it before passing into setData(), create a deep copy
// (e.g., using JSON serialization).

Master password

await sls.setMasterPassword("correct horse battery staple"); // switch to master mode
sls.lock();                                  // remove keys from memory
await sls.unlock("correct horse battery staple"); // derive KEK and unlock

Rotate, export, import

await sls.rotateMasterPassword("old pass", "new pass");
const exported = await sls.exportData("export-pass"); // JSON string
await sls.importData(exported, "export-pass");        // imports and rewraps to device mode by default

API

const sls = secureLocalStorage( {storageKey: "my-sls-storage-name"});

console.log(sls.DATA_VERSION) // returns current data version (3)

// Customized usage
const sls = secureLocalStorage({
  storageKey: "tenant:123", // override localStorage key (recommended)
  idbConfig: {
    dbName: "SLS_KEYS_TENANT123", // override IndexedDB database name
    storeName: "keys",           // override object store name
    keyId: "deviceKek_v1"        // override key record id
  }
});

// Session / mode
await sls.unlock(masterPassword: string); // no-op when uninitialized / password-less mode
await sls.setMasterPassword(masterPassword: string);
await sls.removeMasterPassword();
await sls.rotateMasterPassword(oldMasterPassword: string, newMasterPassword: string); // switches to master password mode when in device key mode
sls.lock();
await sls.rotateKeys(); // password-less only
sls.isUsingMasterPassword() // true / false

// Data
const data = await sls.getData<T extends Record<string, unknown>>();
data.clear(); // securely wipes in-memory decrypted view
await sls.setData(setData: Record<string, unknown>);

// Import / export
const json = await sls.exportData(customExportPassword?: string); // JSON string
await sls.importData(json: string, exportOrMasterPassword?: string);

// Reset
await sls.clear(); // clears localStorage & IndexedDB and reinitializes in device mode

How it works

  • Envelope encryption:

    1. Generate a DEK (CryptoKey, AES‑GCM‑256). DEK encrypts your JSON data.

    2. Wrap (encrypt) the DEK with a KEK.

      • Device mode: KEK is a non‑extractable CryptoKey persisted in IndexedDB (origin‑scoped).
      • Master mode: KEK is derived via Argon2id (64MiB, 20 iters, p=1).
    3. Persist to localStorage:

      {
        "header": { "v": 2, "salt": "", "rounds": 1, "iv": "...", "wrappedKey": "..." },
        "data":   { "iv": "...", "ciphertext": "..." }
      }
  • Non‑extractable keys: KEK is non‑extractable. The DEK is generated extractable only to enable wrapping; when unwrapped for use it is kept non‑extractable. For rewrapping, it’s unwrapped into a short‑lived extractable key.

Input validation & limits

  • All public APIs validate input types and session/mode invariants.
  • localStorage quotas vary by browser (commonly ~5-10 MB). The library throws a StorageFullError if writing exceeds quota.
  • Data must be JSON‑serializable.

Browser support

  • Requires Web Crypto (SubtleCrypto) and IndexedDB. If IndexedDB refuses to store CryptoKey (rare older engines), a memory fallback is used (data remains secure, but device mode becomes ephemeral between reloads). For CI/testing, we polyfill IndexedDB via fake-indexeddb.

Build & test

bun test
bun run build
  • ESM output: dist/esm/sls.browser.min.js
  • CJS output: dist/cjs/sls.browser.min.cjs
  • Types: dist/types/index.d.ts

Security considerations

  • Clearing decrypted views calls a best‑effort memory wipe (overwriting object contents), but JS engines may keep copies; avoid holding long‑lived references to sensitive data.
  • Use strong passwords in master mode. Argon2id settings: 20 iterations, 64 MiB memory, p=1, hashLen=32.
  • Consider Content Security Policy (CSP), dependency pinning, and extension risk mitigation.

License

MIT