Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .git-hooks/pre-commit.mts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ const main = (): number => {
}
}
out(
"Use `getDefaultLogger()` from `@socketsecurity/lib/logger`. " +
'Use `getDefaultLogger()` from `@socketsecurity/lib/logger`. ' +
'For documentation lines that need the literal call, append ' +
'the marker `# socket-hook: allow logger`.',
)
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [5.27.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.27.0) - 2026-05-01

### Added

- `crypto` (new export) — `hash(algorithm, data, encoding)` one-shot helper that prefers Node's native `crypto.hash` (added v21.7.0 / v20.12.0; ~30% faster than `createHash().update().digest()` on small inputs) with a streaming fallback. `getNativeHash` exposed as `@internal` for tests
- `promises` `fromAsync<T>(source)` — drains an async iterable into an array, per [TC39 Array.fromAsync](https://tc39.es/proposal-array-from-async/). Backed by the new `ArrayFromAsync` primordial (Node 22+) with a `for await` + push fallback
- `primordials` `ArrayFromAsync` — ES2024 primordial. Unbound, matching `ArrayFrom`
- `globs` `getGlobMatcher` fast-paths single non-negated patterns through `path.matchesGlob` (Node 22.5+ / 20.17+) instead of compiling picomatch, with results stored in the existing LRU
- `globs` `glob` / `globSync` route through `node:fs.glob` / `node:fs.globSync` (Node 22+) when caller options reduce to `cwd` + `ignore` (mapped to `exclude`); fall back to fast-glob for the wider option surface

### Changed

- `http-request` retry/backoff sites use `setTimeout` from `node:timers/promises` instead of hand-rolled `new Promise(r => setTimeout(r, ms))`
- `dlx/cache`, `dlx/integrity`, `dlx/binary` — 4 one-shot hash sites switched to the new `crypto.hash()` helper

## [5.26.2](https://github.com/SocketDev/socket-lib/releases/tag/v5.26.2) - 2026-04-30

### Fixed
Expand Down
1 change: 1 addition & 0 deletions docs/api-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Each entry links to the source module and shows the first sentence of its `@file
| [`@socketsecurity/lib/cacache`](../src/cacache.ts) | Cacache utilities for Socket ecosystem shared content-addressable cache. |
| [`@socketsecurity/lib/cache-with-ttl`](../src/cache-with-ttl.ts) | Generic TTL-based caching utility using cacache. |
| [`@socketsecurity/lib/colors`](../src/colors.ts) | Color utilities for RGB color conversion and manipulation. |
| [`@socketsecurity/lib/crypto`](../src/crypto.ts) | Crypto helpers that prefer Node builtins where available. |
| [`@socketsecurity/lib/debug`](../src/debug.ts) | Debug logging utilities with lazy loading and environment-based control. |
| [`@socketsecurity/lib/env`](../src/env.ts) | Environment variable parsing and conversion utilities. |
| [`@socketsecurity/lib/errors`](../src/errors.ts) | Error utilities with cause chain support. |
Expand Down
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{
"name": "@socketsecurity/lib",
"version": "5.26.2",
"version": "5.27.0",
"packageManager": "pnpm@11.0.3",
"license": "MIT",
"publishConfig": {
"access": "public",
"provenance": true
},
"description": "Core utilities and infrastructure for Socket.dev security tools",
"keywords": [
"Socket.dev",
Expand Down Expand Up @@ -219,6 +223,10 @@
"types": "./dist/cover/types.d.ts",
"default": "./dist/cover/types.js"
},
"./crypto": {
"types": "./dist/crypto.d.ts",
"default": "./dist/crypto.js"
},
"./debug": {
"types": "./dist/debug.d.ts",
"default": "./dist/debug.js"
Expand Down
91 changes: 91 additions & 0 deletions src/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* @fileoverview Crypto helpers that prefer Node builtins where available.
*/

let _crypto: typeof import('node:crypto') | undefined
// `crypto.hash(algorithm, data, outputEncoding)` was added in Node
// v21.7.0 / v20.12.0 (Stable). Engines is >=22, so it's always present
// here in practice — feature-detect anyway because the type surface
// for `node:crypto` doesn't include it on every TS lib version.
//
// `_hash` is the resolved native function (or `undefined` if absent
// or not yet probed). `_hashProbed` distinguishes the two cases so a
// missing native is detected only once.
let _hash: typeof import('node:crypto').hash | undefined
let _hashProbed = false

/**
* Lazily load the `node:crypto` module.
*
* @private
*/
/*@__NO_SIDE_EFFECTS__*/
function getCrypto() {
if (_crypto === undefined) {
_crypto = /*@__PURE__*/ require('node:crypto')
}
return _crypto as typeof import('node:crypto')
}

/**
* Resolve `crypto.hash` (or `undefined` if the runtime predates it).
*
* Exported for unit tests; not part of the public API.
*
* @internal
*/
/*@__NO_SIDE_EFFECTS__*/
export function getNativeHash(): typeof import('node:crypto').hash | undefined {
if (!_hashProbed) {
const fn = (
getCrypto() as typeof import('node:crypto') & {
hash?: unknown
}
).hash
if (typeof fn === 'function') {
_hash = fn as typeof import('node:crypto').hash
}
_hashProbed = true
}
return _hash
}

/**
* Compute a one-shot cryptographic hash.
*
* Prefers Node's `crypto.hash(algorithm, data, outputEncoding)` (added
* v21.7.0 / v20.12.0), which is ~30% faster than the
* `createHash().update().digest()` chain on small-to-medium inputs
* because it skips constructing the streaming `Hash` object. Falls
* back to that chain on older runtimes.
*
* Use this only for the one-shot case where the entire input is
* available as a single buffer or string; if you need to feed chunks,
* stay on `createHash()`.
*
* @example
* ```typescript
* import { hash } from '@socketsecurity/lib/crypto'
*
* hash('sha256', 'hello', 'hex')
* // '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
*
* hash('sha512', someBuffer, 'base64')
* // 'z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg=='
* ```
*/
/*@__NO_SIDE_EFFECTS__*/
export function hash(
algorithm: string,
data: string | NodeJS.ArrayBufferView,
outputEncoding: 'hex' | 'base64' | 'base64url' | 'binary',
): string {
const native = getNativeHash()
if (native !== undefined) {
return native(algorithm, data, outputEncoding) as string
}
return getCrypto()
.createHash(algorithm)
.update(data as string | Buffer)
.digest(outputEncoding)
}
13 changes: 3 additions & 10 deletions src/dlx/binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { normalizePath } from '../paths/normalize'
import { getSocketDlxDir } from '../paths/socket'
import { processLock } from '../process-lock'
import { spawn } from '../spawn'
import { hash } from '../crypto'
import { generateCacheKey } from './cache'
import { normalizeHash } from './integrity'

Expand Down Expand Up @@ -614,11 +615,7 @@ export async function downloadBinaryFile(
if (stats.size > 0) {
// File exists, compute and return SRI integrity hash.
const fileBuffer = await fs.promises.readFile(destPath)
const hash = crypto
.createHash('sha512')
.update(fileBuffer)
.digest('base64')
return `sha512-${hash}`
return `sha512-${hash('sha512', fileBuffer, 'base64')}`
}
}

Expand All @@ -638,11 +635,7 @@ export async function downloadBinaryFile(

// Compute SRI integrity hash of downloaded file.
const fileBuffer = await fs.promises.readFile(destPath)
const hash = crypto
.createHash('sha512')
.update(fileBuffer)
.digest('base64')
const actualIntegrity = `sha512-${hash}`
const actualIntegrity = `sha512-${hash('sha512', fileBuffer, 'base64')}`

// Verify integrity if provided (constant-time comparison).
if (integrity) {
Expand Down
18 changes: 2 additions & 16 deletions src/dlx/cache.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
/** @fileoverview Cache key generation utilities for DLX package installations. */

let _crypto: typeof import('node:crypto') | undefined
/**
* Lazily load the crypto module to avoid Webpack errors.
* @private
*/
/*@__NO_SIDE_EFFECTS__*/
function getCrypto() {
if (_crypto === undefined) {
// Use non-'node:' prefixed require to avoid Webpack errors.

_crypto = /*@__PURE__*/ require('node:crypto')
}
return _crypto as typeof import('node:crypto')
}
import { hash } from '../crypto'

/**
* Generate a cache directory name using npm/npx approach.
Expand Down Expand Up @@ -46,6 +33,5 @@ function getCrypto() {
* ```
*/
export function generateCacheKey(spec: string): string {
const crypto = getCrypto()
return crypto.createHash('sha512').update(spec).digest('hex').substring(0, 16)
return hash('sha512', spec, 'hex').substring(0, 16)
}
8 changes: 5 additions & 3 deletions src/dlx/integrity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
* form carried around internally is always the object.
*/

import { createHash, timingSafeEqual } from 'node:crypto'
import { timingSafeEqual } from 'node:crypto'

import { hash } from '../crypto'

import {
BufferFrom,
Expand Down Expand Up @@ -130,8 +132,8 @@ export function normalizeHash(spec: HashSpec): NormalizedHash {
* buffer of bytes.
*/
export function computeHashes(bytes: Buffer): ComputedHashes {
const integrity = `sha512-${createHash('sha512').update(bytes).digest('base64')}`
const checksum = createHash('sha256').update(bytes).digest('hex')
const integrity = `sha512-${hash('sha512', bytes, 'base64')}`
const checksum = hash('sha256', bytes, 'hex')
return { integrity, checksum }
}

Expand Down
Loading
Loading