@socketsecurity/lib 5.19.1 → 5.20.1

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.
@@ -890,31 +890,6 @@ export declare function httpRequest(url: string, options?: HttpRequestOptions |
890
890
  * ```
891
891
  */
892
892
  export declare function httpText(url: string, options?: HttpRequestOptions | undefined): Promise<string>;
893
- /**
894
- * Parse a checksums file text into a filename-to-hash map.
895
- *
896
- * Supports standard checksums file formats:
897
- * - BSD style: "SHA256 (filename) = hash"
898
- * - GNU style: "hash filename" (two spaces)
899
- * - Simple style: "hash filename" (single space)
900
- *
901
- * Lines starting with '#' are treated as comments and ignored.
902
- * Empty lines are ignored.
903
- *
904
- * @param text - Raw text content of a checksums file
905
- * @returns Map of filenames to lowercase SHA256 hashes
906
- *
907
- * @example
908
- * ```ts
909
- * const text = `
910
- * # SHA256 checksums
911
- * e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 file.zip
912
- * abc123def456... other.tar.gz
913
- * `
914
- * const checksums = parseChecksums(text)
915
- * console.log(checksums['file.zip']) // 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
916
- * ```
917
- */
918
893
  export declare function parseChecksums(text: string): Checksums;
919
894
  /**
920
895
  * Parse a `Retry-After` HTTP header value into milliseconds.
@@ -644,6 +644,9 @@ async function httpText(url, options) {
644
644
  }
645
645
  return response.text();
646
646
  }
647
+ const CHECKSUM_BSD_RE = /^SHA256\s+\((.+)\)\s+=\s+([a-fA-F0-9]{64})$/;
648
+ const CHECKSUM_GNU_RE = /^([a-fA-F0-9]{64})\s+(.+)$/;
649
+ const RETRY_AFTER_INT_RE = /^\d+$/;
647
650
  function parseChecksums(text) {
648
651
  const checksums = { __proto__: null };
649
652
  for (const line of text.split("\n")) {
@@ -651,14 +654,12 @@ function parseChecksums(text) {
651
654
  if (!trimmed || trimmed.startsWith("#")) {
652
655
  continue;
653
656
  }
654
- const bsdMatch = trimmed.match(
655
- /^SHA256\s+\((.+)\)\s+=\s+([a-fA-F0-9]{64})$/
656
- );
657
+ const bsdMatch = CHECKSUM_BSD_RE.exec(trimmed);
657
658
  if (bsdMatch) {
658
659
  checksums[bsdMatch[1]] = bsdMatch[2].toLowerCase();
659
660
  continue;
660
661
  }
661
- const gnuMatch = trimmed.match(/^([a-fA-F0-9]{64})\s+(.+)$/);
662
+ const gnuMatch = CHECKSUM_GNU_RE.exec(trimmed);
662
663
  if (gnuMatch) {
663
664
  checksums[gnuMatch[2]] = gnuMatch[1].toLowerCase();
664
665
  }
@@ -674,7 +675,7 @@ function parseRetryAfterHeader(value) {
674
675
  return void 0;
675
676
  }
676
677
  const trimmed = raw.trim();
677
- if (/^\d+$/.test(trimmed)) {
678
+ if (RETRY_AFTER_INT_RE.test(trimmed)) {
678
679
  const seconds = Number(trimmed);
679
680
  return seconds * 1e3;
680
681
  }
package/dist/ipc.js CHANGED
@@ -35,15 +35,16 @@ __export(ipc_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(ipc_exports);
37
37
  var import_node_process = __toESM(require("node:process"));
38
+ var import_typebox = require("./external/@sinclair/typebox");
38
39
  var import_socket = require("./paths/socket");
39
- var import_zod = require("./zod");
40
- const IpcStubSchema = import_zod.z.object({
40
+ var import_validate_schema = require("./validation/validate-schema");
41
+ const IpcStubSchema = import_typebox.Type.Object({
41
42
  /** Process ID that created the stub. */
42
- pid: import_zod.z.number().int().positive(),
43
+ pid: import_typebox.Type.Integer({ minimum: 1 }),
43
44
  /** Creation timestamp for age validation. */
44
- timestamp: import_zod.z.number().positive(),
45
+ timestamp: import_typebox.Type.Number({ exclusiveMinimum: 0 }),
45
46
  /** The actual data payload. */
46
- data: import_zod.z.unknown()
47
+ data: import_typebox.Type.Unknown()
47
48
  });
48
49
  let _fs;
49
50
  let _path;
@@ -52,6 +53,24 @@ async function ensureIpcDirectory(filePath) {
52
53
  const path = /* @__PURE__ */ getPath();
53
54
  const dir = path.dirname(filePath);
54
55
  await fs.promises.mkdir(dir, { recursive: true, mode: 448 });
56
+ if (import_node_process.default.platform === "win32") {
57
+ return;
58
+ }
59
+ const stats = await fs.promises.lstat(dir);
60
+ if (!stats.isDirectory()) {
61
+ throw new Error(`IPC path is not a directory: ${dir}`);
62
+ }
63
+ const getuid = import_node_process.default.getuid;
64
+ const ownUid = typeof getuid === "function" ? getuid.call(import_node_process.default) : -1;
65
+ if (ownUid !== -1 && stats.uid !== ownUid) {
66
+ throw new Error(
67
+ `IPC directory ${dir} is owned by another user (uid ${stats.uid}); refusing to use it.`
68
+ );
69
+ }
70
+ const mode = stats.mode & 511;
71
+ if ((mode & 63) !== 0) {
72
+ await fs.promises.chmod(dir, 448);
73
+ }
55
74
  }
56
75
  // @__NO_SIDE_EFFECTS__
57
76
  function getFs() {
@@ -81,12 +100,26 @@ async function writeIpcStub(appName, data) {
81
100
  pid: import_node_process.default.pid,
82
101
  timestamp: Date.now()
83
102
  };
84
- const validated = IpcStubSchema.parse(ipcData);
103
+ const validated = (0, import_validate_schema.parseSchema)(IpcStubSchema, ipcData);
85
104
  const fs = /* @__PURE__ */ getFs();
86
- await fs.promises.writeFile(stubPath, JSON.stringify(validated, null, 2), {
87
- encoding: "utf8",
88
- mode: 384
89
- });
105
+ const flags = fs.constants.O_CREAT | fs.constants.O_WRONLY | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW;
106
+ let handle;
107
+ try {
108
+ handle = await fs.promises.open(stubPath, flags, 384);
109
+ } catch (e) {
110
+ const err = e;
111
+ if (err.code === "EEXIST") {
112
+ await fs.promises.unlink(stubPath);
113
+ handle = await fs.promises.open(stubPath, flags, 384);
114
+ } else {
115
+ throw err;
116
+ }
117
+ }
118
+ try {
119
+ await handle.writeFile(JSON.stringify(validated, null, 2), "utf8");
120
+ } finally {
121
+ await handle.close();
122
+ }
90
123
  return stubPath;
91
124
  }
92
125
  // Annotate the CommonJS export names for ESM import in node:
@@ -7,7 +7,7 @@ import type { EditableJsonConstructor } from './types';
7
7
  *
8
8
  * @example
9
9
  * ```ts
10
- * import { getEditableJsonClass } from '@socketsecurity/lib/json'
10
+ * import { getEditableJsonClass } from '@socketsecurity/lib/json/edit'
11
11
  *
12
12
  * const EditableJson = getEditableJsonClass<MyConfigType>()
13
13
  * const config = await EditableJson.load('./config.json')
@@ -183,6 +183,11 @@ function memoizeAsync(fn, options = {}) {
183
183
  const inflight = refreshing.get(key);
184
184
  if (inflight) {
185
185
  (0, import_debug.debugLog)(`[memoizeAsync:${name}] stale-dedup`, { key });
186
+ const inflightIndex = accessOrder.indexOf(key);
187
+ if (inflightIndex !== -1) {
188
+ accessOrder.splice(inflightIndex, 1);
189
+ }
190
+ accessOrder.push(key);
186
191
  return await inflight;
187
192
  }
188
193
  cache.delete(key);
@@ -198,6 +203,7 @@ function memoizeAsync(fn, options = {}) {
198
203
  const entry = cache.get(key);
199
204
  if (entry) {
200
205
  entry.value = Promise.resolve(result);
206
+ entry.timestamp = Date.now();
201
207
  }
202
208
  return result;
203
209
  },
@@ -34,8 +34,12 @@ function getPath() {
34
34
  return _path;
35
35
  }
36
36
  // @__NO_SIDE_EFFECTS__
37
+ function isPackageJsonFile(filepath) {
38
+ return filepath === "package.json" || filepath.endsWith("/package.json") || filepath.endsWith("\\package.json");
39
+ }
40
+ // @__NO_SIDE_EFFECTS__
37
41
  function resolvePackageJsonDirname(filepath) {
38
- if (filepath.endsWith("package.json")) {
42
+ if (/* @__PURE__ */ isPackageJsonFile(filepath)) {
39
43
  const path = /* @__PURE__ */ getPath();
40
44
  return (0, import_normalize.normalizePath)(path.dirname(filepath));
41
45
  }
@@ -43,7 +47,7 @@ function resolvePackageJsonDirname(filepath) {
43
47
  }
44
48
  // @__NO_SIDE_EFFECTS__
45
49
  function resolvePackageJsonPath(filepath) {
46
- if (filepath.endsWith("package.json")) {
50
+ if (/* @__PURE__ */ isPackageJsonFile(filepath)) {
47
51
  return (0, import_normalize.normalizePath)(filepath);
48
52
  }
49
53
  const path = /* @__PURE__ */ getPath();
@@ -69,7 +69,7 @@ class PromiseQueue {
69
69
  return;
70
70
  }
71
71
  this.running++;
72
- task.fn().then(task.resolve).catch(task.reject).finally(() => {
72
+ Promise.resolve().then(() => task.fn()).then(task.resolve).catch(task.reject).finally(() => {
73
73
  this.running--;
74
74
  this.runNext();
75
75
  });
@@ -174,7 +174,7 @@ class ProgressBar {
174
174
  * Format time in seconds to human readable.
175
175
  */
176
176
  formatTime(ms) {
177
- const seconds = Math.round(ms / 1e3);
177
+ const seconds = Math.max(0, Math.round(ms / 1e3));
178
178
  if (seconds < 60) {
179
179
  return `${seconds}s`;
180
180
  }
package/dist/tables.js CHANGED
@@ -37,11 +37,10 @@ module.exports = __toCommonJS(tables_exports);
37
37
  var import_yoctocolors_cjs = __toESM(require("./external/yoctocolors-cjs"));
38
38
  var import_strings = require("./strings");
39
39
  function displayWidth(text) {
40
- return (0, import_strings.stripAnsi)(text).length;
40
+ return (0, import_strings.stringWidth)(text);
41
41
  }
42
42
  function padText(text, width, align = "left") {
43
- const stripped = (0, import_strings.stripAnsi)(text);
44
- const textWidth = stripped.length;
43
+ const textWidth = displayWidth(text);
45
44
  const padding = Math.max(0, width - textWidth);
46
45
  switch (align) {
47
46
  case "right":
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @fileoverview Universal schema validation for Zod-style schemas (Zod v3,
3
+ * v4, and any `safeParse`-shaped duck type).
4
+ *
5
+ * Accepts a schema and returns a tagged result.
6
+ * - `{ ok: true, value }` — validation passed, `value` is typed as the
7
+ * schema's inferred output (`z.infer<typeof S>`).
8
+ * - `{ ok: false, errors }` — validation failed, `errors` is a normalized
9
+ * list of `{ path, message }`.
10
+ *
11
+ * Zod is detected purely structurally via `.safeParse` — no runtime import of
12
+ * the `zod` package is required by socket-lib.
13
+ *
14
+ * @internal
15
+ * Socket-lib additionally recognizes TypeBox schemas for its own internal
16
+ * use (e.g. `src/ipc.ts`'s stub-file validation). That path is not a
17
+ * supported consumer API — callers should use Zod.
18
+ */
19
+ import type { Schema } from './types';
20
+ /**
21
+ * TypeBox's `Kind` symbol. We reference it structurally for schema detection
22
+ * rather than importing it from `@sinclair/typebox` — detection scans the
23
+ * schema's own-symbol keys for one whose description is `'TypeBox.Kind'`.
24
+ * The `Value` runtime is only loaded lazily when a TypeBox schema is seen.
25
+ */
26
+ type TypeBoxKindSymbol = symbol & {
27
+ __typeBoxKindBrand?: never;
28
+ };
29
+ /**
30
+ * Structural minimum of a TypeBox `TSchema`. The phantom `static` field is
31
+ * the type TypeBox uses for inference (`Static<T> = T['static']`).
32
+ */
33
+ interface TypeBoxLikeSchema {
34
+ [k: TypeBoxKindSymbol]: string;
35
+ static: unknown;
36
+ }
37
+ /**
38
+ * Structural shape of a Zod v4 schema — carries output type on `_zod.output`.
39
+ */
40
+ interface ZodV4LikeSchema<O = unknown> {
41
+ _zod: {
42
+ output: O;
43
+ };
44
+ safeParse(data: unknown): unknown;
45
+ }
46
+ /**
47
+ * Structural shape of a Zod v3 schema — carries output type on `_output`.
48
+ */
49
+ interface ZodV3LikeSchema<O = unknown> {
50
+ _output: O;
51
+ safeParse(data: unknown): unknown;
52
+ }
53
+ /**
54
+ * Any schema kind this helper accepts.
55
+ */
56
+ export type AnySchema = TypeBoxLikeSchema | ZodV4LikeSchema<unknown> | ZodV3LikeSchema<unknown> | Schema<unknown>;
57
+ /**
58
+ * Infer the validated output type from any supported schema kind.
59
+ *
60
+ * Order matters: TypeBox schemas also carry a phantom `static` field, so we
61
+ * check for TypeBox before falling through to Zod and the duck-type.
62
+ */
63
+ export type Infer<S> = S extends {
64
+ static: infer Static;
65
+ } ? Static : S extends {
66
+ _zod: {
67
+ output: infer O;
68
+ };
69
+ } ? O : S extends {
70
+ _output: infer O;
71
+ } ? O : S extends Schema<infer T> ? T : unknown;
72
+ /**
73
+ * A single normalized validation error.
74
+ * - `path` is a dotted or slash-separated identifier locating the bad value.
75
+ * - `message` is human-readable.
76
+ */
77
+ export interface ValidationIssue {
78
+ /** Array path into the value (e.g. `['user', 'age']`). */
79
+ path: Array<string | number>;
80
+ /** Human-readable description of the failure. */
81
+ message: string;
82
+ }
83
+ /**
84
+ * Tagged-union result of {@link validateSchema}. Callers narrow on `ok`.
85
+ */
86
+ export type ValidateResult<T> = {
87
+ ok: true;
88
+ value: T;
89
+ } | {
90
+ ok: false;
91
+ errors: ValidationIssue[];
92
+ };
93
+ /**
94
+ * Validate `data` against a Zod-style `schema`. Non-throwing.
95
+ *
96
+ * Accepted schemas:
97
+ * - `zod` schemas, v3 and v4 (detected via `.safeParse` on the schema)
98
+ * - Any object conforming to {@link Schema} (the socket-lib duck type)
99
+ *
100
+ * The return type narrows `value` to {@link Infer | `Infer<S>`}, so callers
101
+ * get `z.infer<typeof S>` with no casts.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * import { z } from 'zod'
106
+ * const U = z.object({ name: z.string() })
107
+ * const r = validateSchema(U, data)
108
+ * if (r.ok) r.value.name // string
109
+ * ```
110
+ *
111
+ * Errors are normalized to {@link ValidationIssue}: `{ path, message }`.
112
+ */
113
+ export declare function validateSchema<S>(schema: S, data: unknown): ValidateResult<Infer<S>>;
114
+ /**
115
+ * Parse `data` against `schema` and return the validated value. Throws if
116
+ * validation fails. This is the throwing twin of {@link validateSchema}.
117
+ *
118
+ * Use when you want fail-fast semantics at a trust boundary. For recoverable
119
+ * validation (form input, external configs), prefer {@link validateSchema}.
120
+ *
121
+ * @throws {Error} When validation fails. The message lists all issues.
122
+ */
123
+ export declare function parseSchema<S>(schema: S, data: unknown): Infer<S>;
124
+ export {};
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ /* Socket Lib - Built with esbuild */
3
+ "use strict";
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+ var validate_schema_exports = {};
22
+ __export(validate_schema_exports, {
23
+ parseSchema: () => parseSchema,
24
+ validateSchema: () => validateSchema
25
+ });
26
+ module.exports = __toCommonJS(validate_schema_exports);
27
+ function isTypeBoxSchema(schema) {
28
+ if (schema === null || typeof schema !== "object") {
29
+ return false;
30
+ }
31
+ for (const sym of Object.getOwnPropertySymbols(schema)) {
32
+ if (sym.description === "TypeBox.Kind") {
33
+ return typeof schema[sym] === "string";
34
+ }
35
+ }
36
+ return false;
37
+ }
38
+ function normalizeTypeBoxErrors(errors) {
39
+ const out = [];
40
+ for (const err of errors) {
41
+ const segs = err.path.split("/").filter(Boolean);
42
+ out.push({
43
+ path: segs.map((s) => {
44
+ const n = Number(s);
45
+ return Number.isInteger(n) && String(n) === s ? n : s;
46
+ }),
47
+ message: err.message
48
+ });
49
+ }
50
+ return out;
51
+ }
52
+ function normalizeZodError(err) {
53
+ if (err === null || typeof err !== "object") {
54
+ return [{ path: [], message: String(err) }];
55
+ }
56
+ const issues = err.issues;
57
+ if (!Array.isArray(issues)) {
58
+ return [{ path: [], message: "Unknown validation error" }];
59
+ }
60
+ return issues.map((issue) => {
61
+ const i = issue;
62
+ return {
63
+ path: Array.isArray(i.path) ? i.path : [],
64
+ message: typeof i.message === "string" ? i.message : "Invalid value"
65
+ };
66
+ });
67
+ }
68
+ function validateSchema(schema, data) {
69
+ if (isTypeBoxSchema(schema)) {
70
+ const { Value } = require("../external/@sinclair/typebox/value");
71
+ if (Value.Check(schema, data)) {
72
+ return { ok: true, value: data };
73
+ }
74
+ return {
75
+ ok: false,
76
+ errors: normalizeTypeBoxErrors(Value.Errors(schema, data))
77
+ };
78
+ }
79
+ if (schema !== null && typeof schema === "object" && typeof schema.safeParse === "function") {
80
+ const result = schema.safeParse(data);
81
+ if (result.success === true) {
82
+ return {
83
+ ok: true,
84
+ value: result.data
85
+ };
86
+ }
87
+ return {
88
+ ok: false,
89
+ errors: normalizeZodError(result.error)
90
+ };
91
+ }
92
+ throw new TypeError(
93
+ "validateSchema: unsupported schema kind. Expected a TypeBox schema, a Zod schema, or an object with a safeParse method."
94
+ );
95
+ }
96
+ function parseSchema(schema, data) {
97
+ const result = validateSchema(schema, data);
98
+ if (result.ok) {
99
+ return result.value;
100
+ }
101
+ const summary = result.errors.map((e) => `${e.path.join(".") || "(root)"}: ${e.message}`).join(", ");
102
+ throw new Error(`Validation failed: ${summary}`);
103
+ }
104
+ // Annotate the CommonJS export names for ESM import in node:
105
+ 0 && (module.exports = {
106
+ parseSchema,
107
+ validateSchema
108
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@socketsecurity/lib",
3
- "version": "5.19.1",
3
+ "version": "5.20.1",
4
4
  "packageManager": "pnpm@11.0.0-rc.2",
5
5
  "license": "MIT",
6
6
  "description": "Core utilities and infrastructure for Socket.dev security tools",
@@ -663,6 +663,10 @@
663
663
  "types": "./dist/validation/types.d.ts",
664
664
  "default": "./dist/validation/types.js"
665
665
  },
666
+ "./validation/validate-schema": {
667
+ "types": "./dist/validation/validate-schema.d.ts",
668
+ "default": "./dist/validation/validate-schema.js"
669
+ },
666
670
  "./versions": {
667
671
  "types": "./dist/versions.d.ts",
668
672
  "default": "./dist/versions.js"
@@ -671,10 +675,6 @@
671
675
  "types": "./dist/words.d.ts",
672
676
  "default": "./dist/words.js"
673
677
  },
674
- "./zod": {
675
- "types": "./dist/zod.d.ts",
676
- "default": "./dist/zod.js"
677
- },
678
678
  "./data/extensions.json": "./data/extensions.json",
679
679
  "./package.json": "./package.json",
680
680
  "./tsconfig.dts.json": "./tsconfig.dts.json",
@@ -720,10 +720,11 @@
720
720
  "@npmcli/arborist": "9.1.4",
721
721
  "@npmcli/package-json": "7.0.0",
722
722
  "@npmcli/promise-spawn": "8.0.3",
723
+ "@sinclair/typebox": "0.34.49",
723
724
  "@socketregistry/is-unicode-supported": "1.0.5",
724
725
  "@socketregistry/packageurl-js": "1.4.2",
725
726
  "@socketregistry/yocto-spinner": "1.0.25",
726
- "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.19.0",
727
+ "@socketsecurity/lib-stable": "npm:@socketsecurity/lib@5.19.1",
727
728
  "@types/node": "24.9.2",
728
729
  "@typescript/native-preview": "7.0.0-dev.20260415.1",
729
730
  "@vitest/coverage-v8": "4.0.3",