@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.31
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/AUTHORING.md +493 -5
- package/CHANGELOG.md +131 -1
- package/README.md +52 -6
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-check.ts +106 -62
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +63 -55
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +78 -82
- package/bin/apifuse-pack-types.ts +583 -0
- package/bin/apifuse-perf.ts +59 -140
- package/bin/apifuse-record.ts +698 -113
- package/bin/apifuse-submit-check.ts +517 -44
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/bin/submit-check-xml.ts +1 -1
- package/dist/auth-turn/index.d.ts +4 -4
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +16 -2
- package/dist/auth.js +76 -18
- package/dist/ceremonies/index.d.ts +9 -1
- package/dist/ceremonies/index.js +117 -29
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +34 -35
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/cli/templates/provider/README.md.tpl +4 -4
- package/dist/config/loader.d.ts +176 -17
- package/dist/config/loader.js +434 -161
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +7 -14
- package/dist/contract-types.d.ts +3 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +6 -6
- package/dist/declaration-validation.d.ts +23 -0
- package/dist/declaration-validation.js +159 -0
- package/dist/define.d.ts +13 -1
- package/dist/define.js +391 -122
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +123 -0
- package/dist/errors.d.ts +19 -1
- package/dist/errors.js +41 -3
- package/dist/fixture-sanitization.d.ts +26 -0
- package/dist/fixture-sanitization.js +216 -0
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +50 -42
- package/dist/index.js +41 -37
- package/dist/lint.d.ts +6 -1
- package/dist/lint.js +370 -18
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +13 -11
- package/dist/provider.js +10 -9
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +3 -1
- package/dist/runtime/auth-flow.js +8 -3
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +138 -40
- package/dist/runtime/cache.d.ts +2 -1
- package/dist/runtime/cache.js +173 -23
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +14 -1
- package/dist/runtime/choice.js +566 -101
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +25 -3
- package/dist/runtime/http.d.ts +3 -2
- package/dist/runtime/http.js +517 -55
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +371 -23
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +96 -0
- package/dist/runtime/native-network.js +1232 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +9 -7
- package/dist/runtime/proxy-nodemaven.d.ts +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-retry-policy.d.ts +2 -2
- package/dist/runtime/proxy-retry-policy.js +2 -2
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +58 -52
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +5 -5
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-shared.d.ts +3 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
- package/dist/runtime/resolver-vendors/bindings.js +40 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +20 -0
- package/dist/runtime/resolver-vendors/browser.js +282 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +368 -0
- package/dist/runtime/resolver-vendors/types.d.ts +83 -0
- package/dist/runtime/resolver-vendors/types.js +69 -0
- package/dist/runtime/resolver.d.ts +59 -0
- package/dist/runtime/resolver.js +705 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +5 -2
- package/dist/runtime/state.js +280 -74
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +30 -5
- package/dist/runtime/stealth.js +523 -259
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +12 -27
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +6 -6
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test-redaction.d.ts +1 -1
- package/dist/server/self-test-redaction.js +1 -1
- package/dist/server/self-test.d.ts +117 -3
- package/dist/server/self-test.js +787 -151
- package/dist/server/serve-implementation.d.ts +210 -0
- package/dist/server/serve-implementation.js +2078 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1143
- package/dist/server/types.d.ts +34 -9
- package/dist/server/types.js +8 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +21 -21
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.d.ts +1 -1
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.js +3 -2
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +488 -28
- package/dist/types.d.ts +566 -19
- package/dist/types.js +1 -0
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +42 -7
- package/src/auth-turn/index.ts +2 -2
- package/src/auth.ts +146 -86
- package/src/ceremonies/index.ts +167 -92
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +42 -35
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/README.md.tpl +4 -4
- package/src/config/loader.ts +667 -289
- package/src/contract-serialization.ts +10 -18
- package/src/contract-types.ts +3 -2
- package/src/contract.ts +14 -28
- package/src/declaration-validation.ts +202 -0
- package/src/define.ts +631 -495
- package/src/dev.ts +4 -9
- package/src/error-resolution.ts +128 -0
- package/src/errors.ts +56 -11
- package/src/fixture-sanitization.ts +247 -0
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +158 -44
- package/src/lint.ts +488 -154
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +66 -11
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +13 -7
- package/src/runtime/browser.ts +252 -207
- package/src/runtime/cache.ts +209 -81
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +758 -197
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +37 -19
- package/src/runtime/http.ts +645 -65
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +530 -67
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1605 -0
- package/src/runtime/ocr.ts +523 -0
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +29 -42
- package/src/runtime/proxy-nodemaven.ts +221 -0
- package/src/runtime/proxy-retry-policy.ts +3 -3
- package/src/runtime/proxy-telemetry.ts +84 -77
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +10 -13
- package/src/runtime/request-options.ts +679 -9
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +18 -0
- package/src/runtime/resolver-shared.ts +17 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +408 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +500 -0
- package/src/runtime/resolver-vendors/types.ts +173 -0
- package/src/runtime/resolver.ts +1060 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +399 -161
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +681 -295
- package/src/runtime/stt.ts +39 -113
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +6 -1
- package/src/server/index.ts +30 -7
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +1030 -180
- package/src/server/serve-implementation.ts +3062 -0
- package/src/server/serve.ts +1 -1781
- package/src/server/types.ts +12 -13
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +27 -33
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +16 -20
- package/src/testing/index.ts +11 -2
- package/src/testing/run.ts +668 -74
- package/src/types.ts +665 -35
- package/src/user-input.ts +118 -0
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { domainToASCII } from "node:url";
|
|
2
|
+
const STRICT_IPV4_PATTERN = /^(0|[1-9]\d{0,2})\.(0|[1-9]\d{0,2})\.(0|[1-9]\d{0,2})\.(0|[1-9]\d{0,2})$/;
|
|
3
|
+
const DECIMAL_COMPONENT_PATTERN = /^\d+$/;
|
|
4
|
+
const HEX_COMPONENT_PATTERN = /^0[xX][\da-fA-F]+$/;
|
|
5
|
+
const IPV6_GROUP_PATTERN = /^[\da-fA-F]{1,4}$/;
|
|
6
|
+
const FORMAT_CONTROL_PATTERN = /\p{Cf}/u;
|
|
7
|
+
const RESERVED_EGRESS_HOST_DELIMITERS = ["/", "\\", "?", "#", "@", "[", "]", " ", "%"];
|
|
8
|
+
const IPV4_MAPPED_PREFIX = Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff);
|
|
9
|
+
const IPV4_COMPATIBLE_MIN = Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2);
|
|
10
|
+
const IPV4_COMPATIBLE_MAX = Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff);
|
|
11
|
+
export function parseStrictIpv4(value) {
|
|
12
|
+
const match = STRICT_IPV4_PATTERN.exec(value);
|
|
13
|
+
if (!match || match[0] !== value)
|
|
14
|
+
return undefined;
|
|
15
|
+
const [, first, second, third, fourth] = match;
|
|
16
|
+
if (first === undefined || second === undefined || third === undefined || fourth === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
const octets = [Number(first), Number(second), Number(third), Number(fourth)];
|
|
19
|
+
if (octets.some((octet) => octet > 255))
|
|
20
|
+
return undefined;
|
|
21
|
+
const [a, b, c, d] = octets;
|
|
22
|
+
if (a === undefined || b === undefined || c === undefined || d === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
return (a * 0x1000000 + b * 0x10000 + c * 0x100 + d) >>> 0;
|
|
25
|
+
}
|
|
26
|
+
function ipv4HexGroups(value) {
|
|
27
|
+
const address = parseStrictIpv4(value);
|
|
28
|
+
if (address === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
return [((address >>> 16) & 0xffff).toString(16), (address & 0xffff).toString(16)];
|
|
31
|
+
}
|
|
32
|
+
/** Parse the RFC 4291 IPv6 text forms accepted at both policy and runtime boundaries. */
|
|
33
|
+
export function parseIpv6(value) {
|
|
34
|
+
if (!value || value.includes("%") || value.includes("[") || value.includes("]"))
|
|
35
|
+
return undefined;
|
|
36
|
+
if (value.indexOf("::") !== value.lastIndexOf("::"))
|
|
37
|
+
return undefined;
|
|
38
|
+
let text = value;
|
|
39
|
+
if (text.includes(".")) {
|
|
40
|
+
const finalColon = text.lastIndexOf(":");
|
|
41
|
+
if (finalColon < 0)
|
|
42
|
+
return undefined;
|
|
43
|
+
const groups = ipv4HexGroups(text.slice(finalColon + 1));
|
|
44
|
+
if (!groups)
|
|
45
|
+
return undefined;
|
|
46
|
+
text = `${text.slice(0, finalColon + 1)}${groups[0]}:${groups[1]}`;
|
|
47
|
+
}
|
|
48
|
+
const compression = text.indexOf("::");
|
|
49
|
+
let groups;
|
|
50
|
+
if (compression >= 0) {
|
|
51
|
+
const leftText = text.slice(0, compression);
|
|
52
|
+
const rightText = text.slice(compression + 2);
|
|
53
|
+
const left = leftText ? leftText.split(":") : [];
|
|
54
|
+
const right = rightText ? rightText.split(":") : [];
|
|
55
|
+
if (left.some((group) => !IPV6_GROUP_PATTERN.test(group)) ||
|
|
56
|
+
right.some((group) => !IPV6_GROUP_PATTERN.test(group)) ||
|
|
57
|
+
left.length + right.length >= 8)
|
|
58
|
+
return undefined;
|
|
59
|
+
groups = [...left, ...Array(8 - left.length - right.length).fill("0"), ...right];
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
groups = text.split(":");
|
|
63
|
+
if (groups.length !== 8 || groups.some((group) => !IPV6_GROUP_PATTERN.test(group)))
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const bytes = new Uint8Array(16);
|
|
67
|
+
for (let index = 0; index < groups.length; index += 1) {
|
|
68
|
+
const group = groups[index];
|
|
69
|
+
if (group === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
const parsed = Number.parseInt(group, 16);
|
|
72
|
+
bytes[index * 2] = parsed >>> 8;
|
|
73
|
+
bytes[index * 2 + 1] = parsed & 0xff;
|
|
74
|
+
}
|
|
75
|
+
return bytes;
|
|
76
|
+
}
|
|
77
|
+
function prefixMatches(address, network, prefix) {
|
|
78
|
+
const wholeBytes = Math.floor(prefix / 8);
|
|
79
|
+
for (let index = 0; index < wholeBytes; index += 1) {
|
|
80
|
+
if (address[index] !== network[index])
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const remainingBits = prefix % 8;
|
|
84
|
+
if (remainingBits === 0)
|
|
85
|
+
return true;
|
|
86
|
+
const mask = (0xff << (8 - remainingBits)) & 0xff;
|
|
87
|
+
return ((address[wholeBytes] ?? 0) & mask) === ((network[wholeBytes] ?? 0) & mask);
|
|
88
|
+
}
|
|
89
|
+
function compareIpv6(left, right) {
|
|
90
|
+
for (let index = 0; index < 16; index += 1) {
|
|
91
|
+
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
92
|
+
if (difference !== 0)
|
|
93
|
+
return difference;
|
|
94
|
+
}
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
function cidrMaximum(network, prefix) {
|
|
98
|
+
const maximum = network.slice();
|
|
99
|
+
for (let bit = prefix; bit < 128; bit += 1) {
|
|
100
|
+
const byteIndex = Math.floor(bit / 8);
|
|
101
|
+
maximum[byteIndex] = (maximum[byteIndex] ?? 0) | (1 << (7 - (bit % 8)));
|
|
102
|
+
}
|
|
103
|
+
return maximum;
|
|
104
|
+
}
|
|
105
|
+
function rangesOverlap(leftMin, leftMax, rightMin, rightMax) {
|
|
106
|
+
return compareIpv6(leftMin, rightMax) <= 0 && compareIpv6(rightMin, leftMax) <= 0;
|
|
107
|
+
}
|
|
108
|
+
function ipv6CidrOverlap(network, prefix) {
|
|
109
|
+
const maximum = cidrMaximum(network, prefix);
|
|
110
|
+
const mappedMin = new Uint8Array(16);
|
|
111
|
+
mappedMin.set(IPV4_MAPPED_PREFIX);
|
|
112
|
+
const mappedMax = mappedMin.slice();
|
|
113
|
+
mappedMax.fill(0xff, 12);
|
|
114
|
+
if (rangesOverlap(network, maximum, mappedMin, mappedMax))
|
|
115
|
+
return "ipv4-mapped";
|
|
116
|
+
if (rangesOverlap(network, maximum, IPV4_COMPATIBLE_MIN, IPV4_COMPATIBLE_MAX))
|
|
117
|
+
return "ipv4-compatible";
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
export function parseIpv4Cidr(value) {
|
|
121
|
+
const separator = value.indexOf("/");
|
|
122
|
+
if (separator <= 0 || separator !== value.lastIndexOf("/"))
|
|
123
|
+
return { ok: false, reason: "malformed" };
|
|
124
|
+
const address = parseStrictIpv4(value.slice(0, separator));
|
|
125
|
+
const prefixText = value.slice(separator + 1);
|
|
126
|
+
const prefix = Number(prefixText);
|
|
127
|
+
if (address === undefined ||
|
|
128
|
+
!Number.isInteger(prefix) ||
|
|
129
|
+
prefix < 0 ||
|
|
130
|
+
prefix > 32 ||
|
|
131
|
+
String(prefix) !== prefixText)
|
|
132
|
+
return { ok: false, reason: "malformed" };
|
|
133
|
+
const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
|
|
134
|
+
const network = (address & mask) >>> 0;
|
|
135
|
+
if (address !== network)
|
|
136
|
+
return { ok: false, reason: "non-canonical-network" };
|
|
137
|
+
return { ok: true, network, prefix };
|
|
138
|
+
}
|
|
139
|
+
export function parseIpv6Cidr(value) {
|
|
140
|
+
const separator = value.indexOf("/");
|
|
141
|
+
if (separator <= 0 || separator !== value.lastIndexOf("/"))
|
|
142
|
+
return { ok: false, reason: "malformed" };
|
|
143
|
+
const address = parseIpv6(value.slice(0, separator));
|
|
144
|
+
const prefixText = value.slice(separator + 1);
|
|
145
|
+
const prefix = Number(prefixText);
|
|
146
|
+
if (address === undefined ||
|
|
147
|
+
!Number.isInteger(prefix) ||
|
|
148
|
+
prefix < 0 ||
|
|
149
|
+
prefix > 128 ||
|
|
150
|
+
String(prefix) !== prefixText)
|
|
151
|
+
return { ok: false, reason: "malformed" };
|
|
152
|
+
const network = address.slice();
|
|
153
|
+
const wholeBytes = Math.floor(prefix / 8);
|
|
154
|
+
const remainingBits = prefix % 8;
|
|
155
|
+
if (remainingBits > 0) {
|
|
156
|
+
const mask = (0xff << (8 - remainingBits)) & 0xff;
|
|
157
|
+
network[wholeBytes] = (network[wholeBytes] ?? 0) & mask;
|
|
158
|
+
}
|
|
159
|
+
network.fill(0, wholeBytes + (remainingBits > 0 ? 1 : 0));
|
|
160
|
+
if (compareIpv6(address, network) !== 0)
|
|
161
|
+
return { ok: false, reason: "non-canonical-network" };
|
|
162
|
+
const overlap = ipv6CidrOverlap(network, prefix);
|
|
163
|
+
if (overlap)
|
|
164
|
+
return { ok: false, reason: "malformed", overlap };
|
|
165
|
+
return { ok: true, network, prefix };
|
|
166
|
+
}
|
|
167
|
+
export function ipv4InCidr(address, cidr) {
|
|
168
|
+
const parsed = parseIpv4Cidr(cidr);
|
|
169
|
+
if (!parsed.ok)
|
|
170
|
+
return false;
|
|
171
|
+
return (parsed.prefix === 0 ||
|
|
172
|
+
address >>> (32 - parsed.prefix) === parsed.network >>> (32 - parsed.prefix));
|
|
173
|
+
}
|
|
174
|
+
export function ipv6InCidr(address, cidr) {
|
|
175
|
+
if (address.length !== 16)
|
|
176
|
+
return false;
|
|
177
|
+
const parsed = parseIpv6Cidr(cidr);
|
|
178
|
+
return parsed.ok && prefixMatches(address, parsed.network, parsed.prefix);
|
|
179
|
+
}
|
|
180
|
+
export function embeddedIpv4FromIpv6(address) {
|
|
181
|
+
if (address.length !== 16)
|
|
182
|
+
return undefined;
|
|
183
|
+
const mapped = IPV4_MAPPED_PREFIX.every((byte, index) => address[index] === byte);
|
|
184
|
+
const compatible = address.slice(0, 12).every((byte) => byte === 0);
|
|
185
|
+
const embedded = ((address[12] ?? 0) * 0x1000000 +
|
|
186
|
+
(address[13] ?? 0) * 0x10000 +
|
|
187
|
+
(address[14] ?? 0) * 0x100 +
|
|
188
|
+
(address[15] ?? 0)) >>>
|
|
189
|
+
0;
|
|
190
|
+
if (mapped || (compatible && embedded !== 0 && embedded !== 1))
|
|
191
|
+
return embedded;
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
export function formatIpv6(address) {
|
|
195
|
+
if (address.length !== 16)
|
|
196
|
+
throw new TypeError("IPv6 addresses must contain exactly 16 bytes");
|
|
197
|
+
const groups = Array.from({ length: 8 }, (_, index) => (((address[index * 2] ?? 0) << 8) | (address[index * 2 + 1] ?? 0)).toString(16));
|
|
198
|
+
let bestStart = -1;
|
|
199
|
+
let bestLength = 0;
|
|
200
|
+
for (let index = 0; index < groups.length;) {
|
|
201
|
+
if (groups[index] !== "0") {
|
|
202
|
+
index += 1;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
let end = index + 1;
|
|
206
|
+
while (end < groups.length && groups[end] === "0")
|
|
207
|
+
end += 1;
|
|
208
|
+
if (end - index > bestLength && end - index >= 2) {
|
|
209
|
+
bestStart = index;
|
|
210
|
+
bestLength = end - index;
|
|
211
|
+
}
|
|
212
|
+
index = end;
|
|
213
|
+
}
|
|
214
|
+
if (bestStart < 0)
|
|
215
|
+
return groups.join(":");
|
|
216
|
+
const left = groups.slice(0, bestStart).join(":");
|
|
217
|
+
const right = groups.slice(bestStart + bestLength).join(":");
|
|
218
|
+
return `${left}::${right}`;
|
|
219
|
+
}
|
|
220
|
+
/** The single family and ambiguity classifier used by policy and runtime matching. */
|
|
221
|
+
export function classifyEgressHost(host) {
|
|
222
|
+
if (hasReservedEgressHostDelimiter(host) ||
|
|
223
|
+
hasEgressHostControlCharacter(host) ||
|
|
224
|
+
/\s/u.test(host))
|
|
225
|
+
return "numeric-ambiguous";
|
|
226
|
+
if (parseStrictIpv4(host) !== undefined)
|
|
227
|
+
return "ipv4";
|
|
228
|
+
const ipv6 = parseIpv6(host);
|
|
229
|
+
if (ipv6)
|
|
230
|
+
return embeddedIpv4FromIpv6(ipv6) === undefined ? "ipv6" : "ipv4-mapped-ipv6";
|
|
231
|
+
if (host.includes(":"))
|
|
232
|
+
return "numeric-ambiguous";
|
|
233
|
+
const labels = host.split(".");
|
|
234
|
+
if (labels.every((label) => DECIMAL_COMPONENT_PATTERN.exec(label)?.[0] === label ||
|
|
235
|
+
HEX_COMPONENT_PATTERN.exec(label)?.[0] === label))
|
|
236
|
+
return "numeric-ambiguous";
|
|
237
|
+
return "dns";
|
|
238
|
+
}
|
|
239
|
+
export function hasReservedEgressHostDelimiter(value) {
|
|
240
|
+
return RESERVED_EGRESS_HOST_DELIMITERS.some((delimiter) => value.includes(delimiter));
|
|
241
|
+
}
|
|
242
|
+
export function hasEgressHostControlCharacter(value) {
|
|
243
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
244
|
+
const code = value.charCodeAt(index);
|
|
245
|
+
if (code <= 31 || code === 127)
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
return FORMAT_CONTROL_PATTERN.test(value);
|
|
249
|
+
}
|
|
250
|
+
export function canonicalizeEgressHost(value) {
|
|
251
|
+
if (typeof value !== "string")
|
|
252
|
+
return { ok: false, reason: "not-string" };
|
|
253
|
+
if (hasReservedEgressHostDelimiter(value))
|
|
254
|
+
return { ok: false, reason: "reserved-delimiter" };
|
|
255
|
+
if (hasEgressHostControlCharacter(value))
|
|
256
|
+
return { ok: false, reason: "control-character" };
|
|
257
|
+
if (/\s/u.test(value))
|
|
258
|
+
return { ok: false, reason: "whitespace" };
|
|
259
|
+
const raw = value.trim();
|
|
260
|
+
if (!raw)
|
|
261
|
+
return { ok: false, reason: "canonicalization-empty" };
|
|
262
|
+
const ipv6 = parseIpv6(raw);
|
|
263
|
+
if (ipv6)
|
|
264
|
+
return { ok: true, host: formatIpv6(ipv6) };
|
|
265
|
+
// Classify the spelling with one optional root-label dot removed before IDNA.
|
|
266
|
+
// This keeps resolver-numeric ASCII forms from being widened into canonical IPv4.
|
|
267
|
+
const numericCandidate = raw.endsWith(".") ? raw.slice(0, -1) : raw;
|
|
268
|
+
const normalizedNumericCandidate = numericCandidate.toLowerCase();
|
|
269
|
+
if (classifyEgressHost(normalizedNumericCandidate) === "numeric-ambiguous")
|
|
270
|
+
return { ok: true, host: normalizedNumericCandidate };
|
|
271
|
+
let ascii;
|
|
272
|
+
try {
|
|
273
|
+
ascii = domainToASCII(raw).toLowerCase().replace(/\.$/, "");
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return { ok: false, reason: "canonicalization-empty" };
|
|
277
|
+
}
|
|
278
|
+
if (!ascii || /^\.+$/.test(ascii))
|
|
279
|
+
return { ok: false, reason: "canonicalization-empty" };
|
|
280
|
+
return { ok: true, host: ascii };
|
|
281
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { NativeTcpPortRange, NativeTcpTlsMode } from "./types.js";
|
|
2
|
+
export type StaticEgressRuleSnapshot = {
|
|
3
|
+
readonly host: string;
|
|
4
|
+
readonly ports: readonly number[];
|
|
5
|
+
readonly tls: NativeTcpTlsMode;
|
|
6
|
+
};
|
|
7
|
+
export type DynamicEgressRuleSnapshot = {
|
|
8
|
+
readonly sourceHost?: string;
|
|
9
|
+
readonly sourceHostSuffixes: readonly string[];
|
|
10
|
+
readonly sourceIpv4Cidrs: readonly string[];
|
|
11
|
+
readonly sourceIpv6Cidrs: readonly string[];
|
|
12
|
+
readonly sourcePorts: readonly number[];
|
|
13
|
+
readonly sourcePortRanges: readonly NativeTcpPortRange[];
|
|
14
|
+
readonly targetHostSuffixes: readonly string[];
|
|
15
|
+
readonly targetIpv4Cidrs: readonly string[];
|
|
16
|
+
readonly targetIpv6Cidrs: readonly string[];
|
|
17
|
+
readonly targetPorts: readonly number[];
|
|
18
|
+
readonly targetPortRanges: readonly NativeTcpPortRange[];
|
|
19
|
+
readonly tls: NativeTcpTlsMode;
|
|
20
|
+
readonly ttlMs?: number;
|
|
21
|
+
readonly maxGrants?: number;
|
|
22
|
+
};
|
|
23
|
+
export type NativeEgressPolicySnapshot = {
|
|
24
|
+
readonly staticRules: readonly StaticEgressRuleSnapshot[];
|
|
25
|
+
readonly dynamicRules: readonly DynamicEgressRuleSnapshot[];
|
|
26
|
+
};
|
|
27
|
+
export declare class NativeEgressPolicyValidationError extends Error {
|
|
28
|
+
constructor(message: string);
|
|
29
|
+
}
|
|
30
|
+
export declare function parseNativeEgressPolicy(value: unknown): NativeEgressPolicySnapshot;
|
|
31
|
+
export declare function validateNativeProviderConfig(value: unknown): void;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { canonicalizeEgressHost, classifyEgressHost, formatIpv6, parseIpv4Cidr, parseIpv6Cidr, } from "./native-address.js";
|
|
2
|
+
const NATIVE_PROVIDER_FIELD_RECORD = {
|
|
3
|
+
network: true,
|
|
4
|
+
};
|
|
5
|
+
const NATIVE_NETWORK_FIELD_RECORD = {
|
|
6
|
+
tcp: true,
|
|
7
|
+
dynamicTcp: true,
|
|
8
|
+
};
|
|
9
|
+
const NATIVE_TCP_RULE_FIELD_RECORD = {
|
|
10
|
+
host: true,
|
|
11
|
+
ports: true,
|
|
12
|
+
tls: true,
|
|
13
|
+
};
|
|
14
|
+
const NATIVE_DYNAMIC_TCP_RULE_FIELD_RECORD = {
|
|
15
|
+
sourceHost: true,
|
|
16
|
+
sourceHostSuffixes: true,
|
|
17
|
+
sourceIpv4Cidrs: true,
|
|
18
|
+
sourceIpv6Cidrs: true,
|
|
19
|
+
sourcePorts: true,
|
|
20
|
+
sourcePortRanges: true,
|
|
21
|
+
targetHostSuffixes: true,
|
|
22
|
+
targetIpv4Cidrs: true,
|
|
23
|
+
targetIpv6Cidrs: true,
|
|
24
|
+
targetPorts: true,
|
|
25
|
+
targetPortRanges: true,
|
|
26
|
+
tls: true,
|
|
27
|
+
ttlMs: true,
|
|
28
|
+
maxGrants: true,
|
|
29
|
+
};
|
|
30
|
+
const NATIVE_TCP_PORT_RANGE_FIELD_RECORD = {
|
|
31
|
+
start: true,
|
|
32
|
+
end: true,
|
|
33
|
+
};
|
|
34
|
+
const NATIVE_PROVIDER_FIELDS = Object.keys(NATIVE_PROVIDER_FIELD_RECORD);
|
|
35
|
+
const NATIVE_NETWORK_FIELDS = Object.keys(NATIVE_NETWORK_FIELD_RECORD);
|
|
36
|
+
const NATIVE_TCP_RULE_FIELDS = Object.keys(NATIVE_TCP_RULE_FIELD_RECORD);
|
|
37
|
+
const NATIVE_DYNAMIC_TCP_RULE_FIELDS = Object.keys(NATIVE_DYNAMIC_TCP_RULE_FIELD_RECORD);
|
|
38
|
+
const NATIVE_TCP_PORT_RANGE_FIELDS = Object.keys(NATIVE_TCP_PORT_RANGE_FIELD_RECORD);
|
|
39
|
+
export class NativeEgressPolicyValidationError extends Error {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "NativeEgressPolicyValidationError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function fail(message) {
|
|
46
|
+
throw new NativeEgressPolicyValidationError(message);
|
|
47
|
+
}
|
|
48
|
+
function dataRecord(value, fieldPath, allowed) {
|
|
49
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
50
|
+
fail(`${fieldPath} must be an object`);
|
|
51
|
+
const prototype = Reflect.getPrototypeOf(value);
|
|
52
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
53
|
+
fail(`${fieldPath} must be a plain object`);
|
|
54
|
+
const record = {};
|
|
55
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
56
|
+
if (typeof key !== "string")
|
|
57
|
+
fail(`${fieldPath} must not contain symbol fields`);
|
|
58
|
+
if (!allowed.includes(key))
|
|
59
|
+
fail(`Unknown field ${fieldPath}.${key}`);
|
|
60
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
|
|
61
|
+
if (!descriptor || !("value" in descriptor))
|
|
62
|
+
fail(`${fieldPath}.${key} must be a data field`);
|
|
63
|
+
record[key] = descriptor.value;
|
|
64
|
+
}
|
|
65
|
+
return record;
|
|
66
|
+
}
|
|
67
|
+
function dataArray(value, fieldPath) {
|
|
68
|
+
if (!Array.isArray(value))
|
|
69
|
+
fail(`${fieldPath} must be an array`);
|
|
70
|
+
const result = [];
|
|
71
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
72
|
+
if (key === "length")
|
|
73
|
+
continue;
|
|
74
|
+
if (typeof key !== "string" || !/^(?:0|[1-9]\d*)$/.test(key))
|
|
75
|
+
fail(`${fieldPath} must not contain non-index fields`);
|
|
76
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
|
|
77
|
+
if (!descriptor || !("value" in descriptor))
|
|
78
|
+
fail(`${fieldPath}[${key}] must be a data field`);
|
|
79
|
+
result[Number(key)] = descriptor.value;
|
|
80
|
+
}
|
|
81
|
+
if (result.length !== value.length)
|
|
82
|
+
fail(`${fieldPath} must not be sparse`);
|
|
83
|
+
for (let index = 0; index < result.length; index += 1) {
|
|
84
|
+
if (!(index in result))
|
|
85
|
+
fail(`${fieldPath} must not be sparse`);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
function host(value, fieldPath, suffix = false) {
|
|
90
|
+
if (typeof value === "string" && value.includes("*"))
|
|
91
|
+
fail(`${fieldPath} must be an exact ${suffix ? "DNS suffix" : "hostname"}, not a wildcard`);
|
|
92
|
+
const canonical = canonicalizeEgressHost(value);
|
|
93
|
+
if (!canonical.ok)
|
|
94
|
+
fail(`${fieldPath} must be a non-empty hostname`);
|
|
95
|
+
const kind = classifyEgressHost(canonical.host);
|
|
96
|
+
if (kind === "numeric-ambiguous")
|
|
97
|
+
fail(`${fieldPath} value is an unresolvable numeric-ambiguous spelling`);
|
|
98
|
+
if (suffix && kind !== "dns") {
|
|
99
|
+
const family = kind === "ipv4" ? "IPv4" : "IPv6";
|
|
100
|
+
fail(`${fieldPath} must be a DNS suffix, not an ${family} literal`);
|
|
101
|
+
}
|
|
102
|
+
return canonical.host;
|
|
103
|
+
}
|
|
104
|
+
function port(value, fieldPath) {
|
|
105
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > 65_535)
|
|
106
|
+
fail(`${fieldPath} must be an integer from 1 to 65535`);
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
function ports(value, fieldPath) {
|
|
110
|
+
return dataArray(value, fieldPath).map((value, index) => port(value, `${fieldPath}[${index}]`));
|
|
111
|
+
}
|
|
112
|
+
function hostSuffixes(value, fieldPath) {
|
|
113
|
+
return dataArray(value, fieldPath).map((value, index) => host(value, `${fieldPath}[${index}]`, true));
|
|
114
|
+
}
|
|
115
|
+
function ipv4Cidrs(value, fieldPath) {
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
return dataArray(value, fieldPath).map((value, index) => {
|
|
118
|
+
const cidrPath = `${fieldPath}[${index}]`;
|
|
119
|
+
if (typeof value !== "string")
|
|
120
|
+
fail(`${cidrPath} must be an IPv4 CIDR in a.b.c.d/nn form`);
|
|
121
|
+
const parsed = parseIpv4Cidr(value);
|
|
122
|
+
if (!parsed.ok) {
|
|
123
|
+
if (parsed.reason === "non-canonical-network")
|
|
124
|
+
fail(`${cidrPath} must use the canonical network address with no host bits set`);
|
|
125
|
+
fail(`${cidrPath} must be an IPv4 CIDR in a.b.c.d/nn form`);
|
|
126
|
+
}
|
|
127
|
+
const duplicateKey = `${parsed.network}/${parsed.prefix}`;
|
|
128
|
+
if (seen.has(duplicateKey))
|
|
129
|
+
fail(`${fieldPath} must not contain duplicate CIDRs`);
|
|
130
|
+
seen.add(duplicateKey);
|
|
131
|
+
return value;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function ipv6Cidrs(value, fieldPath) {
|
|
135
|
+
const seen = new Set();
|
|
136
|
+
return dataArray(value, fieldPath).map((value, index) => {
|
|
137
|
+
const cidrPath = `${fieldPath}[${index}]`;
|
|
138
|
+
if (typeof value !== "string")
|
|
139
|
+
fail(`${cidrPath} must be an IPv6 CIDR in address/nn form`);
|
|
140
|
+
const parsed = parseIpv6Cidr(value);
|
|
141
|
+
if (!parsed.ok) {
|
|
142
|
+
if (parsed.reason === "non-canonical-network")
|
|
143
|
+
fail(`${cidrPath} must use the canonical network address with no host bits set`);
|
|
144
|
+
if (parsed.overlap === "ipv4-mapped")
|
|
145
|
+
fail(`${cidrPath} overlaps IPv4-mapped ::ffff:0:0/96 address space`);
|
|
146
|
+
if (parsed.overlap === "ipv4-compatible")
|
|
147
|
+
fail(`${cidrPath} overlaps IPv4-compatible ::/96 address space`);
|
|
148
|
+
fail(`${cidrPath} must be an IPv6 CIDR in address/nn form`);
|
|
149
|
+
}
|
|
150
|
+
const duplicateKey = `${formatIpv6(parsed.network)}/${parsed.prefix}`;
|
|
151
|
+
if (seen.has(duplicateKey))
|
|
152
|
+
fail(`${fieldPath} must not contain duplicate CIDRs`);
|
|
153
|
+
seen.add(duplicateKey);
|
|
154
|
+
return value;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function ranges(value, fieldPath) {
|
|
158
|
+
return dataArray(value, fieldPath).map((value, index) => {
|
|
159
|
+
const rangePath = `${fieldPath}[${index}]`;
|
|
160
|
+
const record = dataRecord(value, rangePath, NATIVE_TCP_PORT_RANGE_FIELDS);
|
|
161
|
+
const start = port(record.start, `${rangePath}.start`);
|
|
162
|
+
const end = port(record.end, `${rangePath}.end`);
|
|
163
|
+
if (start > end)
|
|
164
|
+
fail(`${rangePath}.start must not exceed end`);
|
|
165
|
+
return { start, end };
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function tls(value, fieldPath) {
|
|
169
|
+
if (value !== "required" && value !== "allowed" && value !== "disabled")
|
|
170
|
+
fail(`${fieldPath} must be required, allowed, or disabled`);
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
function positiveInteger(value, fieldPath) {
|
|
174
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0)
|
|
175
|
+
fail(`${fieldPath} must be a positive integer`);
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
export function parseNativeEgressPolicy(value) {
|
|
179
|
+
try {
|
|
180
|
+
const policy = dataRecord(value, "native.network", NATIVE_NETWORK_FIELDS);
|
|
181
|
+
const staticRules = policy.tcp === undefined
|
|
182
|
+
? []
|
|
183
|
+
: dataArray(policy.tcp, "native.network.tcp").map((value, index) => {
|
|
184
|
+
const fieldPath = `native.network.tcp[${index}]`;
|
|
185
|
+
const rule = dataRecord(value, fieldPath, NATIVE_TCP_RULE_FIELDS);
|
|
186
|
+
const declaredPorts = ports(rule.ports, `${fieldPath}.ports`);
|
|
187
|
+
if (declaredPorts.length === 0)
|
|
188
|
+
fail(`${fieldPath}.ports must not be empty`);
|
|
189
|
+
return {
|
|
190
|
+
host: host(rule.host, `${fieldPath}.host`),
|
|
191
|
+
ports: declaredPorts,
|
|
192
|
+
tls: tls(rule.tls, `${fieldPath}.tls`),
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
const dynamicRules = policy.dynamicTcp === undefined
|
|
196
|
+
? []
|
|
197
|
+
: dataArray(policy.dynamicTcp, "native.network.dynamicTcp").map((value, index) => {
|
|
198
|
+
const fieldPath = `native.network.dynamicTcp[${index}]`;
|
|
199
|
+
const rule = dataRecord(value, fieldPath, NATIVE_DYNAMIC_TCP_RULE_FIELDS);
|
|
200
|
+
const sourceHost = rule.sourceHost === undefined
|
|
201
|
+
? undefined
|
|
202
|
+
: host(rule.sourceHost, `${fieldPath}.sourceHost`);
|
|
203
|
+
const sourceHostSuffixes = rule.sourceHostSuffixes === undefined
|
|
204
|
+
? []
|
|
205
|
+
: hostSuffixes(rule.sourceHostSuffixes, `${fieldPath}.sourceHostSuffixes`);
|
|
206
|
+
const sourceIpv4Cidrs = rule.sourceIpv4Cidrs === undefined
|
|
207
|
+
? []
|
|
208
|
+
: ipv4Cidrs(rule.sourceIpv4Cidrs, `${fieldPath}.sourceIpv4Cidrs`);
|
|
209
|
+
const sourceIpv6Cidrs = rule.sourceIpv6Cidrs === undefined
|
|
210
|
+
? []
|
|
211
|
+
: ipv6Cidrs(rule.sourceIpv6Cidrs, `${fieldPath}.sourceIpv6Cidrs`);
|
|
212
|
+
if (sourceHost === undefined &&
|
|
213
|
+
sourceHostSuffixes.length === 0 &&
|
|
214
|
+
sourceIpv4Cidrs.length === 0 &&
|
|
215
|
+
sourceIpv6Cidrs.length === 0)
|
|
216
|
+
fail(`${fieldPath} must declare sourceHost or a non-empty sourceHostSuffixes, sourceIpv4Cidrs, or sourceIpv6Cidrs list`);
|
|
217
|
+
const sourcePorts = rule.sourcePorts === undefined
|
|
218
|
+
? []
|
|
219
|
+
: ports(rule.sourcePorts, `${fieldPath}.sourcePorts`);
|
|
220
|
+
const sourcePortRanges = rule.sourcePortRanges === undefined
|
|
221
|
+
? []
|
|
222
|
+
: ranges(rule.sourcePortRanges, `${fieldPath}.sourcePortRanges`);
|
|
223
|
+
if (sourcePorts.length === 0 && sourcePortRanges.length === 0)
|
|
224
|
+
fail(`${fieldPath} must declare a non-empty sourcePorts or sourcePortRanges list`);
|
|
225
|
+
const targetHostSuffixes = rule.targetHostSuffixes === undefined
|
|
226
|
+
? []
|
|
227
|
+
: hostSuffixes(rule.targetHostSuffixes, `${fieldPath}.targetHostSuffixes`);
|
|
228
|
+
const targetIpv4Cidrs = rule.targetIpv4Cidrs === undefined
|
|
229
|
+
? []
|
|
230
|
+
: ipv4Cidrs(rule.targetIpv4Cidrs, `${fieldPath}.targetIpv4Cidrs`);
|
|
231
|
+
const targetIpv6Cidrs = rule.targetIpv6Cidrs === undefined
|
|
232
|
+
? []
|
|
233
|
+
: ipv6Cidrs(rule.targetIpv6Cidrs, `${fieldPath}.targetIpv6Cidrs`);
|
|
234
|
+
if (targetHostSuffixes.length === 0 &&
|
|
235
|
+
targetIpv4Cidrs.length === 0 &&
|
|
236
|
+
targetIpv6Cidrs.length === 0)
|
|
237
|
+
fail(`${fieldPath} must declare a non-empty targetHostSuffixes, targetIpv4Cidrs, or targetIpv6Cidrs list`);
|
|
238
|
+
const targetPorts = rule.targetPorts === undefined
|
|
239
|
+
? []
|
|
240
|
+
: ports(rule.targetPorts, `${fieldPath}.targetPorts`);
|
|
241
|
+
const targetPortRanges = rule.targetPortRanges === undefined
|
|
242
|
+
? []
|
|
243
|
+
: ranges(rule.targetPortRanges, `${fieldPath}.targetPortRanges`);
|
|
244
|
+
if (targetPorts.length === 0 && targetPortRanges.length === 0)
|
|
245
|
+
fail(`${fieldPath} must declare a non-empty targetPorts or targetPortRanges list`);
|
|
246
|
+
return {
|
|
247
|
+
...(sourceHost === undefined ? {} : { sourceHost }),
|
|
248
|
+
sourceHostSuffixes,
|
|
249
|
+
sourceIpv4Cidrs,
|
|
250
|
+
sourceIpv6Cidrs,
|
|
251
|
+
sourcePorts,
|
|
252
|
+
sourcePortRanges,
|
|
253
|
+
targetHostSuffixes,
|
|
254
|
+
targetIpv4Cidrs,
|
|
255
|
+
targetIpv6Cidrs,
|
|
256
|
+
targetPorts,
|
|
257
|
+
targetPortRanges,
|
|
258
|
+
tls: tls(rule.tls, `${fieldPath}.tls`),
|
|
259
|
+
...(rule.ttlMs === undefined
|
|
260
|
+
? {}
|
|
261
|
+
: { ttlMs: positiveInteger(rule.ttlMs, `${fieldPath}.ttlMs`) }),
|
|
262
|
+
...(rule.maxGrants === undefined
|
|
263
|
+
? {}
|
|
264
|
+
: { maxGrants: positiveInteger(rule.maxGrants, `${fieldPath}.maxGrants`) }),
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
return { staticRules, dynamicRules };
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
if (error instanceof NativeEgressPolicyValidationError)
|
|
271
|
+
throw error;
|
|
272
|
+
throw new NativeEgressPolicyValidationError("Native egress policy could not be inspected safely");
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
export function validateNativeProviderConfig(value) {
|
|
276
|
+
if (value === undefined)
|
|
277
|
+
return;
|
|
278
|
+
try {
|
|
279
|
+
const native = dataRecord(value, "native", NATIVE_PROVIDER_FIELDS);
|
|
280
|
+
if (native.network !== undefined)
|
|
281
|
+
parseNativeEgressPolicy(native.network);
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
if (error instanceof NativeEgressPolicyValidationError)
|
|
285
|
+
throw error;
|
|
286
|
+
throw new NativeEgressPolicyValidationError("Native provider config could not be inspected safely");
|
|
287
|
+
}
|
|
288
|
+
}
|
package/dist/observability.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export declare const PROVIDER_OBSERVABILITY_TAXONOMY_VERSION = "2026-
|
|
2
|
-
export declare const PROVIDER_ERROR_CATEGORIES: readonly ["ok", "timeout", "network", "upstream_http", "upstream_rate_limited", "upstream_auth", "upstream_schema_drift", "proxy_pool", "anti_bot_blocked", "credential_expired", "credential_unavailable", "input_validation", "output_validation", "provider_error", "internal_error", "unclassified"];
|
|
1
|
+
export declare const PROVIDER_OBSERVABILITY_TAXONOMY_VERSION = "2026-08-07";
|
|
2
|
+
export declare const PROVIDER_ERROR_CATEGORIES: readonly ["ok", "timeout", "network", "upstream_http", "upstream_rate_limited", "upstream_auth", "upstream_rejected", "upstream_schema_drift", "proxy_pool", "anti_bot_blocked", "credential_expired", "credential_unavailable", "input_validation", "output_validation", "provider_error", "internal_error", "dependency_unavailable", "unsupported_transport", "client_cancelled", "unclassified"];
|
|
3
3
|
export type ProviderErrorCategory = (typeof PROVIDER_ERROR_CATEGORIES)[number];
|
|
4
4
|
export declare function categoryForStatus(status: number): ProviderErrorCategory;
|
|
5
5
|
export declare function isRetryableCategory(category: ProviderErrorCategory): boolean;
|
|
6
|
+
export declare const PROVIDER_ERROR_SOURCES: readonly ["client", "upstream_rule", "upstream_failure", "apifuse"];
|
|
7
|
+
export type ProviderErrorSource = (typeof PROVIDER_ERROR_SOURCES)[number];
|
|
8
|
+
export declare function sourceForCategory(category: ProviderErrorCategory): ProviderErrorSource;
|