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

blindrop-crypto

v1.0.0

Published

Zero-knowledge encryption utilities and atomic operation patterns for secure secret sharing

Readme

blindrop-crypto

Zero-knowledge encryption utilities and atomic operation patterns for building secure secret-sharing applications.

Extracted from Blindrop - a zero-knowledge secret sharing service.

Features

  • AES-256-GCM Encryption - Industry-standard authenticated encryption
  • Zero-Knowledge Architecture - Keys never touch the server
  • Atomic Redis Operations - Lua scripts to prevent race conditions
  • TypeScript First - Full type definitions included

Installation

npm install blindrop-crypto

Zero-Knowledge Encryption

The encryption module implements a zero-knowledge pattern where:

  1. Encryption key is generated client-side
  2. Secret is encrypted before leaving the browser
  3. Key is stored in URL fragment (never sent to server)
  4. Server only sees encrypted ciphertext
import {
  generateKey,
  encrypt,
  decrypt,
  keyToBase64,
  base64ToKey,
} from "blindrop-crypto";

// === SENDER (Client-Side) ===

// Generate a new encryption key
const key = await generateKey();

// Encrypt the secret
const { ciphertext, iv } = await encrypt("my secret password", key);

// Convert key to base64 for URL
const keyBase64 = await keyToBase64(key);

// Send ciphertext + iv to server, keep key in URL fragment
// The fragment (#...) is NEVER sent to the server
const shareUrl = `https://example.com/s/${secretId}#${keyBase64}`;

// === RECIPIENT (Client-Side) ===

// Extract key from URL fragment
const fragment = window.location.hash.slice(1);
const key = await base64ToKey(fragment);

// Fetch ciphertext from server, decrypt client-side
const plaintext = await decrypt(ciphertext, iv, key);
console.log(plaintext); // "my secret password"

Why URL Fragments?

The URL fragment (everything after #) has a special property: browsers never send it to the server. This is defined in RFC 3986.

This means:

  • Server logs don't contain the key
  • Network proxies can't see the key
  • Even if the server is compromised, secrets remain safe

Atomic Redis Operations

When building view-limited secrets, you need atomic decrement operations to prevent race conditions.

The Problem

// VULNERABLE: Race condition!
const secret = await redis.get(key);     // Thread A reads: views = 2
                                          // Thread B reads: views = 2
secret.viewsRemaining--;                  // Both see 2, decrement to 1
await redis.set(key, secret);             // Secret viewed 3+ times!

The Solution

import { ATOMIC_DECREMENT_SCRIPT, parseDecrementResult } from "blindrop-crypto";
import { Redis } from "@upstash/redis";

const redis = new Redis({ url: "...", token: "..." });

// Atomic operation - no race condition possible
const result = await redis.eval(
  ATOMIC_DECREMENT_SCRIPT,
  ["secret:abc123"],
  []
);

const parsed = parseDecrementResult(result);
if (!parsed) {
  console.log("Secret not found or expired");
} else if (parsed.burned) {
  console.log("Last view - secret deleted");
} else {
  console.log(`Views remaining: ${parsed.secret.viewsRemaining}`);
}

How It Works

The Lua script executes atomically in Redis:

local data = redis.call('GET', key)
if not data then return nil end

local secret = cjson.decode(data)
secret.viewsRemaining = secret.viewsRemaining - 1

if secret.viewsRemaining <= 0 then
  redis.call('DEL', key)  -- Burn after reading
  return cjson.encode({secret = secret, burned = true})
else
  local ttl = redis.call('TTL', key)
  redis.call('SET', key, cjson.encode(secret), 'EX', ttl)
  return cjson.encode({secret = secret, burned = false})
end

Security Model

What's Protected

| Threat | Mitigation | |--------|------------| | Server compromise | Zero-knowledge - server never has the key | | Network eavesdropping | Key in fragment, never transmitted | | Database breach | Only encrypted data stored | | Race conditions | Atomic Lua scripts | | Replay attacks | View limits + expiration |

What's NOT Protected

| Threat | Notes | |--------|-------| | Compromised client | If attacker controls browser, game over | | Link interception | If attacker gets the full URL, they can decrypt | | Weak key generation | Uses crypto.subtle which uses secure random |

API Reference

Encryption

| Function | Description | |----------|-------------| | generateKey() | Generate a new AES-256-GCM key | | encrypt(plaintext, key) | Encrypt string, returns { ciphertext, iv } | | decrypt(ciphertext, iv, key) | Decrypt back to string | | keyToBase64(key) | Export key for URL storage | | base64ToKey(base64) | Import key from URL |

Atomic Operations

| Export | Description | |--------|-------------| | ATOMIC_DECREMENT_SCRIPT | Lua script for atomic view counting | | ATOMIC_INCREMENT_WITH_LIMIT_SCRIPT | Lua script for rate limiting | | parseDecrementResult(result) | Parse Lua script response |

Browser Support

Requires browsers with Web Crypto API support:

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

For Node.js, requires Node 18+ (native Web Crypto).

License

MIT - See LICENSE

Credits

Built by BastionForge. See it in action at Blindrop.