
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
unsecure is a collection of runtime-agnostic cryptographically-secure utilities. (ba dum tss 🥁)
Install the package:
# ✨ Auto-detect (supports npm, yarn, pnpm, deno and bun)
npx nypm install unsecure
Import:
ESM (Node.js, Bun, Deno)
// Main functions
import {
hash,
hmac,
hmacVerify,
secureGenerate,
secureCompare,
entropy,
sanitizeObject,
// OTP
hotp,
hotpVerify,
totp,
totpVerify,
generateOTPSecret,
otpauthURI,
// Randomness
createSecureRandomGenerator,
secureRandomNumber,
secureRandomBytes,
secureShuffle,
randomJitter,
} from "unsecure";
// Utility functions
import {
hexEncode,
hexDecode,
base64Encode,
base64Decode,
base32Encode,
base32Decode,
// ...
} from "unsecure/utils";
CDN (Deno, Bun and Browsers)
import { hash, hmac, totp, secureGenerate } from "https://esm.sh/unsecure";
import { hexEncode, base64Encode, base32Encode } from "https://esm.sh/unsecure/utils";
Hashes input data using a specified cryptographic algorithm. It uses the Web Crypto API, so it works in any modern JavaScript runtime.
options:
SHA-1, SHA-256, SHA-384, SHA-512 (default SHA-256)hex, base64, base64url, bytes (default hex)[!WARNING]
hash()operates on complete data. The Web Crypto API does not support incremental/streaming digests, so for hashing large streams (e.g. file uploads) you'll need a platform-specific API like Node.js'scrypto.createHash()or Deno'scrypto.subtle.digestStream().
import { hash } from "unsecure";
// Hash an input using the default SHA-256 and return as a hex string
const hashHex = await hash("hello world");
// 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'
// Hash an input using the default SHA-256 and return as a base64 string
const hashBase64 = await hash("hello world", { returnAs: "base64" });
// 'UhywQV8aBkKEVtnvTpSMAnCoBkQjJSU8t6imt+Q9qcc='
// Hash an input using the default SHA-256 and return as a base64 URL string
const hashBase64URL = await hash("hello world", { returnAs: "base64url" });
// 'UhywQV8aBkKEVtnvTpSMAnCoBkQjJSU8t6imt-Q9qcc'
// Hash and get raw bytes (Uint8Array)
const hashBytes = await hash("hello world", { returnAs: "bytes" });
// Uint8Array(32) [ 185, 77, 39, 185, 147, 77, ... ]
// Hash using SHA-512
const hash512 = await hash("hello world", { algorithm: "SHA-512" });
// '309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f...'
Computes an HMAC signature using the Web Crypto API. Supports the same algorithms and output formats as hash(). When returnAs is not specified, the output type mirrors the input: string data returns a hex string, BufferSource data returns a Uint8Array.
options:
SHA-1, SHA-256, SHA-384, SHA-512 (default SHA-256)hex, base64, base64url, bytes (default mirrors input type)import { hmac, hmacVerify } from "unsecure";
// Sign a string — returns hex by default
const sig = await hmac("my-secret", "hello world");
// 'a3a65e5...'
// Sign with SHA-512 and return as base64
const sig64 = await hmac("my-secret", payload, {
algorithm: "SHA-512",
returnAs: "base64",
});
// Verify a webhook signature in constant time
const expected = request.headers["x-hub-signature-256"].replace("sha256=", "");
const valid = await hmacVerify(webhookSecret, requestBody, expected);
// true or false
// Verify a base64-encoded signature
const valid = await hmacVerify(secret, body, expectedBase64Sig, {
returnAs: "base64",
});
RFC 4226 (HOTP) and RFC 6238 (TOTP) one-time password generation and verification, built on top of hmac().
Secrets can be passed as raw Uint8Array bytes or as a base32-encoded string.
[!NOTE] The RFCs recommend the secret to be at least as long as the hash output (20 bytes for SHA-1, 32 for SHA-256, 48 for SHA-384, 64 for SHA-512). The default
generateOTPSecret()produces 20 bytes, which works with any algorithm but is ideal for SHA-1. UsegenerateOTPSecret(32)orgenerateOTPSecret(64)when targeting SHA-256 or SHA-512.
Generate and verify HMAC-based One-Time Passwords (RFC 4226).
options:
SHA-1, SHA-256, SHA-384, SHA-512 (default SHA-1)6)0)import { hotp, hotpVerify } from "unsecure";
// Generate an OTP for counter 0
const code = await hotp(secretBytes, 0);
// "755224"
// Generate an 8-digit OTP
const code8 = await hotp(secretBytes, 0, { digits: 8 });
// Verify an OTP
const { valid, delta } = await hotpVerify(secret, "287082", 0, { window: 5 });
// valid: true, delta: 1 (matched at counter 0 + 1)
Generate and verify Time-based One-Time Passwords (RFC 6238).
options:
SHA-1, SHA-256, SHA-384, SHA-512 (default SHA-1)6)30)1)import { totp, totpVerify } from "unsecure";
// Generate a TOTP for the current time
const code = await totp(base32Secret);
// Verify a user-provided code (checks current, previous, and next time steps)
const { valid, delta } = await totpVerify(secret, userCode);
// delta: 0 = current step, -1 = previous, +1 = next
Generates a cryptographically random OTP secret, returned as a base32-encoded string (without padding).
import { generateOTPSecret } from "unsecure";
const secret = generateOTPSecret();
// "JBSWY3DPEHPK3PXP..."
// Custom length (32 bytes for SHA-256)
const secret256 = generateOTPSecret(32);
Builds an otpauth:// URI for provisioning OTP tokens via QR code.
import { otpauthURI } from "unsecure";
const uri = otpauthURI({
type: "totp",
secret: base32Secret,
account: "user@example.com",
issuer: "MyApp",
});
// "otpauth://totp/MyApp:user%40example.com?secret=...&issuer=MyApp&algorithm=SHA1&digits=6&period=30"
// HOTP URI (counter is required)
const hotpUri = otpauthURI({
type: "hotp",
secret: secretBytes,
account: "user@example.com",
counter: 0,
});
Generates a cryptographically secure string. You can customize its length and character set (all enabled by default). If a string is passed it will be used as a set of allowed characters.
Internally it uses a buffer, which is constantly updated, to minimize Web Crypto API calls and greatly improve performance. This becomes useful when generating 128-512 characters long tokens.
import { secureGenerate } from "unsecure";
// Generate a default 16-character password
const password = secureGenerate();
// e.g. 'Zk(p4@L!v9{g~8sB'
// Generate a 28-character password
const password = secureGenerate({ length: 28 });
// e.g. '4~j&zgf-tO+PoMBVl}tK/}5$FgzF'
// Generate a 24-character token with no special characters
const token = secureGenerate({
length: 24,
specials: false,
});
// Generate a pin using only custom numbers
const pin = secureGenerate({
length: 6,
uppercase: false,
lowercase: false,
specials: false,
numbers: "13579",
});
// Generate a token with current timestamp prefix
const stamp = secureGenerate({ length: 20, timestamp: true });
// Output example: "mi6dcq...random..."
// Generate a token with a specific date prefix
const date = new Date("2023-01-01T00:00:00Z");
const datestamp = secureGenerate({ length: 20, timestamp: date });
// Output example: "lcclw5...random..."
Compares two values (string or Uint8Array) in a timing-attack-safe manner. The first argument (expected) is always the trusted, server-side value, which determines the loop length. The second argument (received) is the untrusted, user-provided value.
import { secureCompare } from "unsecure";
const expected = "my-super-secret-token";
const received = "my-super-secret-token"; // from user input
if (secureCompare(expected, received)) {
console.log("Tokens match!");
} else {
console.log("Tokens do not match!");
}
// Also works with Uint8Array and mixed types
const mac1 = new Uint8Array([1, 2, 3]);
const mac2 = new Uint8Array([1, 2, 3]);
secureCompare(mac1, mac2); // true
secureCompare(expected, mac2); // false
// Handles undefined received gracefully (returns false)
secureCompare(expected, undefined); // false
Computes Shannon entropy of a string or Uint8Array. Useful for measuring the quality of generated tokens, passwords, or random data.
import { entropy } from "unsecure";
// Measure a generated token
const result = entropy("Zk(p4@L!v9{g~8sB");
console.log(result.bits); // ~60+ bits for a strong 16-char token
console.log(result.bitsPerSymbol); // entropy per character
console.log(result.uniqueSymbols); // number of distinct characters
// Detect a weak secret
entropy("aaaaaaa").bitsPerSymbol; // 0 — completely predictable
// Analyze random bytes
const bytes = new Uint8Array(256);
crypto.getRandomValues(bytes);
entropy(bytes).bitsPerSymbol; // ~7.9+ (close to max of 8 for 256 byte values)
Includes createSecureRandomGenerator, secureRandomNumber, secureRandomBytes, secureShuffle, and randomJitter.
import {
createSecureRandomGenerator,
secureRandomNumber,
secureRandomBytes,
secureShuffle,
randomJitter,
} from "unsecure";
// Creates a secure random number generator (more performant for subsequent calls)
const generator = createSecureRandomGenerator();
// Get a random number in range [0, max)
const n = generator.next(1000); // 0 to 999
// Get a random number in range [min, max)
const n2 = generator.next(50, 150); // 50 to 149
// Exclude specific values using an array or Set
const n3 = generator.next(10, [3, 5, 7]); // 0-9, excluding 3, 5, 7
const n4 = generator.next(50, 100, new Set([55, 60, 65])); // 50-99, excluding 55, 60, 65
// Get a secure random number (more memory-efficient for single use)
const num = secureRandomNumber(100); // 0 to 99
const num2 = secureRandomNumber(50, 150); // 50 to 149
const num3 = secureRandomNumber(10, [2, 4, 6]); // 0-9, excluding 2, 4, 6
// Generate random bytes
const key = secureRandomBytes(32); // 256-bit key material (Uint8Array)
// Securely shuffle an array in-place
const list = [1, 2, 3, 4, 5];
secureShuffle(list); // e.g. [ 3, 1, 5, 2, 4 ]
// Or spread the original to not mutate it
const shuffled = secureShuffle([...list]);
// Reuse generator for better performance when shuffling multiple times
const gen = createSecureRandomGenerator();
secureShuffle(list1, gen);
secureShuffle(list2, gen);
// Add a random delay as defense-in-depth against timing side-channels
await randomJitter(); // 0-99ms
await randomJitter(50); // 0-49ms
await randomJitter(50, 100); // 50-99ms
unsecure/utils)A collection of supplementary utilities for encoding and decoding.
Includes hexEncode, hexDecode, base64Encode, base64Decode, base64UrlEncode, base64UrlDecode, base32Encode, and base32Decode.
import { hexEncode, hexDecode, base32Encode, base32Decode } from "unsecure/utils";
const hex = hexEncode("hello"); // "68656c6c6f"
const text = hexDecode(hex); // "hello"
// Base32 (RFC 4648) — commonly used for OTP secrets
const b32 = base32Encode("foobar"); // "MZXW6YTBOI======"
const decoded = base32Decode("MZXW6YTBOI"); // Uint8Array (handles unpadded, case-insensitive)
Remove prototype-pollution vectors from a plain record in-place. It strips the dangerous own properties __proto__, prototype, and constructor from the target and all nested objects/arrays. It returns the same reference you pass in.
import { sanitizeObject } from "unsecure";
const payload: Record<string, unknown> = {
user: {
name: "alice",
// potential pollution vectors
__proto__: { hacked: true },
profile: [{ constructor: "bad" }, { prototype: { x: 1 } }],
},
};
sanitizeObject(payload); // returns the same reference
// After sanitization
Object.hasOwn(payload.user, "__proto__"); // false
Object.hasOwn(payload.user.profile[0]!, "constructor"); // false
Object.hasOwn(payload.user.profile[1]!, "prototype"); // false
Notes:
__proto__, prototype, and constructor are removed.Inspired by DeepSource Corp work.
Published under the MIT license.
Made by community 💛
🤖 auto updated with automd
FAQs
Low-level Security utilities
We found that unsecure demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.