@socketsecurity/lib 5.19.1 → 5.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +105 -74
  2. package/dist/archives.js +13 -0
  3. package/dist/cacache.js +6 -8
  4. package/dist/cache-with-ttl.d.ts +7 -0
  5. package/dist/cache-with-ttl.js +27 -8
  6. package/dist/constants/socket.js +1 -1
  7. package/dist/dlx/detect.js +25 -8
  8. package/dist/dlx/lockfile.js +4 -1
  9. package/dist/dlx/manifest.d.ts +10 -4
  10. package/dist/dlx/package.d.ts +1 -1
  11. package/dist/dlx/package.js +19 -3
  12. package/dist/external/@npmcli/package-json/lib/read-package.js +40 -32
  13. package/dist/external/@npmcli/package-json/lib/sort.js +104 -92
  14. package/dist/external/@npmcli/package-json.js +9 -3968
  15. package/dist/external/@sinclair/typebox/value.js +9007 -0
  16. package/dist/external/@sinclair/typebox.js +7891 -0
  17. package/dist/external/debug.js +162 -328
  18. package/dist/external/npm-pack.js +13935 -33342
  19. package/dist/fs.js +8 -2
  20. package/dist/globs.js +5 -1
  21. package/dist/http-request.d.ts +0 -25
  22. package/dist/http-request.js +6 -5
  23. package/dist/ipc.js +43 -10
  24. package/dist/json/edit.d.ts +1 -1
  25. package/dist/json/parse.d.ts +47 -2
  26. package/dist/json/parse.js +40 -2
  27. package/dist/json/types.d.ts +49 -0
  28. package/dist/memoization.d.ts +4 -23
  29. package/dist/memoization.js +15 -49
  30. package/dist/packages/specs.js +9 -2
  31. package/dist/paths/packages.js +6 -2
  32. package/dist/process-lock.js +1 -6
  33. package/dist/promise-queue.d.ts +9 -4
  34. package/dist/promise-queue.js +10 -8
  35. package/dist/promises.d.ts +41 -0
  36. package/dist/promises.js +19 -2
  37. package/dist/regexps.d.ts +4 -13
  38. package/dist/regexps.js +60 -3
  39. package/dist/schema/parse.d.ts +26 -0
  40. package/dist/{zod.js → schema/parse.js} +14 -6
  41. package/dist/schema/types.d.ts +121 -0
  42. package/dist/schema/validate.d.ts +35 -0
  43. package/dist/schema/validate.js +98 -0
  44. package/dist/stdio/progress.js +1 -1
  45. package/dist/suppress-warnings.js +0 -2
  46. package/dist/tables.js +2 -3
  47. package/dist/url.js +5 -1
  48. package/dist/versions.js +2 -2
  49. package/dist/words.js +4 -7
  50. package/package.json +15 -14
  51. package/dist/external/zod.js +0 -15223
  52. package/dist/validation/json-parser.d.ts +0 -58
  53. package/dist/validation/json-parser.js +0 -63
  54. package/dist/validation/types.d.ts +0 -118
  55. package/dist/zod.d.ts +0 -5
  56. /package/dist/{validation → schema}/types.js +0 -0
