@agentcomms/events-daemon 0.0.0-stage → 0.17.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.
- package/LICENSE +21 -0
- package/README.md +267 -2
- package/THIRD_PARTY_LICENSES +499 -0
- package/dist/b3-contract-DFxcHY0I.mjs +2486 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +21675 -0
- package/dist/index.d.mts +12751 -0
- package/dist/index.mjs +2 -0
- package/dist/mcp-DXXb3Vv3-B0sHZfig.mjs +22131 -0
- package/dist/records-DjE5a4_H.mjs +2 -0
- package/dist/records-DsUxYBwb.mjs +1143 -0
- package/dist/stdio-CD7QQBAh.mjs +102 -0
- package/dist/webhook-target-DyDTwWhV.mjs +673 -0
- package/package.json +71 -3
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
import { canonicalJson, sha256Hex } from "@agentcomms/core";
|
|
2
|
+
//#endregion
|
|
3
|
+
//#region src/network/iana-special-purpose.ts
|
|
4
|
+
/** Checked-in IANA policy; network code must never refresh or consult it at runtime. */
|
|
5
|
+
const IANA_SPECIAL_PURPOSE = {
|
|
6
|
+
snapshotVersion: "iana-special-purpose-2025-10-09",
|
|
7
|
+
ipv4: {
|
|
8
|
+
"title": "IANA IPv4 Special-Purpose Address Registry",
|
|
9
|
+
"url": "https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry-1.csv",
|
|
10
|
+
"lastUpdated": "2025-10-09",
|
|
11
|
+
"rawSourceSha256": "e3e39e76d00b1677335db8e9a805c7b9480ea2f4dc9e33f0b93cd3a905128d73",
|
|
12
|
+
"prefixes": [
|
|
13
|
+
{
|
|
14
|
+
"prefix": "0.0.0.0/8",
|
|
15
|
+
"globallyReachable": false
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"prefix": "0.0.0.0/32",
|
|
19
|
+
"globallyReachable": false
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"prefix": "10.0.0.0/8",
|
|
23
|
+
"globallyReachable": false
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"prefix": "100.64.0.0/10",
|
|
27
|
+
"globallyReachable": false
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"prefix": "127.0.0.0/8",
|
|
31
|
+
"globallyReachable": false
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"prefix": "169.254.0.0/16",
|
|
35
|
+
"globallyReachable": false
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"prefix": "172.16.0.0/12",
|
|
39
|
+
"globallyReachable": false
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"prefix": "192.0.0.0/24",
|
|
43
|
+
"globallyReachable": false
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"prefix": "192.0.0.0/29",
|
|
47
|
+
"globallyReachable": false
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"prefix": "192.0.0.8/32",
|
|
51
|
+
"globallyReachable": false
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"prefix": "192.0.0.9/32",
|
|
55
|
+
"globallyReachable": true
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"prefix": "192.0.0.10/32",
|
|
59
|
+
"globallyReachable": true
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"prefix": "192.0.0.170/32",
|
|
63
|
+
"globallyReachable": false
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"prefix": "192.0.0.171/32",
|
|
67
|
+
"globallyReachable": false
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"prefix": "192.0.2.0/24",
|
|
71
|
+
"globallyReachable": false
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"prefix": "192.31.196.0/24",
|
|
75
|
+
"globallyReachable": true
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"prefix": "192.52.193.0/24",
|
|
79
|
+
"globallyReachable": true
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"prefix": "192.88.99.0/24",
|
|
83
|
+
"globallyReachable": false
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"prefix": "192.88.99.2/32",
|
|
87
|
+
"globallyReachable": false
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"prefix": "192.168.0.0/16",
|
|
91
|
+
"globallyReachable": false
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"prefix": "192.175.48.0/24",
|
|
95
|
+
"globallyReachable": true
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"prefix": "198.18.0.0/15",
|
|
99
|
+
"globallyReachable": false
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"prefix": "198.51.100.0/24",
|
|
103
|
+
"globallyReachable": false
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"prefix": "203.0.113.0/24",
|
|
107
|
+
"globallyReachable": false
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"prefix": "240.0.0.0/4",
|
|
111
|
+
"globallyReachable": false
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"prefix": "255.255.255.255/32",
|
|
115
|
+
"globallyReachable": false
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
ipv6: {
|
|
120
|
+
"title": "IANA IPv6 Special-Purpose Address Registry",
|
|
121
|
+
"url": "https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry-1.csv",
|
|
122
|
+
"lastUpdated": "2025-10-09",
|
|
123
|
+
"rawSourceSha256": "775feea0621dec8735a44fbf30f762e721e8f0a1b3ab7eb341961a88cfce2139",
|
|
124
|
+
"prefixes": [
|
|
125
|
+
{
|
|
126
|
+
"prefix": "::1/128",
|
|
127
|
+
"globallyReachable": false
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"prefix": "::/128",
|
|
131
|
+
"globallyReachable": false
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"prefix": "::ffff:0:0/96",
|
|
135
|
+
"globallyReachable": false
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"prefix": "64:ff9b::/96",
|
|
139
|
+
"globallyReachable": true
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"prefix": "64:ff9b:1::/48",
|
|
143
|
+
"globallyReachable": false
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"prefix": "100::/64",
|
|
147
|
+
"globallyReachable": false
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"prefix": "100:0:0:1::/64",
|
|
151
|
+
"globallyReachable": false
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"prefix": "2001::/23",
|
|
155
|
+
"globallyReachable": false
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"prefix": "2001::/32",
|
|
159
|
+
"globallyReachable": false
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"prefix": "2001:1::1/128",
|
|
163
|
+
"globallyReachable": true
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"prefix": "2001:1::2/128",
|
|
167
|
+
"globallyReachable": true
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"prefix": "2001:1::3/128",
|
|
171
|
+
"globallyReachable": true
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"prefix": "2001:2::/48",
|
|
175
|
+
"globallyReachable": false
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"prefix": "2001:3::/32",
|
|
179
|
+
"globallyReachable": true
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"prefix": "2001:4:112::/48",
|
|
183
|
+
"globallyReachable": true
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"prefix": "2001:10::/28",
|
|
187
|
+
"globallyReachable": false
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"prefix": "2001:20::/28",
|
|
191
|
+
"globallyReachable": true
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"prefix": "2001:30::/28",
|
|
195
|
+
"globallyReachable": true
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"prefix": "2001:db8::/32",
|
|
199
|
+
"globallyReachable": false
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"prefix": "2002::/16",
|
|
203
|
+
"globallyReachable": false
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"prefix": "2620:4f:8000::/48",
|
|
207
|
+
"globallyReachable": true
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"prefix": "3fff::/20",
|
|
211
|
+
"globallyReachable": false
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"prefix": "5f00::/16",
|
|
215
|
+
"globallyReachable": false
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"prefix": "fc00::/7",
|
|
219
|
+
"globallyReachable": false
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"prefix": "fe80::/10",
|
|
223
|
+
"globallyReachable": false
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var AddressPolicyError = class extends Error {
|
|
229
|
+
code = "NETWORK_ADDRESS_REFUSED";
|
|
230
|
+
constructor(message) {
|
|
231
|
+
super(message);
|
|
232
|
+
this.name = "AddressPolicyError";
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
const IPV4_BITS = 32;
|
|
236
|
+
const IPV6_BITS = 128;
|
|
237
|
+
const IPV4_MAX = (1n << 32n) - 1n;
|
|
238
|
+
const WELL_KNOWN_NAT64 = parseCidr("64:ff9b::/96");
|
|
239
|
+
const LOCALLY_ASSIGNED_NAT64 = parseCidr("64:ff9b:1::/48");
|
|
240
|
+
const LINK_LOCAL_V4 = parseCidr("169.254.0.0/16");
|
|
241
|
+
const LINK_LOCAL_V6 = parseCidr("fe80::/10");
|
|
242
|
+
const MULTICAST_V4 = parseCidr("224.0.0.0/4");
|
|
243
|
+
const MULTICAST_V6 = parseCidr("ff00::/8");
|
|
244
|
+
const METADATA_V4 = parseIpLiteral("169.254.169.254");
|
|
245
|
+
const METADATA_V6 = parseIpLiteral("fd00:ec2::254");
|
|
246
|
+
const registryPrefixes = [...IANA_SPECIAL_PURPOSE.ipv4.prefixes.map((entry) => ({
|
|
247
|
+
...parseCidr(entry.prefix),
|
|
248
|
+
globallyReachable: entry.globallyReachable
|
|
249
|
+
})), ...IANA_SPECIAL_PURPOSE.ipv6.prefixes.map((entry) => ({
|
|
250
|
+
...parseCidr(entry.prefix),
|
|
251
|
+
globallyReachable: entry.globallyReachable
|
|
252
|
+
}))];
|
|
253
|
+
function bitsFor(family) {
|
|
254
|
+
return family === 4 ? IPV4_BITS : IPV6_BITS;
|
|
255
|
+
}
|
|
256
|
+
function mask(family, prefixLength) {
|
|
257
|
+
const bits = bitsFor(family);
|
|
258
|
+
if (!Number.isInteger(prefixLength) || prefixLength < 0 || prefixLength > bits) throw new AddressPolicyError(`invalid CIDR prefix length ${prefixLength}`);
|
|
259
|
+
if (prefixLength === 0) return 0n;
|
|
260
|
+
return (1n << BigInt(prefixLength)) - 1n << BigInt(bits - prefixLength);
|
|
261
|
+
}
|
|
262
|
+
function stripBrackets(value) {
|
|
263
|
+
if (value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1);
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
function parseIpv4(value) {
|
|
267
|
+
const parts = value.split(".");
|
|
268
|
+
if (parts.length !== 4) return null;
|
|
269
|
+
let result = 0n;
|
|
270
|
+
for (const part of parts) {
|
|
271
|
+
if (!/^(?:0|[1-9]\d{0,2})$/.test(part)) return null;
|
|
272
|
+
const octet = Number(part);
|
|
273
|
+
if (octet > 255) return null;
|
|
274
|
+
result = result << 8n | BigInt(octet);
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
function ipv4Text(value) {
|
|
279
|
+
return [
|
|
280
|
+
24n,
|
|
281
|
+
16n,
|
|
282
|
+
8n,
|
|
283
|
+
0n
|
|
284
|
+
].map((shift) => Number(value >> shift & 255n)).join(".");
|
|
285
|
+
}
|
|
286
|
+
function parseIpv6(value) {
|
|
287
|
+
if (value.includes("%")) return null;
|
|
288
|
+
const doubleColon = value.indexOf("::");
|
|
289
|
+
if (doubleColon !== -1 && value.indexOf("::", doubleColon + 1) !== -1) return null;
|
|
290
|
+
const left = doubleColon === -1 ? value : value.slice(0, doubleColon);
|
|
291
|
+
const right = doubleColon === -1 ? "" : value.slice(doubleColon + 2);
|
|
292
|
+
const leftParts = left === "" ? [] : left.split(":");
|
|
293
|
+
const rightParts = right === "" ? [] : right.split(":");
|
|
294
|
+
const rawParts = [...leftParts, ...rightParts];
|
|
295
|
+
if (rawParts.some((part) => part === "")) return null;
|
|
296
|
+
const groups = [];
|
|
297
|
+
for (let index = 0; index < rawParts.length; index += 1) {
|
|
298
|
+
const part = rawParts[index];
|
|
299
|
+
if (part === void 0) return null;
|
|
300
|
+
if (part.includes(".")) {
|
|
301
|
+
if (index !== rawParts.length - 1) return null;
|
|
302
|
+
const embedded = parseIpv4(part);
|
|
303
|
+
if (embedded === null) return null;
|
|
304
|
+
groups.push(Number(embedded >> 16n & 65535n), Number(embedded & 65535n));
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (!/^[0-9a-f]{1,4}$/i.test(part)) return null;
|
|
308
|
+
groups.push(Number.parseInt(part, 16));
|
|
309
|
+
}
|
|
310
|
+
if (doubleColon === -1) {
|
|
311
|
+
if (groups.length !== 8) return null;
|
|
312
|
+
} else {
|
|
313
|
+
if (groups.length >= 8) return null;
|
|
314
|
+
const omitted = 8 - groups.length;
|
|
315
|
+
groups.splice(leftParts.reduce((count, part) => count + (part.includes(".") ? 2 : 1), 0), 0, ...Array(omitted).fill(0));
|
|
316
|
+
}
|
|
317
|
+
let result = 0n;
|
|
318
|
+
for (const group of groups) result = result << 16n | BigInt(group);
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
function ipv6Text(value) {
|
|
322
|
+
const groups = Array.from({ length: 8 }, (_, index) => Number(value >> BigInt((7 - index) * 16) & 65535n));
|
|
323
|
+
let bestStart = -1;
|
|
324
|
+
let bestLength = 0;
|
|
325
|
+
for (let index = 0; index < groups.length;) {
|
|
326
|
+
const group = groups[index];
|
|
327
|
+
if (group === void 0) break;
|
|
328
|
+
if (group !== 0) {
|
|
329
|
+
index += 1;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const start = index;
|
|
333
|
+
while (groups[index] === 0) index += 1;
|
|
334
|
+
const length = index - start;
|
|
335
|
+
if (length > bestLength && length > 1) {
|
|
336
|
+
bestStart = start;
|
|
337
|
+
bestLength = length;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (bestStart === 0 && bestLength === 8) return "::";
|
|
341
|
+
const pieces = [];
|
|
342
|
+
for (let index = 0; index < groups.length; index += 1) {
|
|
343
|
+
if (index === bestStart) {
|
|
344
|
+
pieces.push("");
|
|
345
|
+
index += bestLength - 1;
|
|
346
|
+
if (index === groups.length - 1) pieces.push("");
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const group = groups[index];
|
|
350
|
+
if (group === void 0) throw new Error("IPv6 formatter lost a parsed group");
|
|
351
|
+
pieces.push(group.toString(16));
|
|
352
|
+
}
|
|
353
|
+
return pieces.join(":").replace(/^:/, "::").replace(/:$/, "::");
|
|
354
|
+
}
|
|
355
|
+
/** Parses only a canonical-literal-shaped IP address, never a hostname or a DNS spelling. */
|
|
356
|
+
function parseIpLiteral(input) {
|
|
357
|
+
const value = stripBrackets(input.trim());
|
|
358
|
+
const ipv4 = parseIpv4(value);
|
|
359
|
+
if (ipv4 !== null) return {
|
|
360
|
+
address: ipv4Text(ipv4),
|
|
361
|
+
family: 4,
|
|
362
|
+
value: ipv4
|
|
363
|
+
};
|
|
364
|
+
const ipv6 = parseIpv6(value);
|
|
365
|
+
if (ipv6 !== null) return {
|
|
366
|
+
address: ipv6Text(ipv6),
|
|
367
|
+
family: 6,
|
|
368
|
+
value: ipv6
|
|
369
|
+
};
|
|
370
|
+
throw new AddressPolicyError(`a literal IP address was required, received ${input}`);
|
|
371
|
+
}
|
|
372
|
+
function parseCidr(input) {
|
|
373
|
+
const slash = input.lastIndexOf("/");
|
|
374
|
+
if (slash <= 0 || slash === input.length - 1) throw new AddressPolicyError(`invalid CIDR ${input}`);
|
|
375
|
+
const address = parseIpLiteral(input.slice(0, slash));
|
|
376
|
+
const prefixLength = Number(input.slice(slash + 1));
|
|
377
|
+
const prefixMask = mask(address.family, prefixLength);
|
|
378
|
+
if ((address.value & prefixMask) !== address.value) throw new AddressPolicyError(`CIDR ${input} has host bits set`);
|
|
379
|
+
return {
|
|
380
|
+
source: `${address.address}/${prefixLength}`,
|
|
381
|
+
family: address.family,
|
|
382
|
+
prefixLength,
|
|
383
|
+
value: address.value
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function inCidr(address, cidr) {
|
|
387
|
+
return address.family === cidr.family && (address.value & mask(cidr.family, cidr.prefixLength)) === cidr.value;
|
|
388
|
+
}
|
|
389
|
+
function parseApprovedAddressSet(entries) {
|
|
390
|
+
if (entries.length === 0) throw new AddressPolicyError("approvedAddressSet must be non-empty");
|
|
391
|
+
const parsed = entries.map((entry) => entry.includes("/") ? parseCidr(entry) : parseCidr(`${parseIpLiteral(entry).address}/${bitsFor(parseIpLiteral(entry).family)}`));
|
|
392
|
+
for (let index = 1; index < entries.length; index += 1) {
|
|
393
|
+
const previous = entries[index - 1];
|
|
394
|
+
const current = entries[index];
|
|
395
|
+
if (previous === void 0 || current === void 0) throw new AddressPolicyError("approvedAddressSet could not be read");
|
|
396
|
+
if (previous.localeCompare(current) >= 0) throw new AddressPolicyError("approvedAddressSet must contain sorted, duplicate-free literals or CIDRs");
|
|
397
|
+
}
|
|
398
|
+
return parsed;
|
|
399
|
+
}
|
|
400
|
+
/** Checks the persisted rule input before a connector can use it to resolve a host. */
|
|
401
|
+
function assertApprovedAddressSet(entries) {
|
|
402
|
+
parseApprovedAddressSet(entries);
|
|
403
|
+
}
|
|
404
|
+
function isUnconditionallyDenied(address) {
|
|
405
|
+
return inCidr(address, LINK_LOCAL_V4) || inCidr(address, LINK_LOCAL_V6) || inCidr(address, MULTICAST_V4) || inCidr(address, MULTICAST_V6) || address.family === METADATA_V4.family && address.value === METADATA_V4.value || address.family === METADATA_V6.family && address.value === METADATA_V6.value;
|
|
406
|
+
}
|
|
407
|
+
function globallyReachable(address) {
|
|
408
|
+
return registryPrefixes.filter((entry) => inCidr(address, entry)).sort((left, right) => right.prefixLength - left.prefixLength)[0]?.globallyReachable ?? true;
|
|
409
|
+
}
|
|
410
|
+
function ipv4From(value) {
|
|
411
|
+
const ipv4 = value & IPV4_MAX;
|
|
412
|
+
return {
|
|
413
|
+
address: ipv4Text(ipv4),
|
|
414
|
+
family: 4,
|
|
415
|
+
value: ipv4
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function configuredNat64(options) {
|
|
419
|
+
const raw = options.activeNat64Prefix ?? null;
|
|
420
|
+
if (raw === null) return null;
|
|
421
|
+
const entries = typeof raw === "string" ? [raw] : raw;
|
|
422
|
+
if (entries.length !== 1) throw new AddressPolicyError("NAT64 needs exactly one trusted /96 prefix");
|
|
423
|
+
const prefixText = entries[0];
|
|
424
|
+
if (prefixText === void 0) throw new AddressPolicyError("NAT64 prefix could not be read");
|
|
425
|
+
const prefix = parseCidr(prefixText);
|
|
426
|
+
if (prefix.family !== 6 || prefix.prefixLength !== 96) throw new AddressPolicyError("NAT64 needs one trusted IPv6 /96 prefix");
|
|
427
|
+
return prefix;
|
|
428
|
+
}
|
|
429
|
+
function embeddedAddresses(address, options) {
|
|
430
|
+
if (address.family !== 6) return [];
|
|
431
|
+
const embedded = [];
|
|
432
|
+
const upper96 = address.value >> 32n;
|
|
433
|
+
const mapped = upper96 === 65535n;
|
|
434
|
+
const compatible = upper96 === 0n && address.value > 1n;
|
|
435
|
+
if (mapped || compatible) embedded.push(ipv4From(address.value));
|
|
436
|
+
const configured = configuredNat64(options);
|
|
437
|
+
if (inCidr(address, WELL_KNOWN_NAT64) || inCidr(address, LOCALLY_ASSIGNED_NAT64) || configured !== null && inCidr(address, configured)) {
|
|
438
|
+
if (configured === null || !inCidr(address, configured)) throw new AddressPolicyError("NAT64 address has no single trusted active /96 prefix");
|
|
439
|
+
embedded.push(ipv4From(address.value));
|
|
440
|
+
}
|
|
441
|
+
if (inCidr(address, parseCidr("2002::/16"))) embedded.push(ipv4From(address.value >> 80n));
|
|
442
|
+
if (inCidr(address, parseCidr("2001:0::/32"))) {
|
|
443
|
+
embedded.push(ipv4From(address.value >> 64n));
|
|
444
|
+
embedded.push(ipv4From((address.value ^ IPV4_MAX) & IPV4_MAX));
|
|
445
|
+
}
|
|
446
|
+
const isatapInterface = address.value >> 32n & IPV4_MAX;
|
|
447
|
+
if (isatapInterface === 24318n || isatapInterface === 33578750n) embedded.push(ipv4From(address.value));
|
|
448
|
+
return embedded;
|
|
449
|
+
}
|
|
450
|
+
function assertOne(address, approved, options, seen) {
|
|
451
|
+
if (seen.has(`${address.family}:${address.value}`)) return [];
|
|
452
|
+
seen.add(`${address.family}:${address.value}`);
|
|
453
|
+
if (isUnconditionallyDenied(address)) throw new AddressPolicyError(`address ${address.address} is metadata or link-local and is always refused`);
|
|
454
|
+
if (options.addressMode === "global-only") {
|
|
455
|
+
if (!globallyReachable(address)) throw new AddressPolicyError(`non-global address ${address.address} is refused by global-only policy`);
|
|
456
|
+
} else {
|
|
457
|
+
if (approved === void 0) throw new AddressPolicyError("approvedAddressSet was unavailable");
|
|
458
|
+
const explicitlyApproved = approved.some((entry) => inCidr(address, entry));
|
|
459
|
+
if (!globallyReachable(address) && !explicitlyApproved) throw new AddressPolicyError(`non-global address ${address.address} needs an exact approvedAddressSet entry`);
|
|
460
|
+
if (!explicitlyApproved) throw new AddressPolicyError(`address ${address.address} is not in the approvedAddressSet`);
|
|
461
|
+
}
|
|
462
|
+
const nested = embeddedAddresses(address, options);
|
|
463
|
+
for (const child of nested) assertOne(child, approved, options, seen);
|
|
464
|
+
return nested;
|
|
465
|
+
}
|
|
466
|
+
/** Validates the literal itself and every embedded transition address under the selected exact-set or global-only policy. */
|
|
467
|
+
function assertAddressAllowed(input, options) {
|
|
468
|
+
const address = parseIpLiteral(input);
|
|
469
|
+
const embedded = assertOne(address, options.addressMode === "global-only" ? void 0 : parseApprovedAddressSet(options.approvedAddressSet), options, /* @__PURE__ */ new Set());
|
|
470
|
+
return {
|
|
471
|
+
address: address.address,
|
|
472
|
+
family: address.family,
|
|
473
|
+
embedded
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region src/network/local-endpoint.ts
|
|
478
|
+
var LocalEndpointError = class extends Error {
|
|
479
|
+
code = "LOCAL_ENDPOINT_REFUSED";
|
|
480
|
+
constructor(message) {
|
|
481
|
+
super(message);
|
|
482
|
+
this.name = "LocalEndpointError";
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
/** Validates the deliberately narrow local-only network descriptor. */
|
|
486
|
+
function validateLocalEndpoint(input, approvedAddressSet) {
|
|
487
|
+
const url = typeof input === "string" ? new URL(input) : new URL(input.href);
|
|
488
|
+
const hostname = url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname;
|
|
489
|
+
if (url.protocol !== "http:") throw new LocalEndpointError("a local endpoint must use HTTP");
|
|
490
|
+
if (url.username !== "" || url.password !== "" || url.hash !== "") throw new LocalEndpointError("a local endpoint URL has unsupported authority data");
|
|
491
|
+
let address;
|
|
492
|
+
try {
|
|
493
|
+
address = parseIpLiteral(hostname).address;
|
|
494
|
+
assertAddressAllowed(address, { approvedAddressSet });
|
|
495
|
+
} catch (error) {
|
|
496
|
+
if (error instanceof AddressPolicyError) throw new LocalEndpointError(error.message);
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
499
|
+
if (address !== "127.0.0.1" && address !== "::1") throw new LocalEndpointError("a local endpoint must use literal 127.0.0.1 or ::1");
|
|
500
|
+
if (approvedAddressSet.length !== 1 || approvedAddressSet[0] === void 0 || approvedAddressSet[0].includes("/") || parseIpLiteral(approvedAddressSet[0]).address !== address) throw new LocalEndpointError("a local endpoint requires its one exact approved literal loopback address");
|
|
501
|
+
return {
|
|
502
|
+
url,
|
|
503
|
+
address
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
//#endregion
|
|
507
|
+
//#region src/domain/lifecycle.ts
|
|
508
|
+
/** A content-free domain refusal. No caller should substitute an arbitrary Error message at a control boundary. */
|
|
509
|
+
var EventDomainError = class extends Error {
|
|
510
|
+
code;
|
|
511
|
+
constructor(code, message) {
|
|
512
|
+
super(message);
|
|
513
|
+
this.name = "EventDomainError";
|
|
514
|
+
this.code = code;
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
const JUDGE_KIND_TYPESAFE = "typesafe";
|
|
518
|
+
const JUDGE_KIND_LAYA = "laya";
|
|
519
|
+
const JUDGE_KIND_LOCAL_ENDPOINT = "local-endpoint";
|
|
520
|
+
const JUDGE_KINDS = [
|
|
521
|
+
JUDGE_KIND_TYPESAFE,
|
|
522
|
+
JUDGE_KIND_LAYA,
|
|
523
|
+
JUDGE_KIND_LOCAL_ENDPOINT
|
|
524
|
+
];
|
|
525
|
+
function isJudgeKind(value) {
|
|
526
|
+
return typeof value === "string" && JUDGE_KINDS.includes(value);
|
|
527
|
+
}
|
|
528
|
+
function assertJudgeKindEnabled(kind, enabled) {
|
|
529
|
+
if (!enabled) throw new EventDomainError("JUDGE_KIND_DISABLED", `the ${kind} judge kind is disabled`);
|
|
530
|
+
}
|
|
531
|
+
//#endregion
|
|
532
|
+
//#region src/domain/webhook-target.ts
|
|
533
|
+
function fail(message) {
|
|
534
|
+
throw new EventDomainError("VERSION_DOCUMENT_INVALID", message);
|
|
535
|
+
}
|
|
536
|
+
function record(value, name) {
|
|
537
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return fail(`${name} is an object`);
|
|
538
|
+
try {
|
|
539
|
+
return JSON.parse(canonicalJson(value));
|
|
540
|
+
} catch {
|
|
541
|
+
return fail(`${name} is canonical JSON`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function text(value, name) {
|
|
545
|
+
if (typeof value !== "string" || value.length === 0) return fail(`${name} is a non-empty string`);
|
|
546
|
+
return value;
|
|
547
|
+
}
|
|
548
|
+
function positiveInteger(value, name) {
|
|
549
|
+
if (!Number.isSafeInteger(value) || value < 1) return fail(`${name} is a positive safe integer`);
|
|
550
|
+
return value;
|
|
551
|
+
}
|
|
552
|
+
function canonicalPercentEscapes(value) {
|
|
553
|
+
return value.replace(/%[0-9a-f]{2}/gi, (encoded) => encoded.toUpperCase());
|
|
554
|
+
}
|
|
555
|
+
function canonicalUrl(value, allowQuery) {
|
|
556
|
+
let parsed;
|
|
557
|
+
try {
|
|
558
|
+
parsed = new URL(value);
|
|
559
|
+
} catch {
|
|
560
|
+
return fail("a webhook URL is absolute");
|
|
561
|
+
}
|
|
562
|
+
const scheme = parsed.protocol.slice(0, -1);
|
|
563
|
+
if (scheme !== "http" && scheme !== "https") return fail("a webhook URL uses http or https");
|
|
564
|
+
if (parsed.hash.length !== 0) return fail("a webhook URL has no fragment");
|
|
565
|
+
if (!allowQuery && (parsed.username.length !== 0 || parsed.password.length !== 0 || parsed.search.length !== 0)) return fail("a plain webhook URL has no userinfo or query");
|
|
566
|
+
const port = parsed.port.length === 0 ? scheme === "https" ? 443 : 80 : Number(parsed.port);
|
|
567
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65535) return fail("a webhook URL has a valid port");
|
|
568
|
+
const host = parsed.hostname.toLowerCase();
|
|
569
|
+
if (host.length === 0) return fail("a webhook URL has a host");
|
|
570
|
+
return {
|
|
571
|
+
value: `${scheme}://${allowQuery && (parsed.username.length !== 0 || parsed.password.length !== 0) ? `${canonicalPercentEscapes(parsed.username)}${parsed.password.length === 0 ? "" : `:${canonicalPercentEscapes(parsed.password)}`}@` : ""}${host.includes(":") && !host.startsWith("[") ? `[${host}]` : host}:${port}${canonicalPercentEscapes(parsed.pathname)}${allowQuery ? canonicalPercentEscapes(parsed.search) : ""}`,
|
|
572
|
+
authority: {
|
|
573
|
+
scheme,
|
|
574
|
+
host,
|
|
575
|
+
port
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
/** Canonicalises the complete hidden URL only for the daemon's secret-store factory. */
|
|
580
|
+
function secretWebhookUrlDescriptor(value) {
|
|
581
|
+
const canonical = canonicalUrl(value, true);
|
|
582
|
+
return {
|
|
583
|
+
kind: "secret",
|
|
584
|
+
...canonical.authority,
|
|
585
|
+
sha256: sha256Hex(canonical.value)
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
/** Produces the private stored URL and its public descriptor together; callers return only the descriptor. */
|
|
589
|
+
function canonicalSecretWebhookUrl(value) {
|
|
590
|
+
const canonical = canonicalUrl(value, true);
|
|
591
|
+
return {
|
|
592
|
+
value: canonical.value,
|
|
593
|
+
descriptor: {
|
|
594
|
+
kind: "secret",
|
|
595
|
+
...canonical.authority,
|
|
596
|
+
sha256: sha256Hex(canonical.value)
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function canonicalAddressSet(value) {
|
|
601
|
+
if (!Array.isArray(value) || value.length === 0) return fail("a webhook approvedAddressSet is a non-empty array");
|
|
602
|
+
const addresses = value.map((entry) => text(entry, "a webhook approvedAddressSet entry"));
|
|
603
|
+
const sorted = [...addresses].sort();
|
|
604
|
+
if (new Set(sorted).size !== sorted.length || sorted.some((entry, index) => entry !== addresses[index])) return fail("a webhook approvedAddressSet is sorted and duplicate-free");
|
|
605
|
+
return sorted;
|
|
606
|
+
}
|
|
607
|
+
function canonicalUrlDescriptor(value) {
|
|
608
|
+
const url = record(value, "a webhook URL descriptor");
|
|
609
|
+
if (url.kind === "plain") return {
|
|
610
|
+
kind: "plain",
|
|
611
|
+
value: canonicalUrl(text(url.value, "a plain webhook URL"), false).value
|
|
612
|
+
};
|
|
613
|
+
if (url.kind === "secret") {
|
|
614
|
+
const scheme = text(url.scheme, "a secret webhook scheme");
|
|
615
|
+
if (scheme !== "http" && scheme !== "https") return fail("a secret webhook scheme is http or https");
|
|
616
|
+
const host = text(url.host, "a secret webhook host").toLowerCase();
|
|
617
|
+
const port = positiveInteger(url.port, "a secret webhook port");
|
|
618
|
+
if (port > 65535) return fail("a secret webhook port is at most 65535");
|
|
619
|
+
const authority = canonicalUrl(`${scheme}://${host.includes(":") ? `[${host}]` : host}:${port}/`, false).authority;
|
|
620
|
+
if (authority.scheme !== scheme || authority.host !== host || authority.port !== port) return fail("a secret webhook authority is canonical");
|
|
621
|
+
const sha256 = text(url.sha256, "a secret webhook fingerprint");
|
|
622
|
+
if (!/^[a-f0-9]{64}$/.test(sha256)) return fail("a secret webhook fingerprint is lowercase SHA-256");
|
|
623
|
+
return {
|
|
624
|
+
kind: "secret",
|
|
625
|
+
scheme,
|
|
626
|
+
host,
|
|
627
|
+
port,
|
|
628
|
+
sha256
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return fail("a webhook URL descriptor has a known kind");
|
|
632
|
+
}
|
|
633
|
+
/** The canonical immutable webhook target contains public authority only, never a slot or complete secret URL. */
|
|
634
|
+
function canonicalWebhookTarget(value) {
|
|
635
|
+
const target = record(value, "a webhook target document");
|
|
636
|
+
if (target.kind !== "webhook") return fail("a webhook target document has kind webhook");
|
|
637
|
+
if (target.signing !== "standard-webhooks") return fail("a webhook target uses standard-webhooks signing");
|
|
638
|
+
if (target.ordering !== "strict") return fail("a webhook target uses strict delivery ordering");
|
|
639
|
+
const retryLimit = positiveInteger(target.retryLimit, "a webhook retry limit");
|
|
640
|
+
if (retryLimit > 20) return fail("a webhook retry limit is at most 20");
|
|
641
|
+
if (target.representation !== "plain" && target.representation !== "enveloped") return fail("a webhook representation is plain or enveloped");
|
|
642
|
+
const url = canonicalUrlDescriptor(target.url);
|
|
643
|
+
const approvedAddressSet = canonicalAddressSet(target.approvedAddressSet);
|
|
644
|
+
try {
|
|
645
|
+
assertApprovedAddressSet(approvedAddressSet);
|
|
646
|
+
if (url.kind === "plain" && url.value.startsWith("http:")) validateLocalEndpoint(url.value, approvedAddressSet);
|
|
647
|
+
if (url.kind === "secret" && url.scheme === "http") validateLocalEndpoint(`http://${url.host.includes(":") ? `[${url.host}]` : url.host}:${url.port}/`, approvedAddressSet);
|
|
648
|
+
} catch {
|
|
649
|
+
return fail("a webhook target has an approved network authority");
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
targetId: text(target.targetId, "a webhook target id"),
|
|
653
|
+
version: positiveInteger(target.version, "a webhook target version"),
|
|
654
|
+
kind: "webhook",
|
|
655
|
+
url,
|
|
656
|
+
approvedAddressSet,
|
|
657
|
+
signing: "standard-webhooks",
|
|
658
|
+
ordering: "strict",
|
|
659
|
+
retryLimit,
|
|
660
|
+
representation: target.representation
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
/** Maps an already canonical document to Task 2's closed delivery union without rebuilding delivery bytes. */
|
|
664
|
+
function webhookDeliveryTarget(document) {
|
|
665
|
+
return {
|
|
666
|
+
kind: "webhook",
|
|
667
|
+
targetId: document.targetId,
|
|
668
|
+
targetVersion: document.version,
|
|
669
|
+
representation: document.representation
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
//#endregion
|
|
673
|
+
export { EventDomainError as a, JUDGE_KIND_LOCAL_ENDPOINT as c, isJudgeKind as d, validateLocalEndpoint as f, parseIpLiteral as g, assertApprovedAddressSet as h, webhookDeliveryTarget as i, JUDGE_KIND_TYPESAFE as l, assertAddressAllowed as m, canonicalWebhookTarget as n, JUDGE_KINDS as o, AddressPolicyError as p, secretWebhookUrlDescriptor as r, JUDGE_KIND_LAYA as s, canonicalSecretWebhookUrl as t, assertJudgeKindEnabled as u };
|