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

@bantis/local-cipher

v2.1.0

Published

Client-side encryption for localStorage - Framework-agnostic with React and Angular support

Downloads

501

Readme

@bantis/local-cipher

npm version npm downloads License: MIT TypeScript

Client-side encryption for localStorage using AES-256-GCM

Protect sensitive data in browser storage from XSS attacks, local file access, and casual inspection. Drop-in replacement for localStorage with automatic encryption/decryption.

Problem

localStorage stores data in plain text. Anyone with access to DevTools, browser files, or malicious scripts can read:

  • Authentication tokens
  • User credentials
  • API keys
  • Personal information

Solution

Transparent AES-256-GCM encryption with browser fingerprinting. Data is encrypted before storage and decrypted on retrieval. Keys are derived from browser characteristics, making data unreadable outside the original browser context.

Quick Start

npm install @bantis/local-cipher
import { SecureStorage } from '@bantis/local-cipher';

const storage = SecureStorage.getInstance();

// Store encrypted
await storage.setItem('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');

// Retrieve decrypted
const token = await storage.getItem('token');

// Works like localStorage
await storage.removeItem('token');
storage.clear();

Before:

localStorage: { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }

After:

localStorage: { "__enc_a7f5d8e2": "Qm9keUVuY3J5cHRlZERhdGE..." }

Features

  • AES-256-GCM encryption with authentication
  • PBKDF2 key derivation (100k+ iterations)
  • Browser fingerprinting for unique keys per device
  • Key obfuscation - even key names are encrypted
  • TTL/Expiration - auto-delete expired data
  • Event system - monitor storage operations
  • Compression - automatic gzip for large values
  • Namespaces - organize data in isolated spaces
  • Integrity checks - SHA-256 checksums
  • TypeScript - full type definitions
  • Framework support - React hooks, Angular service

Use Cases

1. Protect Authentication Tokens

// Store JWT with 1-hour expiration
await storage.setItemWithExpiry('accessToken', jwt, { 
  expiresIn: 3600000 
});

// Auto-cleanup expired tokens
storage.on('expired', ({ key }) => {
  console.log(`Token ${key} expired, redirecting to login`);
  window.location.href = '/login';
});

2. Secure User Preferences

const userStorage = storage.namespace('user');
await userStorage.setItem('theme', 'dark');
await userStorage.setItem('language', 'en');

// Isolated from other namespaces
const appStorage = storage.namespace('app');

3. Cache Sensitive API Responses

// Store with compression for large data
const storage = SecureStorage.getInstance({
  storage: { compression: true, compressionThreshold: 512 }
});

await storage.setItem('userData', JSON.stringify(largeObject));

React Integration

import { useSecureStorage, useSecureStorageEvents } from '@bantis/local-cipher';

function App() {
  const [token, setToken, loading] = useSecureStorage('token', '');
  
  useSecureStorageEvents('expired', () => {
    // Handle expiration
  });

  if (loading) return <div>Loading...</div>;
  
  return <div>Token: {token}</div>;
}

🚀 Quick Start

JavaScript Vanilla

import { SecureStorage } from '@bantis/local-cipher';

const storage = SecureStorage.getInstance();

// Store encrypted
await storage.setItem('accessToken', 'mi-token-secreto');

// Retrieve decrypted
const token = await storage.getItem('accessToken');

// With expiration (1 hour)
await storage.setItemWithExpiry('session', sessionData, { expiresIn: 3600000 });

// Remove
await storage.removeItem('accessToken');

Before:

localStorage: { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }

After:

localStorage: { "__enc_a7f5d8e2": "Qm9keUVuY3J5cHRlZERhdGE..." }

React

import { useSecureStorage } from '@bantis/local-cipher/react';

function App() {
  const [token, setToken, loading] = useSecureStorage('accessToken', '');

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <p>Token: {token}</p>
      <button onClick={() => setToken('nuevo-token')}>
        Update Token
      </button>
    </div>
  );
}

Angular

import { SecureStorageService } from '@bantis/local-cipher/angular';

@Component({
  selector: 'app-root',
  template: `<div>{{ token$ | async }}</div>`
})
export class AppComponent {
  token$ = this.storage.getItem('accessToken');

  constructor(private storage: SecureStorageService) {}

  saveToken(token: string) {
    this.storage.setItem('accessToken', token).subscribe();
  }
}

Angular Integration

import { SecureStorageService } from '@bantis/local-cipher';

@Component({...})
export class AppComponent {
  token$ = this.storage.getItem('token');

  constructor(private storage: SecureStorageService) {
    this.storage.events$.subscribe(event => {
      console.log('Storage event:', event);
    });
  }
}

Configuration

const storage = SecureStorage.getInstance({
  encryption: {
    iterations: 150000,      // PBKDF2 iterations
    keyLength: 256,          // 128, 192, or 256 bits
    saltLength: 16,          // Salt size in bytes
    ivLength: 12,            // IV size in bytes
  },
  storage: {
    compression: true,       // Enable gzip compression
    compressionThreshold: 1024,  // Compress if > 1KB
    autoCleanup: true,       // Auto-delete expired items
    cleanupInterval: 60000   // Cleanup every 60s
  },
  debug: {
    enabled: false,          // Enable debug logging
    logLevel: 'info'         // silent, error, warn, info, debug, verbose
  }
});