package/CHANGELOG.md CHANGED
@@ -5,62 +5,100 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [5.19.1](https://github.com/SocketDev/socket-lib/releases/tag/v5.19.1) - 2026-04-19
8
+ ## [5.21.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.21.0) - 2026-04-20
9
+
10
+ ### Added
9
11
 
10
- ### Fixedstdio (restore accidentally-dropped modules)
12
+ - `@socketsecurity/lib/schema/validate`non-throwing Zod/TypeBox validator returning `{ ok, value } | { ok, errors }` with normalized paths
13
+ - `@socketsecurity/lib/schema/parse` — throwing variant for fail-fast trust boundaries
14
+ - `@socketsecurity/lib/schema/types` — `Schema<T>`, `ValidateResult<T>`, `ValidationIssue`, `AnySchema`, `Infer<S>`
15
+ - `@socketsecurity/lib/promises` `withResolvers()` — spec-compliant [`Promise.withResolvers`](https://tc39.es/ecma262/#sec-promise.withResolvers) helper with `PromiseWithResolvers<T>` type. Uses the native implementation when available
11
16
 
12
- 5.19.0 shipped a breaking change that was not called out in its changelog or version bump: a refactor commit removed `stdio/prompts`, `stdio/progress`, `stdio/clear`, and the vendored `external/@inquirer/*` shims. socket-cli and other consumers import `stdio/prompts` directly and broke on upgrade.
17
+ ### Changed
13
18
 
14
- Restored:
19
+ - `@socketsecurity/lib/regexps` `escapeRegExp()` — now spec-compliant with TC39 [`RegExp.escape`](https://tc39.es/ecma262/#sec-regexp.escape); uses the native implementation when available. **Caller-visible shape change**: escaped output now uses `\xHH` for many characters that previously passed through literally (e.g. `escapeRegExp('a')` is now `'\x61'`). Functional equivalence (the compiled regex matches the original input) is preserved; only callers that string-match on escape output need updates
20
+ - `@socketsecurity/lib/memoization` `MemoizeOptions<Args>` — dropped the unused second type parameter. Consumers who wrote `MemoizeOptions<Args, Result>` must drop the second argument
21
+ - `@socketsecurity/lib/packages/specs` `getRepoUrlDetails()` — now accepts `git+https://` / `git+ssh://` GitHub URLs and rejects lookalike hosts (`githubXcom`, `fake-github.com.attacker.tld`). scp-style `git@github.com:…` URLs (no `://`) now return `{ user: '', project: '' }` — callers must normalize to https/ssh upstream
22
+ - `@socketsecurity/lib/url` `urlSearchParamAsBoolean()` — accepts the same truthy vocabulary as `envAsBoolean` (`1` / `true` / `yes` / `on`, case-insensitive). Empty-string input now falls through to `defaultValue` instead of returning `false`
15
23
 
16
- - `@socketsecurity/lib/stdio/prompts` — inquirer wrappers (`password`, `confirm`, `input`, `select`, `checkbox`, `search`)
17
- - `@socketsecurity/lib/stdio/progress` — progress-bar utility (`ProgressBar` class)
18
- - `@socketsecurity/lib/stdio/clear` — terminal line/screen/cursor helpers
19
- - `src/external/@inquirer/{checkbox,confirm,input,password,search,select}.js` vendor shims
20
- - Corresponding test suites
24
+ ### Removed
21
25
 
22
- ## [5.19.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.19.0) - 2026-04-19
26
+ - `@socketsecurity/lib/validation/*` subpath retired — exports re-homed:
27
+ - `validateSchema` / `parseSchema` → `@socketsecurity/lib/schema/validate` / `@socketsecurity/lib/schema/parse`
28
+ - `safeJsonParse` → `@socketsecurity/lib/json/parse`
29
+ - Types → `@socketsecurity/lib/schema/types` and `@socketsecurity/lib/json/types`
30
+ - `memoizeDebounced` from `@socketsecurity/lib/memoization` — was misnamed and had no consumers. Use `memoize` / `memoizeAsync` with a `ttl` instead
23
31
 
24
- ### Added — dlx/integrity (new module)
32
+ ### Fixed
33
+
34
+ - `@socketsecurity/lib/versions` `maxVersion()` / `minVersion()` — return the latest/earliest prerelease for all-prerelease inputs (previously returned `undefined`)
35
+ - `@socketsecurity/lib/fs` `findUp()` / `findUpSync()` — traverse up to and **including** the filesystem root (previously missed matches at `/.foo`)
36
+ - `@socketsecurity/lib/words` `capitalize()` — safe for non-BMP characters (emoji, astral-plane scripts); previously produced broken surrogate pairs
37
+ - `@socketsecurity/lib/words` `determineArticle()` — case-insensitive vowel match (`Apple` → `an Apple`)
38
+ - `@socketsecurity/lib/archives` `extractZip()` / `extractTar()` / `extractTarGz()` — missing-archive errors now uniformly surface as `ENOENT` with `code` / `path` / message (previously `extractZip` surfaced adm-zip's generic `"Invalid filename"`)
39
+ - `@socketsecurity/lib/promise-queue` — bounded queue now rejects the newest submission when full, preserving in-flight work
40
+ - `@socketsecurity/lib/cacache` / `@socketsecurity/lib/cache-with-ttl` — wildcard key deletion anchors both ends of the pattern (`deleteAll('foo*bar')` no longer sweeps `foo123bar-extra`)
41
+ - `@socketsecurity/lib/process-lock` — sub-second `staleMs` values now honored at full precision; TOCTOU window on lock acquisition closed
42
+ - `@socketsecurity/lib/suppress-warnings` `withSuppressedWarnings()` — no longer wipes concurrent suppressions on exit
43
+ - Unbounded LRU caches in `@socketsecurity/lib/dlx` capped (binary path, package.json path); negative package.json lookups now expire after 10s
44
+ - Glob cache keys for array-valued options (e.g. `ignore`) are order-insensitive
25
45
 
26
- - `HashSpec`, `NormalizedHash`, `ComputedHashes` types. `HashSpec` accepts a bare string (sha512 SRI or sha256 hex, sniffed) or an explicit `{ type, value }` object
27
- - `normalizeHash()`, `computeHashes()`, `verifyHash()` — `verifyHash` uses `crypto.timingSafeEqual` for constant-time comparison
28
- - `DlxHashMismatchError` — carries `expected` + `actual` for diagnostics
46
+ ### Performance
29
47
 
30
- ### Addeddlx/arborist (new module)
48
+ - `@socketsecurity/lib/memoization``memoize()` / `memoizeAsync()` cache-hit bookkeeping dropped from O(n) to O(1). Noticeable on caches with many entries
49
+ - `@socketsecurity/lib/cacache` — wildcard `clear()` no longer recompiles the match regex per streamed entry
31
50
 
32
- - `safeIdealTree()`, `safeReify()` — hardened `@npmcli/arborist` wrappers mirroring socket-cli v1.1.79 `SafeArborist` overrides (`audit: false`, `fund: false`, `ignoreScripts: true`, `progress: false`, `saveBundle: false`, `silent: true`)
33
- - `writeSafeNpmrc()` — defense-in-depth `.npmrc` writer matching the Arborist overrides
34
- - Optional `before?: Date` on `safeIdealTree` for release-age enforcement during resolution
51
+ ## [5.20.1](https://github.com/SocketDev/socket-lib/releases/tag/v5.20.1) - 2026-04-19
35
52
 
36
- ### Added — dlx/lockfile (new module)
53
+ ### Fixed
37
54
 
38
- - `generatePackagePin({ package, minReleaseDays?, minReleaseMins? })` returns `PinDetails { name, version, hash: ComputedHashes, packageJson, lockfile }`. Runs Arborist in `packageLockOnly: true` mode against a tmp directory and auto-cleans
39
- - **Default `minReleaseDays: 7`** resolution refuses to select versions published in the last week. Pass `0` to disable. `minReleaseMins` is a pnpm-style alias (mutually exclusive with `minReleaseDays`)
40
- - `LockfileSpec` type export for use as the new `lockfile` option on `downloadPackage`
55
+ - `@socketsecurity/lib/ipc` — harden stub-file writes against symlink/TOCTOU attacks on shared-tmp filesystems (POSIX ownership + mode validation, `O_EXCL | O_NOFOLLOW` open)
56
+ - `@socketsecurity/lib/cache-with-ttl` `getOrFetch()`close concurrent-caller race that let two cold-cache awaits both skip the inflight-dedupe check and fire the fetcher twice
57
+ - `@socketsecurity/lib/cache-with-ttl` — cap the in-memory memo layer with LRU eviction (`memoMaxSize`, default 1000); long-running processes no longer grow unbounded
58
+ - `@socketsecurity/lib/memoization` `memoizeAsync()` — refresh cache entry timestamp on resolve so slow fetches (longer than `ttl`) aren't classified as expired the moment they land
59
+ - `@socketsecurity/lib/tables` — `displayWidth` now measures rendered terminal cells (via `stringWidth`) instead of UTF-16 code units; CJK / emoji / combining marks align correctly
60
+ - `@socketsecurity/lib/paths/packages` — `resolvePackageJsonDirname` / `resolvePackageJsonPath` no longer mis-identify files like `/foo/my-package.json` as package manifests
61
+ - `@socketsecurity/lib/json/edit` — `@example` import path corrected
41
62
 
42
- ### Added dlx existing modules
63
+ ## [5.20.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.20.0) - 2026-04-19
43
64
 
44
- - `DlxPackageOptions.hash?: HashSpec` and `DlxPackageOptions.lockfile?: LockfileSpec` — passing a lockfile materializes it into the install dir (path → `fs.copyFileSync`, content → `fs.writeFileSync`) and drops a hardened `.npmrc` alongside before Arborist runs
45
- - `DlxBinaryOptions.hash?: HashSpec` — ergonomic alternative to the lower-level `integrity` and `sha256` fields (both still accepted)
65
+ ### Added
46
66
 
47
- ### Fixedexternal
67
+ - `@socketsecurity/lib/validation/validate-schema`universal Zod-style schema validator with `validateSchema` (tagged result) and `parseSchema` (throwing); `Infer<S>`, `ValidateResult<T>`, `ValidationIssue`, `AnySchema` types. No runtime `zod` dependency
48
68
 
49
- - `pacote` shim now exposes `tarball`, `manifest`, `packument` alongside `extract`. **Fixes a latent runtime crash** in `src/packages/manifest.ts` callers (`fetchPackageManifest` / `fetchPackagePackument` called `.manifest(...)` / `.packument(...)` on a shim that previously only had `extract`, raising `TypeError: not a function`)
69
+ > **Deprecated in 5.21.0**: moved to `@socketsecurity/lib/schema/*`.
50
70
 
51
- ### Changed — build (bundle size)
71
+ ### Fixed
52
72
 
53
- - `dist/external/npm-pack.js`: 2,526,598 1,755,460 bytes (−771 KB, −30.5%). New `STUB_MAP` entries for code paths our callers never reach:
54
- - `@sigstore/{bundle,core,protobuf-specs,sign,tuf,verify}`, `sigstore`, `tuf-js`, `@tufjs/{canonical-json,models}` Sigstore attestation, only reached via `arb.audit()`
55
- - `@npmcli/metavuln-calculator` — audit-only
56
- - `@npmcli/query`, `postcss-selector-parser` — `arb.query()` unused
57
- - `@npmcli/run-script`, `@npmcli/node-gyp` — guarded out by `ignoreScripts: true`
58
- - `@npmcli/git`, `pacote/lib/{git,file,dir,remote}.js` — registry specs only
59
- - arborist `audit-report.js`, `yarn-lock.js`, `isolated-reifier.js`, `query-selector-all.js`, `printable.js` — each gated or unused
60
- - `cacache/lib/verify.js` — `cacache.verify` (npm cache verify) unused
61
- - `proggy` — progress tracker, gated by `progress: false`
62
- - `debug/src/browser.js` — Node-only bundle
63
- - `dist/external/zod.js`: 597,238 → 291,430 bytes (−306 KB, −51.2%). Stubbed `zod/v4/{core,classic,mini}`'s eager `locales/index.cjs` barrel (40+ translation modules). Opt-in via `z.config(z.locales.xx())` is never called by us
73
+ - `@socketsecurity/lib/promise-queue` synchronous throws inside a queued task now convert to proper rejections instead of escaping as uncaught exceptions
74
+ - `@socketsecurity/lib/stdio/progress` `formatTime()` — clamp negative milliseconds so over-ticking / clock-skewed bars don't render negative ETAs
75
+ - `@socketsecurity/lib/dlx/lockfile` — scratch-directory cleanup can no longer clobber the real exception from the main block
76
+ - `@socketsecurity/lib/dlx/package` `parsePackageSpec` — normalize a bare trailing `@` (e.g. `"pkg@"`) to `version: undefined`
77
+ - `@socketsecurity/lib/stdio/prompts` — tighten an internal destructure type away from `as any`
78
+ - `@socketsecurity/lib/http-request` — hoist checksum regex literals out of a per-line loop
79
+
80
+ ## [5.19.1](https://github.com/SocketDev/socket-lib/releases/tag/v5.19.1) - 2026-04-19
81
+
82
+ ### Fixed
83
+
84
+ Restore `@socketsecurity/lib/stdio/prompts`, `@socketsecurity/lib/stdio/progress`, and `@socketsecurity/lib/stdio/clear` — accidentally removed in 5.19.0 without a major-bump callout. Downstream consumers that import `stdio/prompts` directly are unbroken.
85
+
86
+ ## [5.19.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.19.0) - 2026-04-19
87
+
88
+ ### Added
89
+
90
+ - `@socketsecurity/lib/dlx/integrity` — hash verification utilities: `HashSpec`, `NormalizedHash`, `ComputedHashes`, `normalizeHash()`, `computeHashes()`, `verifyHash()` (constant-time via `crypto.timingSafeEqual`), `DlxHashMismatchError`
91
+ - `@socketsecurity/lib/dlx/arborist` — hardened `@npmcli/arborist` wrappers: `safeIdealTree()`, `safeReify()`, `writeSafeNpmrc()`. Locks down `audit`, `fund`, `ignoreScripts`, `saveBundle`, etc. Supports `before?: Date` for release-age enforcement
92
+ - `@socketsecurity/lib/dlx/lockfile` — `generatePackagePin()` returns `{ name, version, hash, packageJson, lockfile }` for a resolved package. Default `minReleaseDays: 7` refuses versions published in the last week (`0` to disable); `minReleaseMins` accepted as pnpm-style alias
93
+ - `DlxPackageOptions.hash`, `DlxPackageOptions.lockfile`, `DlxBinaryOptions.hash` — first-class integrity + lockfile options on the dlx entry points
94
+
95
+ ### Fixed
96
+
97
+ - `pacote` shim — exposes `tarball`, `manifest`, `packument` alongside `extract`. Fixes a latent runtime crash in `fetchPackageManifest` / `fetchPackagePackument` callers
98
+
99
+ ### Changed
100
+
101
+ Reduced bundle size of `dist/external/npm-pack.js` (−771 KB, −30.5%) and `dist/external/zod.js` (−306 KB, −51.2%) by stubbing code paths our callers never reach (Sigstore attestation, arborist audit/query, zod locale translations, etc.)
64
102
 
65
103
  ## [5.18.2](https://github.com/SocketDev/socket-lib/releases/tag/v5.18.2) - 2026-04-14
66
104
 
@@ -70,70 +108,63 @@ Restored:
70
108
 
71
109
  ## [5.18.1](https://github.com/SocketDev/socket-lib/releases/tag/v5.18.1) - 2026-04-14
72
110
 
73
- ### Changed — build
111
+ ### Changed
74
112
 
75
- - Dedup npm-pack.js bundle via pnpm overrides: pacote 21.5.0, make-fetch-happen 15.0.5, and 7 transitive npm packages (npm-bundled, npm-normalize-package-bin, json-parse-even-better-errors, @npmcli/installed-package-contents, @npmcli/name-from-folder, @npmcli/promise-spawn, @npmcli/redact)
76
- - npm-pack.js: 69,738 → 66,443 lines (2.59MB → 2.46MB), 22 duplicate packages removed
113
+ - Deduplicated the `dist/external/npm-pack` bundle via `pnpm overrides` (pacote 21.5.0, make-fetch-happen 15.0.5, and 7 transitive `@npmcli/*` packages) 22 duplicate packages removed, ~130 KB smaller
77
114
 
78
115
  ## [5.18.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.18.0) - 2026-04-14
79
116
 
80
- ### Added — dlx
117
+ ### Added
81
118
 
82
- - Socket Firewall API check before package downloads resolves dependency tree via `buildIdealTree`, checks all packages against `firewall-api.socket.dev/purl` in parallel, blocks on critical/high severity alerts
119
+ - `@socketsecurity/lib/dlx` — Socket Firewall API check before package downloads. Resolves the dependency tree and blocks on critical/high severity alerts
83
120
 
84
- ### Changed — http-request
121
+ ### Changed
85
122
 
86
- - Default `User-Agent` header updated from `socket-registry/1.0` to `socketsecurity-lib/{version}`
123
+ - `@socketsecurity/lib/http-request` — default `User-Agent` updated from `socket-registry/1.0` to `socketsecurity-lib/{version}`
87
124
 
88
125
  ## [5.17.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.17.0) - 2026-04-14
89
126
 
90
- ### Added — paths
127
+ ### Added
91
128
 
92
- - `isUnixPath()` — detect MSYS/Git Bash drive letter notation (`/c/...`)
129
+ - `@socketsecurity/lib/paths` `isUnixPath()` — detect MSYS/Git Bash drive-letter notation (`/c/...`)
93
130
 
94
- ### Changed — paths
131
+ ### Changed
95
132
 
96
- - `normalizePath()` now converts MSYS drive letters on Windows (`/c/path` → `C:/path`)
97
- - `fromUnixPath()` now produces native Windows paths with backslashes (`/c/path` → `C:\path`), making it the true inverse of `toUnixPath()`
133
+ - `@socketsecurity/lib/paths` `normalizePath()` converts MSYS drive letters on Windows (`/c/path` → `C:/path`)
134
+ - `@socketsecurity/lib/paths` `fromUnixPath()` produces native Windows paths with backslashes (`/c/path` → `C:\path`), making it the true inverse of `toUnixPath()`
98
135
 
99
136
  ## [5.16.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.16.0) - 2026-04-14
100
137
 
101
- ### Added — paths
138
+ ### Added
102
139
 
103
- - `fromUnixPath()` — convert MSYS/Git Bash Unix-style paths (`/c/path`) back to native Windows format (`C:/path`), inverse of `toUnixPath` (#168)
140
+ - `@socketsecurity/lib/paths` `fromUnixPath()` — convert MSYS/Git Bash Unix-style paths (`/c/path`) back to native Windows format (`C:/path`), inverse of `toUnixPath` (#168)
104
141
 
105
- ### Fixed — dlx
142
+ ### Fixed
106
143
 
107
- - Normalize dlx directory path in `isInSocketDlx` for Windows compatibility
144
+ - `@socketsecurity/lib/dlx` `isInSocketDlx` — normalize the dlx directory path for Windows compatibility
108
145
 
109
146
  ## [5.15.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.15.0) - 2026-04-06
110
147
 
111
- ### Added — http-request
112
-
113
- - `stream` option on `HttpRequestOptions` — resolves with `HttpResponse` immediately after headers arrive, leaving `rawResponse` unconsumed for piping to files
114
- - `headers`, `ok`, `status`, `statusText` fields on `HttpDownloadResult`
115
-
116
- ### Changed — http-request
148
+ ### Added
117
149
 
118
- - `httpDownload` now uses `httpRequest` with `stream: true` internally, eliminating ~120 lines of duplicated HTTP plumbing
150
+ - `@socketsecurity/lib/http-request``stream` option on `HttpRequestOptions` resolves with `HttpResponse` immediately after headers arrive, leaving `rawResponse` unconsumed for piping to files
151
+ - `@socketsecurity/lib/http-request` — `headers`, `ok`, `status`, `statusText` fields on `HttpDownloadResult`
119
152
 
120
153
  ## [5.14.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.14.0) - 2026-04-06
121
154
 
122
- ### Added — http-request
155
+ ### Added
123
156
 
124
- - `HttpResponseError` class — thrown on non-2xx when `throwOnError` is enabled, carries the full `HttpResponse`
125
- - `throwOnError` option on `HttpRequestOptions` non-2xx responses throw instead of resolving with `ok: false`, enabling retry of HTTP errors
126
- - `onRetry` callback on `HttpRequestOptions` customize retry behavior per-attempt (return `false` to stop, a `number` to override delay, `undefined` for default backoff)
127
- - Streaming body support `body` accepts `Readable` streams (incl. `form-data` npm package), auto-merges `getHeaders()` when present
128
- - `parseRetryAfterHeader()` standalone RFC 7231 §7.1.3 `Retry-After` header parser (strict integer seconds + HTTP-date formats)
129
- - `sanitizeHeaders()` — redact sensitive headers (`authorization`, `cookie`, `set-cookie`, `proxy-authorization`, `proxy-authenticate`, `www-authenticate`) for safe logging
157
+ - `@socketsecurity/lib/http-request`:
158
+ - `HttpResponseError` class thrown on non-2xx when `throwOnError` is enabled; carries the full `HttpResponse`
159
+ - `throwOnError` optionnon-2xx responses throw instead of resolving with `ok: false`
160
+ - `onRetry` callbackcustomize retry behavior per-attempt (`false` to stop, a `number` to override delay, `undefined` for default backoff)
161
+ - Streaming body support — `body` accepts `Readable` streams (incl. `form-data`), auto-merges `getHeaders()` when present
162
+ - `parseRetryAfterHeader()` — standalone RFC 7231 §7.1.3 parser
163
+ - `sanitizeHeaders()` — redact sensitive headers for safe logging
130
164
 
131
- ### Changed — http-request
165
+ ### Changed
132
166
 
133
- - `HttpRequestOptions.body` type widened from `Buffer | string` to `Buffer | Readable | string`
134
- - Redirect responses now drained via `res.resume()` to free sockets
135
- - `maxResponseSize` exceeded now cleans up both response and request
136
- - `onResponse` hooks wrapped in try/catch — user hook errors can no longer leave promises pending
167
+ - `@socketsecurity/lib/http-request` — `HttpRequestOptions.body` widened to `Buffer | Readable | string`; `onResponse` hook errors no longer leave promises pending
137
168
 
138
169
  ## [5.13.0](https://github.com/SocketDev/socket-lib/releases/tag/v5.13.0) - 2026-04-05
139
170
 
package/dist/archives.js CHANGED
@@ -80,6 +80,16 @@ function validatePathWithinBase(targetPath, baseDir, entryName) {
80
80
  );
81
81
  }
82
82
  }
83
+ function assertArchiveExists(archivePath) {
84
+ if (!(0, import_node_fs.existsSync)(archivePath)) {
85
+ const err = new Error(
86
+ `ENOENT: no such file or directory, open '${archivePath}'`
87
+ );
88
+ err.code = "ENOENT";
89
+ err.path = archivePath;
90
+ throw err;
91
+ }
92
+ }
83
93
  function detectArchiveFormat(filePath) {
84
94
  const lower = filePath.toLowerCase();
85
95
  if (lower.endsWith(".tar.gz")) {
@@ -116,6 +126,7 @@ async function extractArchive(archivePath, outputDir, options = {}) {
116
126
  }
117
127
  }
118
128
  async function extractTar(archivePath, outputDir, options = {}) {
129
+ assertArchiveExists(archivePath);
119
130
  const {
120
131
  maxEntries = DEFAULT_MAX_ENTRIES,
121
132
  maxFileSize = DEFAULT_MAX_FILE_SIZE,
@@ -207,6 +218,7 @@ async function extractTar(archivePath, outputDir, options = {}) {
207
218
  }
208
219
  }
209
220
  async function extractTarGz(archivePath, outputDir, options = {}) {
221
+ assertArchiveExists(archivePath);
210
222
  const {
211
223
  maxEntries = DEFAULT_MAX_ENTRIES,
212
224
  maxFileSize = DEFAULT_MAX_FILE_SIZE,
@@ -298,6 +310,7 @@ async function extractTarGz(archivePath, outputDir, options = {}) {
298
310
  }
299
311
  }
300
312
  async function extractZip(archivePath, outputDir, options = {}) {
313
+ assertArchiveExists(archivePath);
301
314
  const {
302
315
  maxEntries = DEFAULT_MAX_ENTRIES,
303
316
  maxFileSize = DEFAULT_MAX_FILE_SIZE,
package/dist/cacache.js CHANGED
@@ -41,17 +41,14 @@ __export(cacache_exports, {
41
41
  module.exports = __toCommonJS(cacache_exports);
42
42
  var import_cacache = __toESM(require("./external/cacache"));
43
43
  var import_socket = require("./paths/socket");
44
- function matchesPattern(key, pattern) {
44
+ function createPatternMatcher(pattern) {
45
45
  if (!pattern.includes("*")) {
46
- return key.startsWith(pattern);
46
+ return (key) => key.startsWith(pattern);
47
47
  }
48
- const regex = patternToRegex(pattern);
49
- return regex.test(key);
50
- }
51
- function patternToRegex(pattern) {
52
48
  const escaped = pattern.replaceAll(/[.+?^${}()|[\]\\]/g, "\\$&");
53
49
  const regexPattern = escaped.replaceAll("*", ".*");
54
- return new RegExp(`^${regexPattern}`);
50
+ const regex = new RegExp(`^${regexPattern}$`);
51
+ return (key) => regex.test(key);
55
52
  }
56
53
  async function clear(options) {
57
54
  const opts = { __proto__: null, ...options };
@@ -84,9 +81,10 @@ async function clear(options) {
84
81
  return removed2;
85
82
  }
86
83
  let removed = 0;
84
+ const matches = createPatternMatcher(opts.prefix);
87
85
  const stream = cacache2.ls.stream(cacheDir);
88
86
  for await (const entry of stream) {
89
- if (matchesPattern(entry.key, opts.prefix)) {
87
+ if (matches(entry.key)) {
90
88
  try {
91
89
  await cacache2.rm.entry(cacheDir, entry.key);
92
90
  removed++;
@@ -126,6 +126,13 @@ export interface TtlCacheOptions {
126
126
  * @default true
127
127
  */
128
128
  memoize?: boolean | undefined;
129
+ /**
130
+ * Maximum number of entries to keep in the in-memory memo cache. When
131
+ * exceeded, the least-recently-used entry is evicted. The persistent
132
+ * (cacache) layer is unaffected.
133
+ * @default 1000
134
+ */
135
+ memoMaxSize?: number | undefined;
129
136
  /**
130
137
  * Custom cache key prefix.
131
138
  * Must not contain wildcards (*).
@@ -36,10 +36,12 @@ module.exports = __toCommonJS(cache_with_ttl_exports);
36
36
  var cacache = __toESM(require("./cacache"));
37
37
  const DEFAULT_TTL_MS = 5 * 60 * 1e3;
38
38
  const DEFAULT_PREFIX = "ttl-cache";
39
+ const DEFAULT_MEMO_MAX_SIZE = 1e3;
39
40
  function createTtlCache(options) {
40
41
  const opts = {
41
42
  __proto__: null,
42
43
  memoize: true,
44
+ memoMaxSize: DEFAULT_MEMO_MAX_SIZE,
43
45
  prefix: DEFAULT_PREFIX,
44
46
  ttl: DEFAULT_TTL_MS,
45
47
  ...options
@@ -50,6 +52,18 @@ function createTtlCache(options) {
50
52
  );
51
53
  }
52
54
  const memoCache = /* @__PURE__ */ new Map();
55
+ const memoMaxSize = Math.max(1, opts.memoMaxSize ?? DEFAULT_MEMO_MAX_SIZE);
56
+ function memoSet(fullKey, entry) {
57
+ if (memoCache.has(fullKey)) {
58
+ memoCache.delete(fullKey);
59
+ } else if (memoCache.size >= memoMaxSize) {
60
+ const oldest = memoCache.keys().next().value;
61
+ if (oldest !== void 0) {
62
+ memoCache.delete(oldest);
63
+ }
64
+ }
65
+ memoCache.set(fullKey, entry);
66
+ }
53
67
  const ttl = opts.ttl ?? DEFAULT_TTL_MS;
54
68
  function buildKey(key) {
55
69
  return `${opts.prefix}:${key}`;
@@ -70,7 +84,7 @@ function createTtlCache(options) {
70
84
  }
71
85
  const escaped = fullPattern.replaceAll(/[.+?^${}()|[\]\\]/g, "\\$&");
72
86
  const regexPattern = escaped.replaceAll("*", ".*");
73
- const regex = new RegExp(`^${regexPattern}`);
87
+ const regex = new RegExp(`^${regexPattern}$`);
74
88
  return (key) => regex.test(key);
75
89
  }
76
90
  async function get(key) {
@@ -83,6 +97,7 @@ function createTtlCache(options) {
83
97
  if (opts.memoize) {
84
98
  const memoEntry = memoCache.get(fullKey);
85
99
  if (memoEntry && !isExpired(memoEntry)) {
100
+ memoSet(fullKey, memoEntry);
86
101
  return memoEntry.data;
87
102
  }
88
103
  if (memoEntry) {
@@ -103,7 +118,7 @@ function createTtlCache(options) {
103
118
  }
104
119
  if (!isExpired(entry)) {
105
120
  if (opts.memoize) {
106
- memoCache.set(fullKey, entry);
121
+ memoSet(fullKey, entry);
107
122
  }
108
123
  return entry.data;
109
124
  }
@@ -158,7 +173,7 @@ function createTtlCache(options) {
158
173
  }
159
174
  results.set(originalKey, parsed.data);
160
175
  if (opts.memoize) {
161
- memoCache.set(cacheEntry.key, parsed);
176
+ memoSet(cacheEntry.key, parsed);
162
177
  }
163
178
  } catch {
164
179
  }
@@ -177,7 +192,7 @@ function createTtlCache(options) {
177
192
  expiresAt: Date.now() + ttl
178
193
  };
179
194
  if (opts.memoize) {
180
- memoCache.set(fullKey, entry);
195
+ memoSet(fullKey, entry);
181
196
  }
182
197
  try {
183
198
  await cacache.put(fullKey, JSON.stringify(entry), {
@@ -188,14 +203,18 @@ function createTtlCache(options) {
188
203
  }
189
204
  const inflightRequests = /* @__PURE__ */ new Map();
190
205
  async function getOrFetch(key, fetcher) {
206
+ const fullKey = buildKey(key);
207
+ const preexisting = inflightRequests.get(fullKey);
208
+ if (preexisting) {
209
+ return await preexisting;
210
+ }
191
211
  const cached = await get(key);
192
212
  if (cached !== void 0) {
193
213
  return cached;
194
214
  }
195
- const fullKey = buildKey(key);
196
- const existing = inflightRequests.get(fullKey);
197
- if (existing) {
198
- return await existing;
215
+ const rechecked = inflightRequests.get(fullKey);
216
+ if (rechecked) {
217
+ return await rechecked;
199
218
  }
200
219
  const promise = (async () => {
201
220
  try {
@@ -77,7 +77,7 @@ const SOCKET_FIREWALL_APP_NAME = "sfw";
77
77
  const SOCKET_REGISTRY_APP_NAME = "registry";
78
78
  const SOCKET_APP_PREFIX = "_";
79
79
  const SOCKET_LIB_NAME = "@socketsecurity/lib";
80
- const SOCKET_LIB_VERSION = "5.19.1";
80
+ const SOCKET_LIB_VERSION = "5.21.0";
81
81
  const SOCKET_LIB_URL = "https://github.com/SocketDev/socket-lib";
82
82
  const SOCKET_LIB_USER_AGENT = `socketsecurity-lib/${SOCKET_LIB_VERSION} (${SOCKET_LIB_URL})`;
83
83
  const SOCKET_IPC_HANDSHAKE = "SOCKET_IPC_HANDSHAKE";
@@ -33,7 +33,20 @@ var import_socket = require("../paths/socket");
33
33
  let _fs;
34
34
  let _path;
35
35
  const NODE_JS_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs"]);
36
+ const PACKAGE_JSON_PATH_CACHE_MAX_SIZE = 200;
37
+ const PACKAGE_JSON_NEGATIVE_TTL_MS = 1e4;
36
38
  const packageJsonPathCache = /* @__PURE__ */ new Map();
39
+ function packageJsonPathCacheSet(key, value) {
40
+ if (packageJsonPathCache.has(key)) {
41
+ packageJsonPathCache.delete(key);
42
+ } else if (packageJsonPathCache.size >= PACKAGE_JSON_PATH_CACHE_MAX_SIZE) {
43
+ const oldest = packageJsonPathCache.keys().next().value;
44
+ if (oldest !== void 0) {
45
+ packageJsonPathCache.delete(oldest);
46
+ }
47
+ }
48
+ packageJsonPathCache.set(key, { path: value, at: Date.now() });
49
+ }
37
50
  const packageJsonContentCache = /* @__PURE__ */ new Map();
38
51
  function findPackageJson(filePath) {
39
52
  const fs = /* @__PURE__ */ getFs();
@@ -41,25 +54,29 @@ function findPackageJson(filePath) {
41
54
  const startDir = path.dirname(path.resolve(filePath));
42
55
  const cached = packageJsonPathCache.get(startDir);
43
56
  if (cached !== void 0) {
44
- if (cached === null) {
45
- return void 0;
46
- }
47
- if (fs.existsSync(cached)) {
48
- return cached;
57
+ if (cached.path === null) {
58
+ if (Date.now() - cached.at < PACKAGE_JSON_NEGATIVE_TTL_MS) {
59
+ return void 0;
60
+ }
61
+ packageJsonPathCache.delete(startDir);
62
+ } else if (fs.existsSync(cached.path)) {
63
+ packageJsonPathCacheSet(startDir, cached.path);
64
+ return cached.path;
65
+ } else {
66
+ packageJsonPathCache.delete(startDir);
49
67
  }
50
- packageJsonPathCache.delete(startDir);
51
68
  }
52
69
  let currentDir = startDir;
53
70
  const root = path.parse(currentDir).root;
54
71
  while (currentDir !== root) {
55
72
  const packageJsonPath = path.join(currentDir, "package.json");
56
73
  if (fs.existsSync(packageJsonPath)) {
57
- packageJsonPathCache.set(startDir, packageJsonPath);
74
+ packageJsonPathCacheSet(startDir, packageJsonPath);
58
75
  return packageJsonPath;
59
76
  }
60
77
  currentDir = path.dirname(currentDir);
61
78
  }
62
- packageJsonPathCache.set(startDir, null);
79
+ packageJsonPathCacheSet(startDir, null);
63
80
  return void 0;
64
81
  }
65
82
  // @__NO_SIDE_EFFECTS__
@@ -128,7 +128,10 @@ async function generatePackagePin(options) {
128
128
  lockfile: ideal.lockfile
129
129
  };
130
130
  } finally {
131
- await (0, import_fs.safeDelete)(scratch, { force: true });
131
+ try {
132
+ await (0, import_fs.safeDelete)(scratch, { force: true });
133
+ } catch {
134
+ }
132
135
  }
133
136
  }
134
137
  // Annotate the CommonJS export names for ESM import in node:
@@ -3,10 +3,16 @@
3
3
  * Manages persistent caching of DLX package and binary metadata with TTL support
4
4
  * and atomic file operations.
5
5
  *
6
- * Key Functions:
7
- * - getManifestEntry: Retrieve manifest entry by spec
8
- * - setPackageEntry: Store npm package metadata
9
- * - setBinaryEntry: Store binary download metadata
6
+ * Primary API (on {@link DlxManifest}):
7
+ * - `getManifestEntry(spec)` retrieve a manifest entry by spec
8
+ * - `setPackageEntry(spec, key, details)` — store npm package metadata
9
+ * - `setBinaryEntry(spec, key, details)` — store binary download metadata
10
+ * - `getAllPackages()` — enumerate cached package names
11
+ * - `clear(name)` / `clearAll()` — eviction
12
+ * - `isFresh(record, ttlMs)` — TTL check
13
+ *
14
+ * The bare `get(name)` / `set(name, record)` methods are deprecated
15
+ * legacy shims kept for backward compatibility with pre-5.x callers.
10
16
  *
11
17
  * Features:
12
18
  * - TTL-based cache expiration
@@ -136,7 +136,7 @@ export interface DlxPackageResult {
136
136
  * await result.spawnPromise
137
137
  * ```
138
138
  */
139
- export declare function dlxPackage(args: readonly string[] | string[], options?: DlxPackageOptions | undefined, spawnExtra?: SpawnExtra | undefined): Promise<DlxPackageResult>;
139
+ export declare function dlxPackage(args: readonly string[] | string[], options: DlxPackageOptions, spawnExtra?: SpawnExtra | undefined): Promise<DlxPackageResult>;
140
140
  /**
141
141
  * Download and install a package without executing it.
142
142
  * This is useful for self-update or when you need the package files
@@ -62,7 +62,19 @@ const FIREWALL_BLOCK_SEVERITIES = /* @__PURE__ */ new Set([
62
62
  "critical",
63
63
  "high"
64
64
  ]);
65
+ const BINARY_PATH_CACHE_MAX_SIZE = 200;
65
66
  const binaryPathCache = /* @__PURE__ */ new Map();
67
+ function binaryPathCacheSet(key, value) {
68
+ if (binaryPathCache.has(key)) {
69
+ binaryPathCache.delete(key);
70
+ } else if (binaryPathCache.size >= BINARY_PATH_CACHE_MAX_SIZE) {
71
+ const oldest = binaryPathCache.keys().next().value;
72
+ if (oldest !== void 0) {
73
+ binaryPathCache.delete(oldest);
74
+ }
75
+ }
76
+ binaryPathCache.set(key, value);
77
+ }
66
78
  async function checkFirewallPurls(arb, requestedPackage) {
67
79
  const idealTree = arb.idealTree;
68
80
  if (!idealTree) {
@@ -141,7 +153,7 @@ async function dlxPackage(args, options, spawnExtra) {
141
153
  const spawnPromise = executePackage(
142
154
  downloadResult.binaryPath,
143
155
  args,
144
- options?.spawnOptions,
156
+ options.spawnOptions,
145
157
  spawnExtra
146
158
  );
147
159
  return {
@@ -414,9 +426,12 @@ function parsePackageSpec(spec) {
414
426
  if (atIndex === -1 || atIndex === 0) {
415
427
  return { name: spec, version: void 0 };
416
428
  }
429
+ const sliced = spec.slice(atIndex + 1);
417
430
  return {
418
431
  name: spec.slice(0, atIndex),
419
- version: spec.slice(atIndex + 1)
432
+ // A trailing `@` (e.g. `'pkg@'`) yields an empty slice normalize
433
+ // to undefined so downstream "no version" checks behave.
434
+ version: sliced || void 0
420
435
  };
421
436
  }
422
437
  }
@@ -428,6 +443,7 @@ function resolveBinaryPath(basePath) {
428
443
  const cached = binaryPathCache.get(basePath);
429
444
  if (cached) {
430
445
  if (fs.existsSync(cached)) {
446
+ binaryPathCacheSet(basePath, cached);
431
447
  return cached;
432
448
  }
433
449
  binaryPathCache.delete(basePath);
@@ -436,7 +452,7 @@ function resolveBinaryPath(basePath) {
436
452
  for (const ext of extensions) {
437
453
  const testPath = basePath + ext;
438
454
  if (fs.existsSync(testPath)) {
439
- binaryPathCache.set(basePath, testPath);
455
+ binaryPathCacheSet(basePath, testPath);
440
456
  return testPath;
441
457
  }
442
458
  }