@claude-flow/cli 3.65.0 → 3.65.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.65.0",
3
+ "version": "3.65.1",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "631fac9d518835e2852a406888316bbbddddb49e7ed8e406d2e971f46833df9a",
6
6
  "hook-handler.cjs": "24502a3bcb78770971147940515c992e35b6736ae14ee2dc54e07d4cdef6a1d8",
@@ -9,6 +9,6 @@
9
9
  "router.cjs": "16648b3c0d73b6681ac2d8d13def6a68286c57f5a11004de5b19a0fa088c833d"
10
10
  }
11
11
  },
12
- "signature": "cwZNQ9x3YdIZ8qur+qzjhgVAubgv3FSXoWEfhkPtNc86sTPBOg6zlIb8Z2bt8ogZ8PnVmNgKas6Ft6QswIBiBg==",
12
+ "signature": "vAyxpgZ8P+tdc0y57OdMmpY1skgNclrdXaKIl8ZFcQzujKG9+Tix+tLhr9XKhgu9Wbt8Fx6ttlPetWN0cKFUAA==",
13
13
  "algorithm": "ed25519"
14
14
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 7,
4
- "generatedAt": "2026-10-30T02:51:18.963Z",
5
- "gitSha": "d000e27e",
4
+ "generatedAt": "2026-10-30T19:15:36.709Z",
5
+ "gitSha": "5ccd8011",
6
6
  "catalog": {
7
7
  "agents": 177,
8
8
  "tools": 445,
@@ -0,0 +1,44 @@
1
+ /**
2
+ * http_fetch's address checks and connection-pinned transport (ADR-164 §5.1.8).
3
+ *
4
+ * Checking the URL's hostname string is not enough: a public-looking name can resolve to
5
+ * loopback, RFC-1918 or cloud metadata (169.254.169.254), and `fetch()` resolves the name
6
+ * again on its own, so even a pre-resolution check can be rebound (TTL-0 DNS) between the
7
+ * check and the connect. Here the socket's DNS lookup *is* the check: `pinnedLookup`
8
+ * resolves the name, refuses it when any address is private, and hands the socket only the
9
+ * addresses it checked, so the connection cannot land anywhere else. IP-literal hosts never
10
+ * reach a lookup; `validateUrl` checks those by text, with the same `isPrivateOrLoopback`.
11
+ *
12
+ * @module @claude-flow/cli/mcp-tools/http-fetch-pinned
13
+ */
14
+ import { type LookupFunction } from 'node:net';
15
+ /** Raised when a hostname resolves to an address the allowlist refuses. */
16
+ export declare class PrivateAddressError extends Error {
17
+ readonly code = "PRIVATE_ADDRESS";
18
+ constructor(host: string, address: string);
19
+ }
20
+ export declare const allowPrivateAddresses: () => boolean;
21
+ /**
22
+ * Whether a host (an IP literal, bracketed or not, or `localhost`) is one http_fetch refuses
23
+ * by default. A name that is not an IP literal is `false` here: what it resolves to is
24
+ * checked at connect time by `pinnedLookup`.
25
+ */
26
+ export declare function isPrivateOrLoopback(host: string): boolean;
27
+ /**
28
+ * The socket's DNS lookup: resolve every address, refuse the host when any of them is
29
+ * private (unless opted in), and return only those checked addresses to the socket.
30
+ */
31
+ export declare const pinnedLookup: LookupFunction;
32
+ export interface PinnedInit {
33
+ method: string;
34
+ headers: Record<string, string>;
35
+ body?: string;
36
+ signal: AbortSignal;
37
+ }
38
+ /**
39
+ * One HTTP(S) request whose connection goes only to an address `pinnedLookup` checked, with
40
+ * no redirect following and a fresh connection every time (no pooled socket from elsewhere).
41
+ * Returns a standard `Response` so the caller reads it as it would a `fetch()` result.
42
+ */
43
+ export declare function pinnedRequest(url: string, init: PinnedInit): Promise<Response>;
44
+ //# sourceMappingURL=http-fetch-pinned.d.ts.map
@@ -0,0 +1,148 @@
1
+ /**
2
+ * http_fetch's address checks and connection-pinned transport (ADR-164 §5.1.8).
3
+ *
4
+ * Checking the URL's hostname string is not enough: a public-looking name can resolve to
5
+ * loopback, RFC-1918 or cloud metadata (169.254.169.254), and `fetch()` resolves the name
6
+ * again on its own, so even a pre-resolution check can be rebound (TTL-0 DNS) between the
7
+ * check and the connect. Here the socket's DNS lookup *is* the check: `pinnedLookup`
8
+ * resolves the name, refuses it when any address is private, and hands the socket only the
9
+ * addresses it checked, so the connection cannot land anywhere else. IP-literal hosts never
10
+ * reach a lookup; `validateUrl` checks those by text, with the same `isPrivateOrLoopback`.
11
+ *
12
+ * @module @claude-flow/cli/mcp-tools/http-fetch-pinned
13
+ */
14
+ import { lookup as dnsLookup } from 'node:dns';
15
+ import * as http from 'node:http';
16
+ import * as https from 'node:https';
17
+ import { isIP } from 'node:net';
18
+ import { Readable } from 'node:stream';
19
+ /** Raised when a hostname resolves to an address the allowlist refuses. */
20
+ export class PrivateAddressError extends Error {
21
+ code = 'PRIVATE_ADDRESS';
22
+ constructor(host, address) {
23
+ super(`host ${host} resolves to ${address}, which is loopback/private/link-local; set CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 to override`);
24
+ this.name = 'PrivateAddressError';
25
+ }
26
+ }
27
+ export const allowPrivateAddresses = () => process.env.CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE === '1';
28
+ /** The 16-bit groups of an IPv6 address (brackets stripped), or null when it is not one. */
29
+ function ipv6Groups(addr) {
30
+ if (isIP(addr) !== 6)
31
+ return null;
32
+ let text = addr.split('%')[0];
33
+ // A trailing dotted IPv4 (`::ffff:1.2.3.4`) is two groups.
34
+ const v4 = text.match(/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
35
+ if (v4) {
36
+ const [a, b, c, d] = v4.slice(1).map(Number);
37
+ text = `${text.slice(0, v4.index)}${((a << 8) | b).toString(16)}:${((c << 8) | d).toString(16)}`;
38
+ }
39
+ const [head, tail] = text.includes('::') ? text.split('::') : [text, undefined];
40
+ const parse = (s) => (s ? s.split(':').map((g) => parseInt(g, 16)) : []);
41
+ const h = parse(head);
42
+ const t = tail === undefined ? [] : parse(tail);
43
+ const groups = tail === undefined ? h : [...h, ...new Array(8 - h.length - t.length).fill(0), ...t];
44
+ return groups.length === 8 && groups.every((g) => Number.isInteger(g) && g >= 0 && g <= 0xffff) ? groups : null;
45
+ }
46
+ function privateIPv4(a, b) {
47
+ return a === 0 // 0.0.0.0/8 ("this" network)
48
+ || a === 127 // loopback
49
+ || a === 10 // RFC 1918
50
+ || (a === 172 && b >= 16 && b <= 31) // RFC 1918
51
+ || (a === 192 && b === 168) // RFC 1918
52
+ || (a === 169 && b === 254) // link-local, cloud metadata
53
+ || (a === 100 && b >= 64 && b <= 127) // CGNAT
54
+ || (a === 198 && (b === 18 || b === 19)) // benchmarking
55
+ || a >= 224; // multicast, reserved, broadcast
56
+ }
57
+ /**
58
+ * Whether a host (an IP literal, bracketed or not, or `localhost`) is one http_fetch refuses
59
+ * by default. A name that is not an IP literal is `false` here: what it resolves to is
60
+ * checked at connect time by `pinnedLookup`.
61
+ */
62
+ export function isPrivateOrLoopback(host) {
63
+ const lower = host.toLowerCase();
64
+ if (lower === 'localhost' || lower === 'localhost.localdomain' || lower.endsWith('.localhost'))
65
+ return true;
66
+ const bare = lower.startsWith('[') && lower.endsWith(']') ? lower.slice(1, -1) : lower;
67
+ const v4 = bare.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
68
+ if (v4)
69
+ return privateIPv4(Number(v4[1]), Number(v4[2]));
70
+ const g = ipv6Groups(bare);
71
+ if (!g)
72
+ return false;
73
+ const embedded = (hi) => privateIPv4(hi >> 8, hi & 0xff);
74
+ if (g.slice(0, 7).every((x) => x === 0) && g[7] <= 1)
75
+ return true; // :: and ::1
76
+ if (g.slice(0, 5).every((x) => x === 0) && g[5] === 0xffff)
77
+ return embedded(g[6]); // ::ffff:a.b.c.d (IPv4-mapped)
78
+ if (g.slice(0, 6).every((x) => x === 0))
79
+ return true; // ::a.b.c.d (deprecated IPv4-compatible)
80
+ if (g[0] === 0x64 && g[1] === 0xff9b)
81
+ return true; // 64:ff9b::/96 and /48 NAT64: may wrap any IPv4
82
+ if (g[0] === 0x2002)
83
+ return embedded(g[1]); // 6to4 carries an IPv4
84
+ if ((g[0] & 0xfe00) === 0xfc00)
85
+ return true; // fc00::/7 unique local
86
+ if ((g[0] & 0xffc0) === 0xfe80 || (g[0] & 0xffc0) === 0xfec0)
87
+ return true; // fe80::/10 link-local, fec0::/10 site-local
88
+ if ((g[0] & 0xff00) === 0xff00)
89
+ return true; // ff00::/8 multicast
90
+ return false;
91
+ }
92
+ /**
93
+ * The socket's DNS lookup: resolve every address, refuse the host when any of them is
94
+ * private (unless opted in), and return only those checked addresses to the socket.
95
+ */
96
+ export const pinnedLookup = (hostname, options, callback) => {
97
+ dnsLookup(hostname, { ...options, all: true }, (err, addresses) => {
98
+ if (err)
99
+ return callback(err, []);
100
+ const list = addresses;
101
+ if (!allowPrivateAddresses()) {
102
+ const bad = list.find((a) => isPrivateOrLoopback(a.address));
103
+ if (bad || list.length === 0) {
104
+ return callback(new PrivateAddressError(hostname, bad?.address ?? 'no address'), []);
105
+ }
106
+ }
107
+ if (options.all)
108
+ return callback(null, list);
109
+ callback(null, list[0].address, list[0].family);
110
+ });
111
+ };
112
+ /** Statuses whose Response must not carry a body. */
113
+ const NULL_BODY = new Set([101, 103, 204, 205, 304]);
114
+ /**
115
+ * One HTTP(S) request whose connection goes only to an address `pinnedLookup` checked, with
116
+ * no redirect following and a fresh connection every time (no pooled socket from elsewhere).
117
+ * Returns a standard `Response` so the caller reads it as it would a `fetch()` result.
118
+ */
119
+ export function pinnedRequest(url, init) {
120
+ const target = new URL(url);
121
+ const client = target.protocol === 'https:' ? https : http;
122
+ const headers = { accept: '*/*', ...init.headers };
123
+ if (init.body !== undefined && !Object.keys(headers).some((k) => k.toLowerCase() === 'content-length')) {
124
+ headers['content-length'] = String(Buffer.byteLength(init.body));
125
+ }
126
+ return new Promise((resolve, reject) => {
127
+ const req = client.request(target, { method: init.method, headers, lookup: pinnedLookup, agent: false, signal: init.signal }, (res) => {
128
+ const out = new Headers();
129
+ for (const [key, value] of Object.entries(res.headers)) {
130
+ for (const v of Array.isArray(value) ? value : value === undefined ? [] : [value])
131
+ out.append(key, v);
132
+ }
133
+ const status = res.statusCode ?? 0;
134
+ if (status < 200 || status > 599) {
135
+ res.destroy();
136
+ reject(new Error(`unsupported HTTP status ${status}`));
137
+ return;
138
+ }
139
+ const body = NULL_BODY.has(status) || init.method === 'HEAD' ? null : Readable.toWeb(res);
140
+ if (body === null)
141
+ res.resume();
142
+ resolve(new Response(body, { status, statusText: res.statusMessage ?? '', headers: out }));
143
+ });
144
+ req.on('error', reject);
145
+ req.end(init.body);
146
+ });
147
+ }
148
+ //# sourceMappingURL=http-fetch-pinned.js.map
@@ -11,7 +11,10 @@
11
11
  * AbortController, response truncation, default User-Agent.
12
12
  *
13
13
  * Architectural constraints (load-bearing):
14
- * - DEFAULT-REFUSES private addresses + loopback (CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 opt-in)
14
+ * - DEFAULT-REFUSES private addresses + loopback (CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 opt-in):
15
+ * IP literals by text here, and every resolved address of a hostname at connect time, on the
16
+ * connection itself (http-fetch-pinned.ts), so DNS that points at, or rebinds to, a private
17
+ * address is refused too
15
18
  * - DEFAULT-REFUSES Authorization / Cookie / X-Auth-* (CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1 opt-in)
16
19
  * - hard timeout 30s default, 60s ceiling
17
20
  * - response truncated to 256KB default, 1MB ceiling
@@ -20,6 +23,8 @@
20
23
  * @module @claude-flow/cli/mcp-tools/http-fetch
21
24
  */
22
25
  import type { MCPTool } from './types.js';
26
+ import { type PinnedInit } from './http-fetch-pinned.js';
27
+ export { isPrivateOrLoopback } from './http-fetch-pinned.js';
23
28
  export declare class HttpFetchValidationError extends Error {
24
29
  readonly code: string;
25
30
  constructor(message: string, code: string);
@@ -45,11 +50,15 @@ export interface HttpFetchResult {
45
50
  error?: string;
46
51
  errorCode?: string;
47
52
  }
53
+ /** How one request is sent; tests replace it to script responses. The default pins each connection to checked addresses. */
54
+ export type HttpFetchRequest = (url: string, init: PinnedInit) => Promise<Response>;
48
55
  /**
49
56
  * Pure execution path so tests can call it without going through the MCP
50
57
  * dispatcher. Returns a result object (does not throw on validation failure
51
58
  * — it returns success: false with an errorCode).
52
59
  */
53
- export declare function httpFetchExecute(input: Record<string, unknown>): Promise<HttpFetchResult>;
60
+ export declare function httpFetchExecute(input: Record<string, unknown>, deps?: {
61
+ request?: HttpFetchRequest;
62
+ }): Promise<HttpFetchResult>;
54
63
  export declare const httpFetchTools: MCPTool[];
55
64
  //# sourceMappingURL=http-fetch-tools.d.ts.map
@@ -11,7 +11,10 @@
11
11
  * AbortController, response truncation, default User-Agent.
12
12
  *
13
13
  * Architectural constraints (load-bearing):
14
- * - DEFAULT-REFUSES private addresses + loopback (CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 opt-in)
14
+ * - DEFAULT-REFUSES private addresses + loopback (CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 opt-in):
15
+ * IP literals by text here, and every resolved address of a hostname at connect time, on the
16
+ * connection itself (http-fetch-pinned.ts), so DNS that points at, or rebinds to, a private
17
+ * address is refused too
15
18
  * - DEFAULT-REFUSES Authorization / Cookie / X-Auth-* (CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1 opt-in)
16
19
  * - hard timeout 30s default, 60s ceiling
17
20
  * - response truncated to 256KB default, 1MB ceiling
@@ -19,6 +22,8 @@
19
22
  *
20
23
  * @module @claude-flow/cli/mcp-tools/http-fetch
21
24
  */
25
+ import { allowPrivateAddresses, isPrivateOrLoopback, pinnedRequest, PrivateAddressError } from './http-fetch-pinned.js';
26
+ export { isPrivateOrLoopback } from './http-fetch-pinned.js';
22
27
  const DEFAULT_TIMEOUT_MS = 30_000;
23
28
  const MAX_TIMEOUT_MS = 60_000;
24
29
  const DEFAULT_MAX_RESPONSE_BYTES = 256 * 1024; // 256 KB
@@ -54,75 +59,11 @@ export function validateUrl(rawUrl) {
54
59
  throw new HttpFetchValidationError(`protocol ${parsed.protocol} not allowed (only http: and https:)`, 'FORBIDDEN_PROTOCOL');
55
60
  }
56
61
  const host = parsed.hostname.toLowerCase();
57
- const allowPrivate = process.env.CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE === '1';
58
- if (!allowPrivate && isPrivateOrLoopback(host)) {
62
+ if (!allowPrivateAddresses() && isPrivateOrLoopback(host)) {
59
63
  throw new HttpFetchValidationError(`host ${host} is loopback/private/link-local; set CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1 to override`, 'PRIVATE_ADDRESS');
60
64
  }
61
65
  return parsed;
62
66
  }
63
- function isPrivateOrLoopback(host) {
64
- if (host === 'localhost' || host === 'localhost.localdomain')
65
- return true;
66
- // Node's URL parser keeps brackets in `.hostname` for every IPv6 literal
67
- // (e.g. "[::1]", "[fc00::1]", "[::ffff:7f00:1]"). Strip them once so every
68
- // check below operates on the bare address — without this, the fc00::/7
69
- // and fe80::/10 checks further down never match anything (they test
70
- // `startsWith('fc'/'fe80:')` against a string starting with "["), and an
71
- // IPv4-mapped-IPv6 literal (`::ffff:a.b.c.d`, which Node normalizes to a
72
- // canonical `::ffff:xxxx:yyyy` hex form) sails through both the IPv4 and
73
- // IPv6 branches untested — a live SSRF bypass (e.g. `::ffff:127.0.0.1` or
74
- // `::ffff:169.254.169.254` reaching loopback / cloud-metadata targets).
75
- const bare = host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host;
76
- if (bare === '::1')
77
- return true; // IPv6 loopback
78
- // IPv4-mapped IPv6 — decode back to dotted-decimal and recurse into the
79
- // IPv4 check below so it inherits every existing private/loopback/
80
- // link-local/CGNAT rule instead of needing its own copy.
81
- const mappedHex = bare.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
82
- if (mappedHex) {
83
- const hi = parseInt(mappedHex[1], 16);
84
- const lo = parseInt(mappedHex[2], 16);
85
- const decoded = `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`;
86
- return isPrivateOrLoopback(decoded);
87
- }
88
- const mappedDecimal = bare.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
89
- if (mappedDecimal)
90
- return isPrivateOrLoopback(mappedDecimal[1]);
91
- // IPv4 numeric checks
92
- const m = bare.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
93
- if (m) {
94
- const a = Number(m[1]);
95
- const b = Number(m[2]);
96
- if (a === 0)
97
- return true; // 0.0.0.0/8
98
- if (a === 127)
99
- return true; // loopback
100
- if (a === 10)
101
- return true; // RFC1918
102
- if (a === 172 && b >= 16 && b <= 31)
103
- return true; // RFC1918
104
- if (a === 192 && b === 168)
105
- return true; // RFC1918
106
- if (a === 169 && b === 254)
107
- return true; // link-local
108
- if (a === 100 && b >= 64 && b <= 127)
109
- return true; // CGNAT
110
- return false;
111
- }
112
- // IPv6 special forms — be conservative and accept only public-looking
113
- // literals. Reject obvious private/local forms.
114
- if (bare.startsWith('fc') || bare.startsWith('fd'))
115
- return true; // fc00::/7 ULA
116
- if (bare.startsWith('fe80:'))
117
- return true; // link-local
118
- // KNOWN LIMITATION (pre-existing, not introduced by the bracket-stripping /
119
- // IPv4-mapped-IPv6 fix above; flagged by an independent adversarial review):
120
- // `::` (IPv6 unspecified/"any", the analog of 0.0.0.0, which the IPv4 branch
121
- // above does block), IPv6 multicast (ff00::/8), and NAT64 (64:ff9b::/96) are
122
- // not covered. Left as a follow-up hardening item rather than scope-creeping
123
- // this patch.
124
- return false;
125
- }
126
67
  export function validateHeaders(headers) {
127
68
  const allowAuth = process.env.CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH === '1';
128
69
  const out = {};
@@ -156,7 +97,8 @@ function clampNumber(raw, defaultValue, max) {
156
97
  * dispatcher. Returns a result object (does not throw on validation failure
157
98
  * — it returns success: false with an errorCode).
158
99
  */
159
- export async function httpFetchExecute(input) {
100
+ export async function httpFetchExecute(input, deps = {}) {
101
+ const send = deps.request ?? pinnedRequest;
160
102
  const startedAt = Date.now();
161
103
  const url = String(input.url ?? '');
162
104
  const method = String(input.method ?? 'GET').toUpperCase();
@@ -242,12 +184,13 @@ export async function httpFetchExecute(input) {
242
184
  // protocols after passing the allowlist. Every hop is validated with the
243
185
  // same rules; sensitive headers are dropped when the origin changes.
244
186
  while (true) {
245
- response = await fetch(currentUrl.toString(), {
187
+ // Never fetch(): it resolves the hostname again on its own, so what it connects to is
188
+ // not what was checked. `send` connects only to addresses its DNS lookup checked.
189
+ response = await send(currentUrl.toString(), {
246
190
  method: currentMethod,
247
191
  headers,
248
192
  body: currentMethod === 'GET' || currentMethod === 'HEAD' ? undefined : currentBody,
249
193
  signal: controller.signal,
250
- redirect: 'manual',
251
194
  });
252
195
  if (!REDIRECT_STATUSES.has(response.status))
253
196
  break;
@@ -378,6 +321,12 @@ export async function httpFetchExecute(input) {
378
321
  }
379
322
  catch (e) {
380
323
  const msg = e instanceof Error ? e.message : String(e);
324
+ if (e instanceof PrivateAddressError) {
325
+ return {
326
+ success: false, status: 0, statusText: '', headers: {}, body: '', bodyTruncated: false, bytesRead: 0,
327
+ durationMs: Date.now() - startedAt, url: parsed.toString(), method, error: msg, errorCode: 'PRIVATE_ADDRESS',
328
+ };
329
+ }
381
330
  const aborted = msg.includes('aborted') || e?.name === 'AbortError';
382
331
  return {
383
332
  success: false,
@@ -401,7 +350,7 @@ export async function httpFetchExecute(input) {
401
350
  export const httpFetchTools = [
402
351
  {
403
352
  name: 'http_fetch',
404
- description: 'ADR-164 §5.1.8 — HTTP probe primitive for business-pod ops benches (synthetic 200/500 endpoint checks, third-party status pages). Default-secure: blocks file://, ftp://, RFC-1918 / loopback / link-local hosts unless CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1, and rejects Authorization / Cookie / X-Auth-* headers unless CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1. Hard 30s timeout (60s ceiling), response truncated to 256 KB (1 MB ceiling), default User-Agent ruflo-http-fetch/1.0. Use when a pod or smoke contract needs a guarded HTTP probe — calling Node fetch() directly is wrong because it skips the URL allowlist and header sanitization that ADR-164 mandates for autopilot mode. Pair with the ops-pod bench in plugins/ruflo-business-pods/templates/ops.json (the §4.4 synthetic-endpoint test).',
353
+ description: 'ADR-164 §5.1.8 — HTTP probe primitive for business-pod ops benches (synthetic 200/500 endpoint checks, third-party status pages). Default-secure: blocks file://, ftp://, and RFC-1918 / loopback / link-local hosts, whether given as an address or as a name that resolves to one (checked on the connection itself, every redirect hop included), unless CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE=1, and rejects Authorization / Cookie / X-Auth-* headers unless CLAUDE_FLOW_HTTP_FETCH_ALLOW_AUTH=1. Hard 30s timeout (60s ceiling), response truncated to 256 KB (1 MB ceiling), default User-Agent ruflo-http-fetch/1.0. Use when a pod or smoke contract needs a guarded HTTP probe — calling Node fetch() directly is wrong because it skips the URL allowlist and header sanitization that ADR-164 mandates for autopilot mode. Pair with the ops-pod bench in plugins/ruflo-business-pods/templates/ops.json (the §4.4 synthetic-endpoint test).',
405
354
  category: 'business-pods',
406
355
  tags: ['business-pods', 'http', 'fetch', 'adr-164', 'security', 'ops-pod'],
407
356
  inputSchema: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.65.0",
3
+ "version": "3.65.1",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",