Security

What This Protects Against

XSS attacks - Encrypted data is useless without the browser-specific key
Local file access - Malware reading browser files gets encrypted data
Casual inspection - DevTools shows encrypted values
Data tampering - Integrity checks detect modifications

What This Does NOT Protect Against

Server-side attacks - Encryption is client-side only
Man-in-the-Middle - Use HTTPS for data in transit
Memory dumps - Keys exist in memory during runtime
Compromised browser - If the browser is compromised, all bets are off
Physical access during active session - Data is decrypted when accessed

Best Practices

  1. Use HTTPS - Always transmit data over secure connections
  2. Short TTLs - Set expiration on sensitive data
  3. Clear on logout - Call storage.clear() when user logs out
  4. Monitor events - Track suspicious activity via event listeners
  5. Rotate keys - Periodically call storage.rotateKeys()
  6. Don't store passwords - Never store plaintext passwords, even encrypted

Browser Support

Requires Web Crypto API:

  • Chrome 37+
  • Firefox 34+
  • Safari 11+
  • Edge 12+
  • Opera 24+

Fallback: Gracefully degrades to unencrypted localStorage in unsupported browsers.

API Reference

Core Methods

setItem(key: string, value: string): Promise<void>
getItem(key: string): Promise<string | null>
removeItem(key: string): Promise<void>
hasItem(key: string): Promise<boolean>
clear(): void

Expiration

setItemWithExpiry(key: string, value: string, options: {
  expiresIn?: number;    // milliseconds from now
  expiresAt?: Date;      // absolute date
}): Promise<void>

cleanExpired(): Promise<number>  // Returns count of deleted items

Events

on(event: StorageEventType, listener: EventListener): void
off(event: StorageEventType, listener: EventListener): void
once(event: StorageEventType, listener: EventListener): void

// Event types: 'encrypted', 'decrypted', 'deleted', 'cleared', 
//              'expired', 'error', 'keyRotated', 'compressed'

Namespaces

namespace(name: string): NamespacedStorage

const userStorage = storage.namespace('user');
await userStorage.setItem('profile', data);
await userStorage.clearNamespace();

Key Rotation

rotateKeys(): Promise<void>
exportEncryptedData(): Promise<EncryptedBackup>
importEncryptedData(backup: EncryptedBackup): Promise<void>

FAQ

Q: Is this secure enough for passwords?
A: No. Never store passwords in localStorage, even encrypted. Use secure, httpOnly cookies or sessionStorage with server-side session management.

Q: Can data be decrypted on another device?
A: No. Keys are derived from browser fingerprinting. Data encrypted on Chrome/Windows cannot be decrypted on Firefox/Mac.

Q: What happens if Web Crypto API is unavailable?
A: The library falls back to unencrypted localStorage with a console warning. Check EncryptionHelper.isSupported() to detect support.

Q: Does this protect against XSS?
A: Partially. It makes stolen data harder to use, but XSS can still intercept data when it's decrypted in memory. Use CSP headers and sanitize inputs.

Q: How is this different from sessionStorage?
A: sessionStorage is cleared on tab close. This provides persistent, encrypted storage across sessions.

Q: Can I use this in Node.js?
A: No. This library requires browser APIs (Web Crypto, localStorage). For Node.js, use native crypto module.

Q: What's the performance impact?
A: Encryption adds ~2-5ms per operation. Compression adds ~5-10ms for large values. Negligible for most use cases.

Migration from v1

v1 data is automatically migrated to v2 format on first read. No action required.

// v1 and v2 are API-compatible
const storage = SecureStorage.getInstance();  // Works with both

Migration from v2.0.x to v2.1.0

[!WARNING] Breaking Change: Framework-specific imports required

For React Users

Before (v2.0.x):

import { useSecureStorage } from '@bantis/local-cipher';

After (v2.1.0):

import { useSecureStorage } from '@bantis/local-cipher/react';

For Angular Users

Before (v2.0.x):

import { SecureStorageService } from '@bantis/local-cipher';

After (v2.1.0):

import { SecureStorageService } from '@bantis/local-cipher/angular';

For Core/Vanilla JS Users

No changes required:

import { SecureStorage } from '@bantis/local-cipher';  // Still works

Why This Change?

v2.0.x bundled all framework code together, causing dependency conflicts:

  • React projects needed Angular dependencies (@angular/core, rxjs)
  • Vanilla JS projects loaded unused React/Angular code
  • 40% larger bundle size for non-framework users

v2.1.0 separates frameworks into independent bundles:

  • ✅ Core: 42KB (no framework dependencies)
  • ✅ React: 49KB (core + hooks)
  • ✅ Angular: 55KB (core + service)

FAQ