@wrongstack/core 0.257.0 → 0.257.2

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.
@@ -3,6 +3,8 @@ export { T as TaskItem, c as computeTaskItemProgress, f as formatTaskList, a as
3
3
  export { a as WstackPathOptions, W as WstackPaths, p as projectHash, b as projectSlug, r as resolveWstackPaths, w as wstackGlobalRoot } from '../wstack-paths-CJjEwPXn.js';
4
4
  export { expectDefined } from './expect-defined.js';
5
5
  import { a as ModelsDevPayload, t as CustomModelDefinition } from '../config-DuAu23zm.js';
6
+ import * as https from 'node:https';
7
+ import { Dispatcher } from 'undici';
6
8
  export { a as TaskPriority, b as TaskStatus, T as TaskType } from '../task-graph-u1q9Jkyk.js';
7
9
 
8
10
  interface AtomicWriteOptions {
@@ -602,4 +604,100 @@ declare function mergeModelsPayload(base: ModelsDevPayload, overlay: ModelsDevPa
602
604
  */
603
605
  declare function mergeCustomModelDefs(providerCustomModels: Record<string, CustomModelDefinition> | undefined, configModels: Record<string, CustomModelDefinition> | undefined): Record<string, CustomModelDefinition> | undefined;
604
606
 
605
- export { type AtomicWriteOptions, type BuildChildEnvOptions, type CompileFail, type CompileResult, type DeepMergeOptions, FORBIDDEN_PROTO_KEYS, type FileLockOptions, type MessageRepairReport, type MessageRepairResult, type NewlineStyle, type OutputLineGuard, type RequestTokenBreakdown, type SafeParseResult, type ToolOutputSerializerOptions, type UnifiedDiffOptions, type ValidationError, type ValidationResult, assertNever, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, computeMessageTokens, createToolOutputSerializer, deepMerge, detectNewlineStyle, ensureDir, estimateMessageTokens, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, formatTodosList, getCalibrationState, getTermSize, isInteractive, isPrimitiveArray, isStdinTTY, isStdoutTTY, matchAny, matchGlob, mergeCustomModelDefs, mergeModelsPayload, normalizeToLf, onResize, recordActualUsage, repairToolUseAdjacency, resetCalibration, safeParse, safeStringify, sanitizeJsonString, setOutputLineGuard, setRawMode, sleep, stripAnsi, toStyle, truncate, unifiedDiff, validateAgainstSchema, withFileLock, writeErr, writeOut };
607
+ /**
608
+ * Shared IP-address guards for SSRF protection.
609
+ *
610
+ * Exported so `fetch.ts` (tools), `web-search/index.ts` (plugins), and any
611
+ * other package that needs to validate IPs can all consume the same logic.
612
+ * Any future additions (e.g. extra CIDR blocks) need only be made here.
613
+ */
614
+ /**
615
+ * True if `addr` is in a private / loopback / link-local / reserved / CGNAT /
616
+ * multicast range. `net.isIP` is called by the caller first so `addr` is
617
+ * guaranteed to be a canonical dotted-quad at this point.
618
+ */
619
+ declare function isPrivateIPv4(addr: string): boolean;
620
+ /**
621
+ * True if `raw` (an IPv6 literal, already lowercased) is loopback / unique-local /
622
+ * link-local / unspecified / IPv4-mapped-private.
623
+ */
624
+ declare function isPrivateIPv6(raw: string): boolean;
625
+ /**
626
+ * Expand an IPv6 string into exactly 8 16-bit numbers. Handles `::` compression.
627
+ * Returns null on malformed input — caller should treat that as "block".
628
+ */
629
+ declare function expandIPv6(addr: string): number[] | null;
630
+ /**
631
+ * Convenience: throw if `hostname` resolves to a private / loopback IP.
632
+ * Use as a pre-flight check before opening a socket.
633
+ *
634
+ * ⚠️ This is not sufficient alone — connections must also use a pinned
635
+ * dispatcher (so the OS re-uses the already-resolved address) or the same
636
+ * check must be applied after every redirect hop. See `guardedLookup` in
637
+ * `fetch.ts` for the connection-level enforcement.
638
+ */
639
+ declare function assertNotPrivateHost(hostname: string): Promise<void>;
640
+
641
+ /**
642
+ * `dispatcher-types.d.ts` — Unify `https.Agent` and `undici.Dispatcher` types.
643
+ *
644
+ * Problem: `https.Agent` (Node.js built-in) and `undici.Dispatcher` implement the
645
+ * same interface at runtime — `fetch`'s `RequestInit.dispatcher` accepts anything
646
+ * with a `dispatch(req, opts)` method. However, TypeScript's type definitions
647
+ * treat them as unrelated types because they come from different `@types/*` packages
648
+ * (or undici@7 bundles its own Dispatcher type that conflicts with @types/node's
649
+ * copy of the same concept via undici-types).
650
+ *
651
+ * Solution: This module augments the global `RequestInit` type so that
652
+ * `https.Agent` is accepted as a valid `dispatcher` value without a cast.
653
+ *
654
+ * Usage:
655
+ * import '@wrongstack/core/utils/dispatcher-types';
656
+ * const agent = new https.Agent({ rejectUnauthorized: false });
657
+ * fetch(url, { dispatcher: agent }); // ✅ no cast needed
658
+ *
659
+ * Alternatively, use `as HttpsAgentAsDispatcher` to silence any remaining
660
+ * conflicts at the call site.
661
+ *
662
+ * Verified at runtime: `https.Agent` has a `dispatch(req, opts)` method and
663
+ * is callable by the built-in fetch implementation — this shim is a type-level
664
+ * correction only, not a runtime polyfill.
665
+ */
666
+
667
+
668
+
669
+ /**
670
+ * Marker type: a value that fetch's `RequestInit.dispatcher` accepts at runtime.
671
+ * Both `https.Agent` and `undici.Dispatcher` satisfy this structural interface.
672
+ */
673
+ type HttpDispatcher = Pick<Dispatcher, 'dispatch'>;
674
+
675
+ /**
676
+ * Augment `RequestInit` so that `https.Agent` is a valid dispatcher type.
677
+ * Without this, TypeScript rejects `https.Agent` for `dispatcher` because the
678
+ * two agent types are not structurally compatible in the installed @types set.
679
+ */
680
+ declare global {
681
+ interface RequestInit {
682
+ /**
683
+ * Accepts `https.Agent` in addition to `undici.Dispatcher`.
684
+ * Runtime type-check is performed by Node.js / undici — this declaration
685
+ * only tells TypeScript the same thing.
686
+ */
687
+ dispatcher?: HttpDispatcher | undefined;
688
+ }
689
+ }
690
+
691
+ /**
692
+ * Use this cast at call sites where `https.Agent` must be passed to a function
693
+ * typed for `undici.Dispatcher`. Documents the trust boundary: the cast is safe
694
+ * because both types share a `dispatch(req, opts)` method at runtime.
695
+ *
696
+ * @example
697
+ * import type { HttpsAgentAsDispatcher } from '@wrongstack/core';
698
+ * const agent = new https.Agent({ rejectUnauthorized: false });
699
+ * fetch(url, { dispatcher: agent as HttpsAgentAsDispatcher });
700
+ */
701
+ type HttpsAgentAsDispatcher = https.Agent;
702
+
703
+ export { type AtomicWriteOptions, type BuildChildEnvOptions, type CompileFail, type CompileResult, type DeepMergeOptions, FORBIDDEN_PROTO_KEYS, type FileLockOptions, type HttpDispatcher, type HttpsAgentAsDispatcher, type MessageRepairReport, type MessageRepairResult, type NewlineStyle, type OutputLineGuard, type RequestTokenBreakdown, type SafeParseResult, type ToolOutputSerializerOptions, type UnifiedDiffOptions, type ValidationError, type ValidationResult, assertNever, assertNotPrivateHost, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, computeMessageTokens, createToolOutputSerializer, deepMerge, detectNewlineStyle, ensureDir, estimateMessageTokens, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, expandIPv6, formatTodosList, getCalibrationState, getTermSize, isInteractive, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isStdinTTY, isStdoutTTY, matchAny, matchGlob, mergeCustomModelDefs, mergeModelsPayload, normalizeToLf, onResize, recordActualUsage, repairToolUseAdjacency, resetCalibration, safeParse, safeStringify, sanitizeJsonString, setOutputLineGuard, setRawMode, sleep, stripAnsi, toStyle, truncate, unifiedDiff, validateAgainstSchema, withFileLock, writeErr, writeOut };
@@ -3,6 +3,8 @@ import * as fs from 'fs/promises';
3
3
  import * as path2 from 'path';
4
4
  import { isAbsolute, resolve } from 'path';
5
5
  import * as os from 'os';
6
+ import * as dns from 'dns/promises';
7
+ import * as net from 'net';
6
8
 
7
9
  // src/utils/atomic-write.ts
8
10
  async function atomicWrite(targetPath, content, opts = {}) {
@@ -1661,7 +1663,96 @@ function mergeCustomModelDefs(providerCustomModels, configModels) {
1661
1663
  if (Object.keys(out).length === 0) return void 0;
1662
1664
  return out;
1663
1665
  }
1666
+ function isPrivateIPv4(addr) {
1667
+ const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
1668
+ if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
1669
+ return true;
1670
+ }
1671
+ const [a, b, c] = parts;
1672
+ if (a === 0) return true;
1673
+ if (a === 10) return true;
1674
+ if (a === 127) return true;
1675
+ if (a === 169 && b === 254) return true;
1676
+ if (a === 172 && b >= 16 && b <= 31) return true;
1677
+ if (a === 192 && b === 168) return true;
1678
+ if (a === 192 && b === 0 && c === 0) return true;
1679
+ if (a === 100 && b >= 64 && b <= 127) return true;
1680
+ if (a >= 224) return true;
1681
+ return false;
1682
+ }
1683
+ function isPrivateIPv6(raw) {
1684
+ const lower = raw.toLowerCase();
1685
+ if (lower === "::" || lower === "::1") return true;
1686
+ const groups = expandIPv6(lower);
1687
+ if (!groups) return true;
1688
+ if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
1689
+ const a = (groups[6] ?? 0) >> 8;
1690
+ const b = (groups[6] ?? 0) & 255;
1691
+ const c = (groups[7] ?? 0) >> 8;
1692
+ const d = (groups[7] ?? 0) & 255;
1693
+ return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
1694
+ }
1695
+ const high = groups[0] ?? 0;
1696
+ if ((high & 65024) === 64512) return true;
1697
+ if ((high & 65472) === 65152) return true;
1698
+ if ((high & 65280) === 65280) return true;
1699
+ return false;
1700
+ }
1701
+ function expandIPv6(addr) {
1702
+ const parts = addr.split("::");
1703
+ if (parts.length > 2) return null;
1704
+ const parseGroups = (s) => {
1705
+ if (s === "") return [];
1706
+ const out = [];
1707
+ for (const g of s.split(":")) {
1708
+ if (g.length === 0 || g.length > 4) return null;
1709
+ const n = Number.parseInt(g, 16);
1710
+ if (Number.isNaN(n) || n < 0 || n > 65535) return null;
1711
+ out.push(n);
1712
+ }
1713
+ return out;
1714
+ };
1715
+ if (parts.length === 1) {
1716
+ const groups = parseGroups(parts[0] ?? "");
1717
+ if (!groups || groups.length !== 8) return null;
1718
+ return groups;
1719
+ }
1720
+ const head = parseGroups(parts[0] ?? "");
1721
+ const tail = parseGroups(parts[1] ?? "");
1722
+ if (!head || !tail) return null;
1723
+ const fill = 8 - head.length - tail.length;
1724
+ if (fill < 0) return null;
1725
+ return [...head, ...new Array(fill).fill(0), ...tail];
1726
+ }
1727
+ async function assertNotPrivateHost(hostname) {
1728
+ const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
1729
+ if (host === "localhost" || host.endsWith(".localhost")) {
1730
+ throw new Error("fetch: blocked localhost target");
1731
+ }
1732
+ const ipVersion = net.isIP(host);
1733
+ if (ipVersion === 4) {
1734
+ if (isPrivateIPv4(host)) {
1735
+ throw new Error(`fetch: blocked private/loopback address "${host}"`);
1736
+ }
1737
+ } else if (ipVersion === 6) {
1738
+ if (isPrivateIPv6(host)) {
1739
+ throw new Error(`fetch: blocked private/loopback address "${host}"`);
1740
+ }
1741
+ } else {
1742
+ try {
1743
+ const records = await dns.lookup(host, { all: true });
1744
+ for (const r of records) {
1745
+ const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
1746
+ if (bad) {
1747
+ throw new Error(`fetch: resolved to private address ${r.address}`);
1748
+ }
1749
+ }
1750
+ } catch (err) {
1751
+ if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
1752
+ }
1753
+ }
1754
+ }
1664
1755
 
1665
- export { FORBIDDEN_PROTO_KEYS, assertNever, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, computeMessageTokens, computeTaskItemProgress, createToolOutputSerializer, deepMerge, detectNewlineStyle, ensureDir, estimateMessageTokens, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, expectDefined, formatTaskList, formatTaskProgress, formatTodosList, getCalibrationState, getTermSize, isInteractive, isPrimitiveArray, isStdinTTY, isStdoutTTY, matchAny, matchGlob, mergeCustomModelDefs, mergeModelsPayload, normalizeToLf, onResize, projectHash, projectSlug, recordActualUsage, repairToolUseAdjacency, resetCalibration, resolveWstackPaths, safeParse, safeStringify, sanitizeJsonString, setOutputLineGuard, setRawMode, sleep, stripAnsi, toStyle, truncate, unifiedDiff, validateAgainstSchema, withFileLock, writeErr, writeOut, wstackGlobalRoot };
1756
+ export { FORBIDDEN_PROTO_KEYS, assertNever, assertNotPrivateHost, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, computeMessageTokens, computeTaskItemProgress, createToolOutputSerializer, deepMerge, detectNewlineStyle, ensureDir, estimateMessageTokens, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, expandIPv6, expectDefined, formatTaskList, formatTaskProgress, formatTodosList, getCalibrationState, getTermSize, isInteractive, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isStdinTTY, isStdoutTTY, matchAny, matchGlob, mergeCustomModelDefs, mergeModelsPayload, normalizeToLf, onResize, projectHash, projectSlug, recordActualUsage, repairToolUseAdjacency, resetCalibration, resolveWstackPaths, safeParse, safeStringify, sanitizeJsonString, setOutputLineGuard, setRawMode, sleep, stripAnsi, toStyle, truncate, unifiedDiff, validateAgainstSchema, withFileLock, writeErr, writeOut, wstackGlobalRoot };
1666
1757
  //# sourceMappingURL=index.js.map
1667
1758
  //# sourceMappingURL=index.js.map