@fgv/ts-extras 5.1.0-46 → 5.1.0-48
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 +1 -1
- package/README.md +90 -0
- package/dist/index.browser.js +3 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/packlets/safer-fetch/addressClassification.js +438 -0
- package/dist/packlets/safer-fetch/addressClassification.js.map +1 -0
- package/dist/packlets/safer-fetch/addressPolicy.js +119 -0
- package/dist/packlets/safer-fetch/addressPolicy.js.map +1 -0
- package/dist/packlets/safer-fetch/contentType.js +120 -0
- package/dist/packlets/safer-fetch/contentType.js.map +1 -0
- package/dist/packlets/safer-fetch/deadline.js +254 -0
- package/dist/packlets/safer-fetch/deadline.js.map +1 -0
- package/dist/packlets/safer-fetch/defaults.js +131 -0
- package/dist/packlets/safer-fetch/defaults.js.map +1 -0
- package/dist/packlets/safer-fetch/failureReason.js +21 -0
- package/dist/packlets/safer-fetch/failureReason.js.map +1 -0
- package/dist/packlets/safer-fetch/guards.js +148 -0
- package/dist/packlets/safer-fetch/guards.js.map +1 -0
- package/dist/packlets/safer-fetch/index.browser.js +58 -0
- package/dist/packlets/safer-fetch/index.browser.js.map +1 -0
- package/dist/packlets/safer-fetch/index.js +52 -0
- package/dist/packlets/safer-fetch/index.js.map +1 -0
- package/dist/packlets/safer-fetch/model.js +21 -0
- package/dist/packlets/safer-fetch/model.js.map +1 -0
- package/dist/packlets/safer-fetch/nodeAddressGuard.js +191 -0
- package/dist/packlets/safer-fetch/nodeAddressGuard.js.map +1 -0
- package/dist/packlets/safer-fetch/redirect.js +93 -0
- package/dist/packlets/safer-fetch/redirect.js.map +1 -0
- package/dist/packlets/safer-fetch/retry.js +188 -0
- package/dist/packlets/safer-fetch/retry.js.map +1 -0
- package/dist/packlets/safer-fetch/saferFetch.js +755 -0
- package/dist/packlets/safer-fetch/saferFetch.js.map +1 -0
- package/dist/packlets/safer-fetch/transport.js +50 -0
- package/dist/packlets/safer-fetch/transport.js.map +1 -0
- package/dist/ts-extras.d.ts +1337 -0
- package/lib/index.browser.d.ts +2 -1
- package/lib/index.browser.d.ts.map +1 -1
- package/lib/index.browser.js +4 -1
- package/lib/index.browser.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -1
- package/lib/index.js.map +1 -1
- package/lib/packlets/safer-fetch/addressClassification.d.ts +144 -0
- package/lib/packlets/safer-fetch/addressClassification.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/addressClassification.js +441 -0
- package/lib/packlets/safer-fetch/addressClassification.js.map +1 -0
- package/lib/packlets/safer-fetch/addressPolicy.d.ts +113 -0
- package/lib/packlets/safer-fetch/addressPolicy.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/addressPolicy.js +123 -0
- package/lib/packlets/safer-fetch/addressPolicy.js.map +1 -0
- package/lib/packlets/safer-fetch/contentType.d.ts +51 -0
- package/lib/packlets/safer-fetch/contentType.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/contentType.js +127 -0
- package/lib/packlets/safer-fetch/contentType.js.map +1 -0
- package/lib/packlets/safer-fetch/deadline.d.ts +138 -0
- package/lib/packlets/safer-fetch/deadline.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/deadline.js +258 -0
- package/lib/packlets/safer-fetch/deadline.js.map +1 -0
- package/lib/packlets/safer-fetch/defaults.d.ts +108 -0
- package/lib/packlets/safer-fetch/defaults.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/defaults.js +134 -0
- package/lib/packlets/safer-fetch/defaults.js.map +1 -0
- package/lib/packlets/safer-fetch/failureReason.d.ts +148 -0
- package/lib/packlets/safer-fetch/failureReason.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/failureReason.js +22 -0
- package/lib/packlets/safer-fetch/failureReason.js.map +1 -0
- package/lib/packlets/safer-fetch/guards.d.ts +62 -0
- package/lib/packlets/safer-fetch/guards.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/guards.js +153 -0
- package/lib/packlets/safer-fetch/guards.js.map +1 -0
- package/lib/packlets/safer-fetch/index.browser.d.ts +33 -0
- package/lib/packlets/safer-fetch/index.browser.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/index.browser.js +82 -0
- package/lib/packlets/safer-fetch/index.browser.js.map +1 -0
- package/lib/packlets/safer-fetch/index.d.ts +27 -0
- package/lib/packlets/safer-fetch/index.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/index.js +78 -0
- package/lib/packlets/safer-fetch/index.js.map +1 -0
- package/lib/packlets/safer-fetch/model.d.ts +373 -0
- package/lib/packlets/safer-fetch/model.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/model.js +22 -0
- package/lib/packlets/safer-fetch/model.js.map +1 -0
- package/lib/packlets/safer-fetch/nodeAddressGuard.d.ts +129 -0
- package/lib/packlets/safer-fetch/nodeAddressGuard.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/nodeAddressGuard.js +196 -0
- package/lib/packlets/safer-fetch/nodeAddressGuard.js.map +1 -0
- package/lib/packlets/safer-fetch/redirect.d.ts +62 -0
- package/lib/packlets/safer-fetch/redirect.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/redirect.js +98 -0
- package/lib/packlets/safer-fetch/redirect.js.map +1 -0
- package/lib/packlets/safer-fetch/retry.d.ts +162 -0
- package/lib/packlets/safer-fetch/retry.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/retry.js +197 -0
- package/lib/packlets/safer-fetch/retry.js.map +1 -0
- package/lib/packlets/safer-fetch/saferFetch.d.ts +108 -0
- package/lib/packlets/safer-fetch/saferFetch.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/saferFetch.js +760 -0
- package/lib/packlets/safer-fetch/saferFetch.js.map +1 -0
- package/lib/packlets/safer-fetch/transport.d.ts +18 -0
- package/lib/packlets/safer-fetch/transport.d.ts.map +1 -0
- package/lib/packlets/safer-fetch/transport.js +53 -0
- package/lib/packlets/safer-fetch/transport.js.map +1 -0
- package/package.json +17 -7
package/dist/ts-extras.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Brand } from '@fgv/ts-utils';
|
|
|
2
2
|
import { Conversion } from '@fgv/ts-utils';
|
|
3
3
|
import { Converter } from '@fgv/ts-utils';
|
|
4
4
|
import { DateTime } from 'luxon';
|
|
5
|
+
import { DetailedResult } from '@fgv/ts-utils';
|
|
5
6
|
import { FileTree } from '@fgv/ts-json-base';
|
|
6
7
|
import { Hash as Hash_2 } from '@fgv/ts-utils';
|
|
7
8
|
import { JsonObject } from '@fgv/ts-json-base';
|
|
@@ -12,6 +13,54 @@ import { Result } from '@fgv/ts-utils';
|
|
|
12
13
|
import { Uuid } from '@fgv/ts-utils';
|
|
13
14
|
import { Validator } from '@fgv/ts-utils';
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The security-relevant classification of an IP address.
|
|
18
|
+
*
|
|
19
|
+
* `'public'` is the only classification that describes an address which is
|
|
20
|
+
* globally routable on the public internet; every other value names a
|
|
21
|
+
* special-purpose range that a server-side fetch reaching untrusted input
|
|
22
|
+
* should not be able to reach by default.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
declare type AddressClassification =
|
|
27
|
+
/** Globally routable unicast — the only classification a default guard permits. */
|
|
28
|
+
'public'
|
|
29
|
+
/** `0.0.0.0/8` and `::` — "this host on this network"; routes to localhost on Linux. */
|
|
30
|
+
| 'unspecified'
|
|
31
|
+
/** `127.0.0.0/8` and `::1`. */
|
|
32
|
+
| 'loopback'
|
|
33
|
+
/**
|
|
34
|
+
* `169.254.0.0/16` and `fe80::/10`. The IPv4 range contains the cloud
|
|
35
|
+
* instance-metadata endpoint (`169.254.169.254`) and is the single
|
|
36
|
+
* highest-value SSRF target.
|
|
37
|
+
*/
|
|
38
|
+
| 'link-local'
|
|
39
|
+
/** RFC 1918 — `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`. */
|
|
40
|
+
| 'private'
|
|
41
|
+
/** RFC 4193 IPv6 unique-local — `fc00::/7`. */
|
|
42
|
+
| 'unique-local'
|
|
43
|
+
/** RFC 6598 carrier-grade NAT — `100.64.0.0/10`. Frequently carrier or container internal. */
|
|
44
|
+
| 'carrier-grade-nat'
|
|
45
|
+
/** RFC 2544 benchmarking — `198.18.0.0/15`. */
|
|
46
|
+
| 'benchmarking'
|
|
47
|
+
/** Documentation ranges — `192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24`, `2001:db8::/32`. */
|
|
48
|
+
| 'documentation'
|
|
49
|
+
/** IETF protocol assignments — `192.0.0.0/24` and `2001::/23`. */
|
|
50
|
+
| 'protocol-assignment'
|
|
51
|
+
/** `224.0.0.0/4` and `ff00::/8`. */
|
|
52
|
+
| 'multicast'
|
|
53
|
+
/** `255.255.255.255/32`. */
|
|
54
|
+
| 'broadcast'
|
|
55
|
+
/** Every other special-purpose or future-use range (e.g. `240.0.0.0/4`, `fec0::/10`). */
|
|
56
|
+
| 'reserved';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The address family of a parsed IP address literal.
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
declare type AddressFamily = 'ipv4' | 'ipv6';
|
|
63
|
+
|
|
15
64
|
/**
|
|
16
65
|
* Key size in bytes for AES-256.
|
|
17
66
|
* @public
|
|
@@ -418,12 +467,89 @@ declare const allModelCapabilities: ReadonlyArray<AiModelCapability>;
|
|
|
418
467
|
*/
|
|
419
468
|
declare const allModelSpecKeys: ReadonlyArray<ModelSpecKey>;
|
|
420
469
|
|
|
470
|
+
/**
|
|
471
|
+
* An address guard that permits every address.
|
|
472
|
+
*
|
|
473
|
+
* @remarks
|
|
474
|
+
* **This is the absence of the SSRF guarantee, given a name.** It performs no address
|
|
475
|
+
* classification, no DNS resolution, and no scheme or port narrowing beyond the core's
|
|
476
|
+
* refusal of non-`http(s)` schemes. A call site using it is reachable at any address the
|
|
477
|
+
* process can route to, including cloud metadata endpoints, loopback admin ports, and
|
|
478
|
+
* RFC-1918 hosts.
|
|
479
|
+
*
|
|
480
|
+
* It ships anyway, for two reasons. It is the honest and only possible choice in a browser,
|
|
481
|
+
* where neither DNS resolution nor redirect interposition exists. And omitting it would not
|
|
482
|
+
* make anyone safer — it would make consumers hand-roll something worse. It is deliberately
|
|
483
|
+
* named to be uncomfortable in review and to grep as a distinct posture.
|
|
484
|
+
*
|
|
485
|
+
* This is the **guard-layer** factory: it returns an {@link SaferFetch.IAddressGuard}, which is
|
|
486
|
+
* what an entry point's `addressGuard` option takes. A pure address *policy* — the synchronous,
|
|
487
|
+
* address-list-in classifier a resolving guard delegates to — is a different layer and needs a
|
|
488
|
+
* distinct name.
|
|
489
|
+
* @public
|
|
490
|
+
*/
|
|
491
|
+
declare function allowAnyAddress(): IAddressGuard;
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Creates a policy that permits every address, including loopback, link-local
|
|
495
|
+
* and private ones.
|
|
496
|
+
*
|
|
497
|
+
* **This policy provides no protection whatsoever.** It classifies nothing,
|
|
498
|
+
* rejects nothing, and never fails — not even for an empty address list or an
|
|
499
|
+
* address that is not a well-formed literal. It exists so that choosing to go
|
|
500
|
+
* without an address guarantee is a deliberate, named, greppable act at the
|
|
501
|
+
* call site rather than something reachable by omission, and so that tests and
|
|
502
|
+
* genuinely trusted-input paths do not hand-roll something worse.
|
|
503
|
+
*
|
|
504
|
+
* It is the only correct choice in a browser, where neither name resolution nor
|
|
505
|
+
* redirect interposition is available and no address guarantee is possible.
|
|
506
|
+
*
|
|
507
|
+
* @returns the policy.
|
|
508
|
+
* @public
|
|
509
|
+
*/
|
|
510
|
+
declare function allowAnyAddressPolicy(): IAddressPolicy;
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* A response-headers guard that accepts only the listed media types.
|
|
514
|
+
*
|
|
515
|
+
* @remarks
|
|
516
|
+
* Rejecting on `Content-Type` is strictly cheaper than capping mid-read — a header comparison
|
|
517
|
+
* instead of a partial body transfer — so a consumer ingesting URLs wants it on every call. It
|
|
518
|
+
* is also what keeps an HTML error page served with a `200` from being parsed as JSON twenty
|
|
519
|
+
* frames away from where it went wrong.
|
|
520
|
+
*
|
|
521
|
+
* Entries are media types, optionally with a wildcard subtype (`text/*`); a wildcard type
|
|
522
|
+
* requires a wildcard subtype. Matching is case-insensitive and ignores parameters, so
|
|
523
|
+
* `text/html` accepts `text/html; charset=utf-8`. A response with **no** `Content-Type` is
|
|
524
|
+
* rejected: an untyped response has not satisfied a content-type allowlist.
|
|
525
|
+
*
|
|
526
|
+
* Construction is fallible because a malformed entry in a security-adjacent allowlist must be
|
|
527
|
+
* reported to its author rather than silently compiled into a pattern that never matches.
|
|
528
|
+
*
|
|
529
|
+
* @param types - Accepted media types. Must be non-empty.
|
|
530
|
+
* @public
|
|
531
|
+
*/
|
|
532
|
+
declare function allowContentTypes(types: ReadonlyArray<string>): Result<IResponseHeadersGuard>;
|
|
533
|
+
|
|
421
534
|
/**
|
|
422
535
|
* All valid provider ID values, in the same order as the registry.
|
|
423
536
|
* @public
|
|
424
537
|
*/
|
|
425
538
|
declare const allProviderIds: ReadonlyArray<AiProviderId>;
|
|
426
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Header names dropped on every cross-origin redirect hop, whatever the caller configured.
|
|
542
|
+
*
|
|
543
|
+
* @remarks
|
|
544
|
+
* Turning on manual redirects makes this primitive responsible for a rule the platform was
|
|
545
|
+
* applying for free: browsers and `curl` both strip credential headers when a redirect leaves
|
|
546
|
+
* the origin, and a hand-rolled loop that replays them hands `Authorization: Bearer …` to
|
|
547
|
+
* whatever host the redirect names. Callers add their own names with
|
|
548
|
+
* `ISaferFetchOptions.sensitiveHeaders`; these three are not removable.
|
|
549
|
+
* @public
|
|
550
|
+
*/
|
|
551
|
+
declare const ALWAYS_STRIPPED_HEADERS: ReadonlyArray<string>;
|
|
552
|
+
|
|
427
553
|
/**
|
|
428
554
|
* Maps Anthropic effort level to the `thinking.budget_tokens` integer that the
|
|
429
555
|
* Anthropic API requires when `thinking.type === 'enabled'`.
|
|
@@ -499,6 +625,85 @@ declare function base64UrlNoPadDecode(encoded: string): Result<Uint8Array>;
|
|
|
499
625
|
*/
|
|
500
626
|
declare function base64UrlNoPadEncode(data: Uint8Array): string;
|
|
501
627
|
|
|
628
|
+
/**
|
|
629
|
+
* Creates the recommended address guard: resolves each hop's host and requires every resolved
|
|
630
|
+
* address to be globally routable public unicast.
|
|
631
|
+
*
|
|
632
|
+
* @remarks
|
|
633
|
+
* **Node only** — it resolves names, and no browser API returns a hostname's A/AAAA records.
|
|
634
|
+
* `allowAnyAddress()` is the honest choice there, and its name says so.
|
|
635
|
+
*
|
|
636
|
+
* This is the guard an entry point's `addressGuard` option takes. It is the resolving,
|
|
637
|
+
* hop-chain-aware half; the judgement itself belongs to
|
|
638
|
+
* {@link SaferFetch.blockPrivateNetworksPolicy}, which this guard delegates to unchanged. Keeping
|
|
639
|
+
* the adversarial classification matrix in one pure, synchronous implementation is what makes
|
|
640
|
+
* "did the address check run, and run correctly?" answerable by reading one file.
|
|
641
|
+
*
|
|
642
|
+
* The guard is invoked **once per redirect hop**, on the last entry of the chain. Hop 0 is not a
|
|
643
|
+
* special case: a guard that only validated the initial URL is defeated by a single `302` to
|
|
644
|
+
* `http://169.254.169.254/`.
|
|
645
|
+
*
|
|
646
|
+
* The URL-level constraints — `https:` unless `allowInsecureHttp`, plus the optional `allowHosts`
|
|
647
|
+
* and `allowPorts` allowlists — are checked **before** the name resolution, so a host the caller
|
|
648
|
+
* never allowlisted is refused by string comparison rather than handed to a resolver.
|
|
649
|
+
*
|
|
650
|
+
* ```typescript
|
|
651
|
+
* // A local Ollama sidecar: every deviation from the default posture is named and greppable.
|
|
652
|
+
* const guard = blockPrivateNetworks({
|
|
653
|
+
* allowLoopback: true,
|
|
654
|
+
* allowInsecureHttp: true,
|
|
655
|
+
* allowHosts: ['localhost'],
|
|
656
|
+
* allowPorts: [11434]
|
|
657
|
+
* });
|
|
658
|
+
* ```
|
|
659
|
+
*
|
|
660
|
+
* **What this does not protect against.** It validates a resolved address and the transport then
|
|
661
|
+
* re-resolves, so hostile DNS can answer the two lookups differently — the documented
|
|
662
|
+
* DNS-rebinding limit, which is open in this release. A strict `allowHosts` list is the
|
|
663
|
+
* recommended posture precisely because it shrinks that exposure to "an allowlisted host's own
|
|
664
|
+
* resolver is hostile".
|
|
665
|
+
*
|
|
666
|
+
* @param options - optional relaxations of the default posture, plus the resolver seam.
|
|
667
|
+
* @returns the guard. Construction cannot fail.
|
|
668
|
+
* @public
|
|
669
|
+
*/
|
|
670
|
+
declare function blockPrivateNetworks(options?: IBlockPrivateNetworksGuardOptions): IAddressGuard;
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Creates the recommended address policy: every address must be a globally
|
|
674
|
+
* routable public unicast address.
|
|
675
|
+
*
|
|
676
|
+
* Rejected, in both their plain and their IPv6-embedded encodings: loopback,
|
|
677
|
+
* the link-local range that carries the cloud instance-metadata endpoint
|
|
678
|
+
* (`169.254.169.254`), RFC 1918 private ranges, IPv6 unique-local, carrier-grade
|
|
679
|
+
* NAT (`100.64.0.0/10`), the unspecified address, multicast, broadcast,
|
|
680
|
+
* benchmarking, documentation and every other reserved range. The encoding
|
|
681
|
+
* bypasses covered are IPv4-mapped IPv6 (`::ffff:169.254.169.254`),
|
|
682
|
+
* IPv4-compatible IPv6, NAT64 (`64:ff9b::a9fe:a9fe`), 6to4 (`2002:a9fe:a9fe::`)
|
|
683
|
+
* and the shortened / octal / hexadecimal / decimal IPv4 literal forms
|
|
684
|
+
* (`127.1`, `0177.0.0.1`, `0x7f.1`, `2130706433`).
|
|
685
|
+
*
|
|
686
|
+
* **What this does not protect against.** The policy classifies addresses a
|
|
687
|
+
* caller has already resolved; it cannot see the address the connection
|
|
688
|
+
* ultimately uses. A hostile DNS server that answers the resolution with a
|
|
689
|
+
* public address and the connect with a private one is not stopped by this
|
|
690
|
+
* policy — closing that requires connecting to a pinned address. The policy
|
|
691
|
+
* also says nothing about scheme, host, port, redirects or response content.
|
|
692
|
+
*
|
|
693
|
+
* This is the **policy-layer** factory. It is not what a safer-fetch call's
|
|
694
|
+
* `addressGuard` option takes: an entry point takes an `IAddressGuard`, which is
|
|
695
|
+
* the asynchronous, hop-chain-aware, name-resolving half. `blockPrivateNetworks`
|
|
696
|
+
* (Node only) is the guard that resolves a hostname and delegates every resolved
|
|
697
|
+
* address to this policy; reach for that unless you are classifying an address
|
|
698
|
+
* list you already hold.
|
|
699
|
+
*
|
|
700
|
+
* @param options - optional {@link IBlockPrivateNetworksOptions | relaxations}
|
|
701
|
+
* of the default posture.
|
|
702
|
+
* @returns the policy. Construction cannot fail.
|
|
703
|
+
* @public
|
|
704
|
+
*/
|
|
705
|
+
declare function blockPrivateNetworksPolicy(options?: IBlockPrivateNetworksOptions): IAddressPolicy;
|
|
706
|
+
|
|
502
707
|
/**
|
|
503
708
|
* Calls the appropriate chat completion API for a given provider. Routes by
|
|
504
709
|
* `apiFormat`: `'openai'` (xAI/OpenAI/Groq/Mistral — switches to Responses API
|
|
@@ -638,6 +843,45 @@ declare function callProxiedImageGeneration(proxyUrl: string, params: IProviderI
|
|
|
638
843
|
*/
|
|
639
844
|
declare function callProxiedListModels(proxyUrl: string, params: IProviderListModelsParams): Promise<Result<ReadonlyArray<IAiModelInfo>>>;
|
|
640
845
|
|
|
846
|
+
/**
|
|
847
|
+
* Classifies a single IP address literal.
|
|
848
|
+
*
|
|
849
|
+
* The function is pure, synchronous, and deterministic — it performs no name
|
|
850
|
+
* resolution and no I/O. Input that is not an IP address literal (a DNS
|
|
851
|
+
* hostname, for instance) fails; callers that accept hostnames should treat a
|
|
852
|
+
* failure here as "not a literal" and resolve the name, then classify each
|
|
853
|
+
* resolved address.
|
|
854
|
+
*
|
|
855
|
+
* Accepted forms:
|
|
856
|
+
*
|
|
857
|
+
* - dotted-quad IPv4 (`169.254.169.254`)
|
|
858
|
+
* - the shortened and non-decimal IPv4 forms the WHATWG URL parser accepts
|
|
859
|
+
* (`127.1`, `2130706433`, `0177.0.0.1`, `0x7f.1`)
|
|
860
|
+
* - IPv6, with or without `::` compression, optionally bracketed as a URL
|
|
861
|
+
* `hostname` is (`[::1]`) and optionally carrying a zone id (`fe80::1%eth0`)
|
|
862
|
+
* - IPv6 forms that embed an IPv4 address — IPv4-mapped, IPv4-compatible,
|
|
863
|
+
* NAT64 and 6to4 — which are classified by their embedded IPv4 address
|
|
864
|
+
*
|
|
865
|
+
* A trailing dot is accepted on an IPv4 literal (`127.0.0.1.`), matching URL
|
|
866
|
+
* hostname normalization.
|
|
867
|
+
*
|
|
868
|
+
* **Classify a URL's `hostname`, never the raw URL text.** This function does
|
|
869
|
+
* not apply IDNA/Unicode normalization, and that step is not cosmetic: the
|
|
870
|
+
* platform's URL parser reads `http://127.0.0.1/`, `http://127。0。0。1/` and
|
|
871
|
+
* even `http://⑫7.0.0.1/` as the host `127.0.0.1`, because IDNA normalizes
|
|
872
|
+
* fullwidth digits, the ideographic full stop and circled numbers to their
|
|
873
|
+
* ASCII equivalents. Handed one of those strings directly this function fails —
|
|
874
|
+
* which is fail-closed, but only because the caller is then expected to treat
|
|
875
|
+
* "not a literal" as "resolve it as a name". Reading `new URL(...).hostname`
|
|
876
|
+
* gets the normalization for free and is the only supported use.
|
|
877
|
+
*
|
|
878
|
+
* @param address - the address literal to classify.
|
|
879
|
+
* @returns `Success` with the {@link IClassifiedAddress | classification},
|
|
880
|
+
* or `Failure` if the supplied text is not a well-formed IP address literal.
|
|
881
|
+
* @public
|
|
882
|
+
*/
|
|
883
|
+
declare function classifyAddress(address: string): Result<IClassifiedAddress>;
|
|
884
|
+
|
|
641
885
|
/**
|
|
642
886
|
* Classifies why a JSON-shaped LLM response would not parse, returning a
|
|
643
887
|
* {@link AiAssist.JsonParseFailureReason} a caller can branch on — repair the
|
|
@@ -905,6 +1149,13 @@ declare const DEFAULT_ALGORITHM: "AES-256-GCM";
|
|
|
905
1149
|
*/
|
|
906
1150
|
declare const DEFAULT_ANTHROPIC_MAX_TOKENS: number;
|
|
907
1151
|
|
|
1152
|
+
/**
|
|
1153
|
+
* Default deadline to first response headers, in milliseconds. A host that has not sent
|
|
1154
|
+
* headers in ten seconds is not answering.
|
|
1155
|
+
* @public
|
|
1156
|
+
*/
|
|
1157
|
+
declare const DEFAULT_HEADERS_TIMEOUT_MS: number;
|
|
1158
|
+
|
|
908
1159
|
/**
|
|
909
1160
|
* Default PBKDF2 iterations for key store encryption.
|
|
910
1161
|
* Higher than regular files since this protects the master key vault.
|
|
@@ -912,6 +1163,29 @@ declare const DEFAULT_ANTHROPIC_MAX_TOKENS: number;
|
|
|
912
1163
|
*/
|
|
913
1164
|
declare const DEFAULT_KEYSTORE_ITERATIONS: number;
|
|
914
1165
|
|
|
1166
|
+
/**
|
|
1167
|
+
* Default cap on redirect hops followed under `'validate-each-hop'`.
|
|
1168
|
+
*
|
|
1169
|
+
* @remarks
|
|
1170
|
+
* Five is enough for the ordinary shortener-then-canonicalize-then-CDN chains real services
|
|
1171
|
+
* produce and small enough that a chain designed to burn budget is refused quickly. Every hop
|
|
1172
|
+
* costs a full guard evaluation — including a DNS resolution — against the same overall
|
|
1173
|
+
* deadline, so the cap bounds work, not just politeness.
|
|
1174
|
+
* @public
|
|
1175
|
+
*/
|
|
1176
|
+
declare const DEFAULT_MAX_REDIRECTS: number;
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* Default cap on decoded response bytes (5 MiB) — roughly an order of magnitude above a
|
|
1180
|
+
* realistic JSON API response and well below a size that threatens a Node heap.
|
|
1181
|
+
*
|
|
1182
|
+
* @remarks
|
|
1183
|
+
* Raising it is a per-call option, not a construction-time setting, precisely because real
|
|
1184
|
+
* documents clear this. See `ISaferFetchOptions.maxResponseBytes`.
|
|
1185
|
+
* @public
|
|
1186
|
+
*/
|
|
1187
|
+
declare const DEFAULT_MAX_RESPONSE_BYTES: number;
|
|
1188
|
+
|
|
915
1189
|
/**
|
|
916
1190
|
* Default capability config used by `callProviderListModels` when callers
|
|
917
1191
|
* don't supply their own. Patterns are intentionally narrow — false
|
|
@@ -929,6 +1203,23 @@ declare const DEFAULT_MODEL_CAPABILITY_CONFIG: IAiModelCapabilityConfig;
|
|
|
929
1203
|
*/
|
|
930
1204
|
declare const DEFAULT_RANGEOF_FORMATS: RangeOfFormats;
|
|
931
1205
|
|
|
1206
|
+
/**
|
|
1207
|
+
* Default base delay for {@link SaferFetch.IRetryPolicy | retry} backoff, in milliseconds.
|
|
1208
|
+
* @public
|
|
1209
|
+
*/
|
|
1210
|
+
declare const DEFAULT_RETRY_BASE_DELAY_MS: number;
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* Default ceiling on a single {@link SaferFetch.IRetryPolicy | retry} delay, in milliseconds.
|
|
1214
|
+
*
|
|
1215
|
+
* @remarks
|
|
1216
|
+
* It bounds the computed backoff **and** a server-supplied `Retry-After`. That second role is
|
|
1217
|
+
* the security-relevant one: `Retry-After` is a header an attacker-controlled server chooses
|
|
1218
|
+
* freely, so an unclamped `Retry-After: 86400` is a denial of service on the caller.
|
|
1219
|
+
* @public
|
|
1220
|
+
*/
|
|
1221
|
+
declare const DEFAULT_RETRY_MAX_DELAY_MS: number;
|
|
1222
|
+
|
|
932
1223
|
/**
|
|
933
1224
|
* Default PBKDF2 iterations for secret-level key derivation.
|
|
934
1225
|
* Lower than keystore encryption since these are used more frequently.
|
|
@@ -936,6 +1227,14 @@ declare const DEFAULT_RANGEOF_FORMATS: RangeOfFormats;
|
|
|
936
1227
|
*/
|
|
937
1228
|
declare const DEFAULT_SECRET_ITERATIONS: number;
|
|
938
1229
|
|
|
1230
|
+
/**
|
|
1231
|
+
* Default overall deadline, in milliseconds — comfortably above a slow-but-real API and far
|
|
1232
|
+
* below "a hung request pins a socket for minutes". Streaming LLM calls should not use this
|
|
1233
|
+
* primitive at all; that is what `AiAssist` is for.
|
|
1234
|
+
* @public
|
|
1235
|
+
*/
|
|
1236
|
+
declare const DEFAULT_TIMEOUT_MS: number;
|
|
1237
|
+
|
|
939
1238
|
/**
|
|
940
1239
|
* Derives an asymmetric keypair *deterministically* from a fixed secret seed
|
|
941
1240
|
* using `globalThis.crypto.subtle`, shared by the Node and browser
|
|
@@ -1355,6 +1654,155 @@ declare function fencedStringifiedJson(options?: IFencedStringifiedJsonExtractor
|
|
|
1355
1654
|
*/
|
|
1356
1655
|
declare function fencedStringifiedJson<T>(options: IFencedStringifiedJsonOptions<T>): Converter<T>;
|
|
1357
1656
|
|
|
1657
|
+
/**
|
|
1658
|
+
* Machine-readable reason a safer-fetch call failed, carried as the detail of the
|
|
1659
|
+
* `DetailedResult` every entry point returns.
|
|
1660
|
+
*
|
|
1661
|
+
* @remarks
|
|
1662
|
+
* **This value is an internal-network scanning oracle. Do not echo it, or any string
|
|
1663
|
+
* derived from it, to an untrusted caller.** A `'blocked-by-guard'` detail names the
|
|
1664
|
+
* URL, the hop, and the guard that rejected it; an attacker who can influence the
|
|
1665
|
+
* request URL and observe the error learns whether an internal host exists. Log the
|
|
1666
|
+
* detail; return a coarse code (the `kind` alone, or a generic message) to callers.
|
|
1667
|
+
*
|
|
1668
|
+
* Following the `AiAssist.JsonParseFailureReason` precedent in this package: a `kind`
|
|
1669
|
+
* discriminant with per-kind payload fields, and `'unknown'` as an honest catch-all
|
|
1670
|
+
* rather than a guess.
|
|
1671
|
+
* @public
|
|
1672
|
+
*/
|
|
1673
|
+
declare type FetchFailureReason =
|
|
1674
|
+
/** The supplied URL could not be parsed, or its scheme is not `http:` or `https:`. */
|
|
1675
|
+
{
|
|
1676
|
+
readonly kind: 'invalid-url';
|
|
1677
|
+
readonly url: string;
|
|
1678
|
+
readonly detail: string;
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* A guard refused. `hop` is 0-based; hop 0 is the caller's URL, and `guard` names which
|
|
1682
|
+
* guard rejected — which is what makes "did the address check run, and was it the thing that
|
|
1683
|
+
* said no?" answerable from the failure alone. A content-type rejection is reported as
|
|
1684
|
+
* `'unsupported-content-type'` instead, because that case carries a more useful payload.
|
|
1685
|
+
*/
|
|
1686
|
+
| {
|
|
1687
|
+
readonly kind: 'blocked-by-guard';
|
|
1688
|
+
readonly url: string;
|
|
1689
|
+
readonly hop: number;
|
|
1690
|
+
readonly guard: string;
|
|
1691
|
+
readonly detail: string;
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* A redirect status was received that this call would not follow: the policy is `'reject'`,
|
|
1695
|
+
* or the response carried no usable `Location`, or following it would revisit a URL already
|
|
1696
|
+
* in the chain.
|
|
1697
|
+
*
|
|
1698
|
+
* @remarks
|
|
1699
|
+
* The three are deliberately one kind. Splitting them would buy a caller almost nothing and
|
|
1700
|
+
* would widen the scanning oracle this taxonomy already is — and the two follow-time cases
|
|
1701
|
+
* are facts about the chain the redirecting server produced, not about the network behind
|
|
1702
|
+
* this process. "The chain got too long" is a different question and stays
|
|
1703
|
+
* `'too-many-redirects'`.
|
|
1704
|
+
*
|
|
1705
|
+
* `url` is always the URL that **issued** the rejected redirect — the hop this call actually
|
|
1706
|
+
* requested and got a 3xx back from — never the `Location` target it pointed at. That holds
|
|
1707
|
+
* for all three cases, including the revisit case, where the target is the URL already in the
|
|
1708
|
+
* chain and naming it here would make the same field mean two different things. The target is
|
|
1709
|
+
* named in the message instead. `status` is the redirect status that was received.
|
|
1710
|
+
*/
|
|
1711
|
+
| {
|
|
1712
|
+
readonly kind: 'redirect-rejected';
|
|
1713
|
+
readonly url: string;
|
|
1714
|
+
readonly status: number;
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* The platform returned an opaque redirect, whose `Location` is not readable. This is
|
|
1718
|
+
* what a browser yields for `redirect: 'manual'`; the hop cannot be inspected at all.
|
|
1719
|
+
*/
|
|
1720
|
+
| {
|
|
1721
|
+
readonly kind: 'redirect-opaque';
|
|
1722
|
+
}
|
|
1723
|
+
/** The redirect hop budget was exhausted. */
|
|
1724
|
+
| {
|
|
1725
|
+
readonly kind: 'too-many-redirects';
|
|
1726
|
+
readonly hops: number;
|
|
1727
|
+
readonly limit: number;
|
|
1728
|
+
}
|
|
1729
|
+
/** One of the deadlines elapsed. Distinct from `'aborted'`, which is the caller's signal. */
|
|
1730
|
+
| {
|
|
1731
|
+
readonly kind: 'timeout';
|
|
1732
|
+
readonly phase: FetchTimeoutPhase;
|
|
1733
|
+
readonly elapsedMs: number;
|
|
1734
|
+
readonly limitMs: number;
|
|
1735
|
+
}
|
|
1736
|
+
/** The caller's `AbortSignal` fired. Distinct from `'timeout'`, which is our deadline. */
|
|
1737
|
+
| {
|
|
1738
|
+
readonly kind: 'aborted';
|
|
1739
|
+
}
|
|
1740
|
+
/** The transport could not complete the request. */
|
|
1741
|
+
| {
|
|
1742
|
+
readonly kind: 'network';
|
|
1743
|
+
readonly detail: string;
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* A non-2xx response. `bodyPreview` is **never populated in this release** — error bodies
|
|
1747
|
+
* routinely echo request content, including credentials, so surfacing one has to be an
|
|
1748
|
+
* explicit, length-capped opt-in rather than a default. The field is declared so that adding
|
|
1749
|
+
* that opt-in later is additive.
|
|
1750
|
+
*/
|
|
1751
|
+
| {
|
|
1752
|
+
readonly kind: 'http-status';
|
|
1753
|
+
readonly status: number;
|
|
1754
|
+
readonly statusText: string;
|
|
1755
|
+
readonly bodyPreview?: string;
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* The response exceeded the byte cap. `declared` is what `Content-Length` claimed, present
|
|
1759
|
+
* only when the header was sent — and it counts *encoded* bytes where `bytesRead` counts
|
|
1760
|
+
* *decoded* bytes, so a `declared` far below `bytesRead` is evidence of a compression bomb
|
|
1761
|
+
* or a lying server rather than an arithmetic error.
|
|
1762
|
+
*/
|
|
1763
|
+
| {
|
|
1764
|
+
readonly kind: 'too-large';
|
|
1765
|
+
readonly bytesRead: number;
|
|
1766
|
+
readonly limit: number;
|
|
1767
|
+
readonly declared?: number;
|
|
1768
|
+
}
|
|
1769
|
+
/** A response-headers guard rejected the response's content type. */
|
|
1770
|
+
| {
|
|
1771
|
+
readonly kind: 'unsupported-content-type';
|
|
1772
|
+
readonly contentType?: string;
|
|
1773
|
+
readonly accepted: ReadonlyArray<string>;
|
|
1774
|
+
}
|
|
1775
|
+
/** The response bytes could not be decoded to text with the indicated charset. */
|
|
1776
|
+
| {
|
|
1777
|
+
readonly kind: 'decode';
|
|
1778
|
+
readonly detail: string;
|
|
1779
|
+
}
|
|
1780
|
+
/** The decoded text could not be parsed, or failed the caller's converter. */
|
|
1781
|
+
| {
|
|
1782
|
+
readonly kind: 'parse';
|
|
1783
|
+
readonly detail: string;
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Anything else — including invalid options and a guard or transport that violated its
|
|
1787
|
+
* contract. Reports what it knows rather than guessing at a more specific kind.
|
|
1788
|
+
*/
|
|
1789
|
+
| {
|
|
1790
|
+
readonly kind: 'unknown';
|
|
1791
|
+
readonly detail: string;
|
|
1792
|
+
};
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* Which deadline a {@link SaferFetch.FetchFailureReason | timeout failure} belongs to.
|
|
1796
|
+
*
|
|
1797
|
+
* - `'headers'`: the per-attempt headers deadline elapsed before the server sent
|
|
1798
|
+
* response headers — the host is not answering.
|
|
1799
|
+
* - `'body'`: the overall deadline elapsed while the response body was being read
|
|
1800
|
+
* — the host answered and then trickled (the slowloris shape).
|
|
1801
|
+
* - `'overall'`: the overall deadline elapsed before response headers arrived.
|
|
1802
|
+
* @public
|
|
1803
|
+
*/
|
|
1804
|
+
declare type FetchTimeoutPhase = 'headers' | 'body' | 'overall';
|
|
1805
|
+
|
|
1358
1806
|
/**
|
|
1359
1807
|
* Formats a list of items using the supplied template and formatter, one result
|
|
1360
1808
|
* per output line.
|
|
@@ -1538,6 +1986,23 @@ declare function hexDecode(encoded: string): Result<Uint8Array>;
|
|
|
1538
1986
|
*/
|
|
1539
1987
|
declare function hexEncode(data: Uint8Array): string;
|
|
1540
1988
|
|
|
1989
|
+
/**
|
|
1990
|
+
* Resolves a hostname to every address it names.
|
|
1991
|
+
*
|
|
1992
|
+
* @remarks
|
|
1993
|
+
* Injectable so that the resolving half of an address guard is testable without a network — a
|
|
1994
|
+
* guard test that performs a real lookup fails in CI for reasons unrelated to the guard, and a
|
|
1995
|
+
* guard test that could reach `169.254.169.254` is worse than flaky.
|
|
1996
|
+
*
|
|
1997
|
+
* An implementation must return a `Failure` rather than reject: `node:dns` rejects on
|
|
1998
|
+
* `ENOTFOUND`, `EAI_AGAIN` and friends, and an entry point documented to always return a
|
|
1999
|
+
* `Result` must not let that escape as a throw. {@link SaferFetch.nodeHostResolver} converts.
|
|
2000
|
+
*
|
|
2001
|
+
* @param hostname - The host to resolve, with no surrounding brackets.
|
|
2002
|
+
* @public
|
|
2003
|
+
*/
|
|
2004
|
+
declare type HostResolver = (hostname: string) => Promise<Result<ReadonlyArray<string>>>;
|
|
2005
|
+
|
|
1541
2006
|
/**
|
|
1542
2007
|
* HPKE base mode (RFC 9180) — `DHKEM(X25519, HKDF-SHA256) + HKDF-SHA256 + AES-256-GCM`.
|
|
1543
2008
|
*
|
|
@@ -1741,6 +2206,85 @@ declare interface IAddKeyPairResult {
|
|
|
1741
2206
|
readonly warning?: string;
|
|
1742
2207
|
}
|
|
1743
2208
|
|
|
2209
|
+
/**
|
|
2210
|
+
* The evidence behind an allowed address-policy decision.
|
|
2211
|
+
* @public
|
|
2212
|
+
*/
|
|
2213
|
+
declare interface IAddressCheckVerdict {
|
|
2214
|
+
/** The {@link IAddressPolicy.name | name} of the policy that produced the verdict. */
|
|
2215
|
+
readonly policy: string;
|
|
2216
|
+
/**
|
|
2217
|
+
* The classification of every address the policy examined, in the order
|
|
2218
|
+
* supplied. Empty for {@link allowAnyAddressPolicy}, which classifies nothing.
|
|
2219
|
+
*/
|
|
2220
|
+
readonly addresses: ReadonlyArray<IClassifiedAddress>;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
/**
|
|
2224
|
+
* Decides whether a connection may be made. Invoked once per redirect hop, never only on
|
|
2225
|
+
* the initial URL.
|
|
2226
|
+
*
|
|
2227
|
+
* @remarks
|
|
2228
|
+
* This is the SSRF boundary and the one guard whose failure is catastrophic, which is why
|
|
2229
|
+
* it is a separate seam from the request and response policy guards: "did the address check
|
|
2230
|
+
* run, and run correctly?" should be answerable by reading one small implementation.
|
|
2231
|
+
*
|
|
2232
|
+
* The seam is **asynchronous and hop-chain-aware so that it can wrap a pure classifier**: a
|
|
2233
|
+
* real guard resolves `url.hostname` to an address list and then delegates the judgement to a
|
|
2234
|
+
* synchronous, address-list-in policy. That split keeps the part with the adversarial test
|
|
2235
|
+
* matrix — the address classification itself — free of transports, DNS, and hop bookkeeping,
|
|
2236
|
+
* and keeps exactly one implementation of it. A guard is the resolving, chain-aware half; it
|
|
2237
|
+
* should not re-derive what the classifier already decides.
|
|
2238
|
+
*
|
|
2239
|
+
* Classify `url.hostname`, never raw URL text: the WHATWG parser has already normalized
|
|
2240
|
+
* `127.0x.1` to `127.0.0.1` and `::ffff:169.254.169.254` to `::ffff:a9fe:a9fe`, and text
|
|
2241
|
+
* matching misses both.
|
|
2242
|
+
* @public
|
|
2243
|
+
*/
|
|
2244
|
+
declare interface IAddressGuard {
|
|
2245
|
+
/** Stable identifier, surfaced in `'blocked-by-guard'` failures. */
|
|
2246
|
+
readonly name: string;
|
|
2247
|
+
/**
|
|
2248
|
+
* @param chain - Every hop so far, oldest first. The URL under consideration is the last
|
|
2249
|
+
* entry; `chain.length === 1` is the initial request. There is deliberately no separate
|
|
2250
|
+
* "is this a redirect" flag — hop 0 is not a special case, and a guard that compares only
|
|
2251
|
+
* against the immediately previous hop cannot see an `A → B → A` laundering hop.
|
|
2252
|
+
*/
|
|
2253
|
+
check(chain: ReadonlyArray<IRequestHop>): Promise<Result<IGuardVerdict>>;
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
/**
|
|
2257
|
+
* A pure, synchronous decision over a set of IP addresses.
|
|
2258
|
+
*
|
|
2259
|
+
* A policy performs no name resolution and no I/O: a caller resolves a
|
|
2260
|
+
* hostname to its addresses and hands the whole list to
|
|
2261
|
+
* {@link IAddressPolicy.checkAddresses}. The list contract is
|
|
2262
|
+
* **reject-if-any** — a hostname that resolves to one public and one private
|
|
2263
|
+
* address is disallowed, because without connect-time address pinning there is
|
|
2264
|
+
* no guarantee which address the connection will use.
|
|
2265
|
+
*
|
|
2266
|
+
* @public
|
|
2267
|
+
*/
|
|
2268
|
+
declare interface IAddressPolicy {
|
|
2269
|
+
/**
|
|
2270
|
+
* A stable, greppable identifier for the posture this policy implements.
|
|
2271
|
+
* Surfaced in the failure message when an address is disallowed.
|
|
2272
|
+
*/
|
|
2273
|
+
readonly name: string;
|
|
2274
|
+
/**
|
|
2275
|
+
* Decides whether a connection may be made to a destination that resolved to
|
|
2276
|
+
* `addresses`.
|
|
2277
|
+
*
|
|
2278
|
+
* @param addresses - every address the destination resolved to, or the
|
|
2279
|
+
* single literal address when the destination was an IP literal.
|
|
2280
|
+
* @returns `Success` with the {@link IAddressCheckVerdict | verdict} when
|
|
2281
|
+
* *every* address is permitted, `Failure` naming every address that is not.
|
|
2282
|
+
* An empty list is a failure for any policy that offers a guarantee: nothing
|
|
2283
|
+
* was verified, so nothing may be reached.
|
|
2284
|
+
*/
|
|
2285
|
+
checkAddresses(addresses: ReadonlyArray<string>): Result<IAddressCheckVerdict>;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
1744
2288
|
/**
|
|
1745
2289
|
* Options for adding an Argon2id password-derived secret.
|
|
1746
2290
|
* @public
|
|
@@ -2808,6 +3352,71 @@ declare interface IArgon2idProvider {
|
|
|
2808
3352
|
argon2id(password: Uint8Array | string, salt: Uint8Array, params: IArgon2idParams, options?: IArgon2idKeyingOptions): Promise<Result<Uint8Array>>;
|
|
2809
3353
|
}
|
|
2810
3354
|
|
|
3355
|
+
/**
|
|
3356
|
+
* Options for {@link SaferFetch.blockPrivateNetworks}.
|
|
3357
|
+
* @public
|
|
3358
|
+
*/
|
|
3359
|
+
declare interface IBlockPrivateNetworksGuardOptions extends IBlockPrivateNetworksOptions {
|
|
3360
|
+
/**
|
|
3361
|
+
* Restricts every hop to these hostnames. Absent means any hostname.
|
|
3362
|
+
*
|
|
3363
|
+
* @remarks
|
|
3364
|
+
* Matching is case-insensitive and exact — no wildcards and no suffix matching, because
|
|
3365
|
+
* `endsWith('.example.com')` is the classic host-allowlist bypass (`evil-example.com`,
|
|
3366
|
+
* `example.com.attacker.net`) and a primitive that offers the convenient form invites it.
|
|
3367
|
+
* List the hosts.
|
|
3368
|
+
*
|
|
3369
|
+
* **A host allowlist is the recommended posture**, and it is stronger than address
|
|
3370
|
+
* classification alone: with one, DNS rebinding can only be mounted by an allowlisted host's
|
|
3371
|
+
* own resolver, which is a far smaller surface than "any hostname the caller was handed".
|
|
3372
|
+
*/
|
|
3373
|
+
readonly allowHosts?: ReadonlyArray<string>;
|
|
3374
|
+
/**
|
|
3375
|
+
* Restricts every hop to these ports. Absent means any port.
|
|
3376
|
+
*
|
|
3377
|
+
* @remarks
|
|
3378
|
+
* A URL with no explicit port is checked against the scheme's default — `443` for `https:`,
|
|
3379
|
+
* `80` for `http:` — so `allowPorts: [443]` accepts `https://example.com/`.
|
|
3380
|
+
*/
|
|
3381
|
+
readonly allowPorts?: ReadonlyArray<number>;
|
|
3382
|
+
/**
|
|
3383
|
+
* Permits `http:` hops. **Off by default**, so this guard requires `https:`.
|
|
3384
|
+
*
|
|
3385
|
+
* @remarks
|
|
3386
|
+
* The core refuses everything that is not `http:` or `https:` and deliberately chooses
|
|
3387
|
+
* between those two here rather than there, because the choice is a posture rather than a
|
|
3388
|
+
* structural rule. Plaintext HTTP is exposed to a network-position attacker and is the scheme
|
|
3389
|
+
* every SSRF payload reaches for, so the polarity matches `allowLoopback`: deny by default,
|
|
3390
|
+
* opt in visibly, and let a reviewer grep for the opt-in.
|
|
3391
|
+
*/
|
|
3392
|
+
readonly allowInsecureHttp?: boolean;
|
|
3393
|
+
/**
|
|
3394
|
+
* Name resolution. Defaults to {@link SaferFetch.nodeHostResolver}.
|
|
3395
|
+
*
|
|
3396
|
+
* @remarks
|
|
3397
|
+
* Present so the guard is unit-testable with no network. It is **not** a hook through which
|
|
3398
|
+
* DNS rebinding can be closed: swapping the resolver changes only the address the guard
|
|
3399
|
+
* validates, while the transport still connects by hostname and resolves again. Closing that
|
|
3400
|
+
* requires a pinning transport — see `IGuardVerdict.pinnedAddress`.
|
|
3401
|
+
*/
|
|
3402
|
+
readonly resolve?: HostResolver;
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
/**
|
|
3406
|
+
* Options shared by {@link blockPrivateNetworksPolicy} and the address guard built over it.
|
|
3407
|
+
* @public
|
|
3408
|
+
*/
|
|
3409
|
+
declare interface IBlockPrivateNetworksOptions {
|
|
3410
|
+
/**
|
|
3411
|
+
* Permits `127.0.0.0/8` and `::1` (and their IPv4-mapped forms). Off by
|
|
3412
|
+
* default: the polarity that ships a guard permitting `http://127.0.0.1:6379/`
|
|
3413
|
+
* so a local-development convenience keeps working is the wrong one. A
|
|
3414
|
+
* caller talking to a local sidecar opts in here, and the opt-in is
|
|
3415
|
+
* independently greppable at the call site.
|
|
3416
|
+
*/
|
|
3417
|
+
readonly allowLoopback?: boolean;
|
|
3418
|
+
}
|
|
3419
|
+
|
|
2811
3420
|
/**
|
|
2812
3421
|
* A single chat message in OpenAI format.
|
|
2813
3422
|
* @public
|
|
@@ -2854,6 +3463,36 @@ declare interface IChatRequest {
|
|
|
2854
3463
|
readonly messages: ReadonlyArray<IChatMessage>;
|
|
2855
3464
|
}
|
|
2856
3465
|
|
|
3466
|
+
/**
|
|
3467
|
+
* The result of classifying a single IP address literal.
|
|
3468
|
+
* @public
|
|
3469
|
+
*/
|
|
3470
|
+
declare interface IClassifiedAddress {
|
|
3471
|
+
/** The address exactly as supplied to {@link classifyAddress}. */
|
|
3472
|
+
readonly address: string;
|
|
3473
|
+
/**
|
|
3474
|
+
* A canonical textual form of the address: dotted-quad for IPv4, and
|
|
3475
|
+
* lowercase RFC 5952 hex-group form (with `::` run compression) for IPv6.
|
|
3476
|
+
* Note that IPv6 addresses embedding an IPv4 address canonicalize to their
|
|
3477
|
+
* hex-group form (`::ffff:a9fe:a9fe`), not to the dotted-quad mixed form;
|
|
3478
|
+
* `embeddedIpv4` carries the readable IPv4 value.
|
|
3479
|
+
*/
|
|
3480
|
+
readonly canonical: string;
|
|
3481
|
+
/** The address family of the literal as supplied — never the embedded family. */
|
|
3482
|
+
readonly family: AddressFamily;
|
|
3483
|
+
/**
|
|
3484
|
+
* The classification used for policy decisions. For an IPv6 address that
|
|
3485
|
+
* embeds an IPv4 address, this is the classification of the *embedded* IPv4
|
|
3486
|
+
* address.
|
|
3487
|
+
*/
|
|
3488
|
+
readonly classification: AddressClassification;
|
|
3489
|
+
/**
|
|
3490
|
+
* Present exactly when `classification` was derived from an IPv4 address
|
|
3491
|
+
* embedded in this IPv6 address, and absent otherwise.
|
|
3492
|
+
*/
|
|
3493
|
+
readonly embeddedIpv4?: IEmbeddedIpv4;
|
|
3494
|
+
}
|
|
3495
|
+
|
|
2857
3496
|
/**
|
|
2858
3497
|
* Result of context validation, containing details about missing variables.
|
|
2859
3498
|
* @public
|
|
@@ -3236,6 +3875,16 @@ declare interface ICryptoProvider {
|
|
|
3236
3875
|
verifyHmacSha256(key: CryptoKey, signature: Uint8Array, data: Uint8Array): Promise<Result<boolean>>;
|
|
3237
3876
|
}
|
|
3238
3877
|
|
|
3878
|
+
/**
|
|
3879
|
+
* Methods retried without an explicit opt-in.
|
|
3880
|
+
*
|
|
3881
|
+
* @remarks
|
|
3882
|
+
* A timeout does not tell you whether the server processed the request, so a retried `POST`
|
|
3883
|
+
* can double-charge. `retryNonIdempotent` opts out, per call, visibly.
|
|
3884
|
+
* @public
|
|
3885
|
+
*/
|
|
3886
|
+
declare const IDEMPOTENT_METHODS: ReadonlyArray<string>;
|
|
3887
|
+
|
|
3239
3888
|
/**
|
|
3240
3889
|
* Parameters for creating a {@link DirectEncryptionProvider}.
|
|
3241
3890
|
* @public
|
|
@@ -3257,6 +3906,17 @@ declare interface IDirectEncryptionProviderParams {
|
|
|
3257
3906
|
readonly boundSecretName?: string;
|
|
3258
3907
|
}
|
|
3259
3908
|
|
|
3909
|
+
/**
|
|
3910
|
+
* The IPv4 address an IPv6 address embedded, and the way it embedded it.
|
|
3911
|
+
* @public
|
|
3912
|
+
*/
|
|
3913
|
+
declare interface IEmbeddedIpv4 {
|
|
3914
|
+
/** The encoding that carried the IPv4 address. */
|
|
3915
|
+
readonly kind: Ipv4EmbeddingKind;
|
|
3916
|
+
/** The dotted-quad form of the embedded IPv4 address. */
|
|
3917
|
+
readonly address: string;
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3260
3920
|
/**
|
|
3261
3921
|
* Result of a raw-byte AES-256-GCM encryption via
|
|
3262
3922
|
* {@link CryptoUtils.ICryptoProvider.encryptBytes | encryptBytes}. The
|
|
@@ -3575,6 +4235,34 @@ declare interface IFencedStringifiedJsonOptions<T> extends IFencedStringifiedJso
|
|
|
3575
4235
|
readonly inner: Converter<T> | Validator<T>;
|
|
3576
4236
|
}
|
|
3577
4237
|
|
|
4238
|
+
/**
|
|
4239
|
+
* Performs the actual request.
|
|
4240
|
+
*
|
|
4241
|
+
* @remarks
|
|
4242
|
+
* Injectable for two reasons. It is the seam through which a pinned-connect implementation
|
|
4243
|
+
* drops in without any caller-visible signature change — which is what keeps closing the
|
|
4244
|
+
* DNS-rebinding hole additive rather than breaking. And it is the test seam: redirect chains,
|
|
4245
|
+
* lying `Content-Length` headers, compression-bomb shapes, and trickling bodies are all
|
|
4246
|
+
* unit-testable through a scripted transport, with no live server anywhere in the suite.
|
|
4247
|
+
* @public
|
|
4248
|
+
*/
|
|
4249
|
+
declare interface IFetchTransport {
|
|
4250
|
+
readonly name: string;
|
|
4251
|
+
fetch(url: URL, init: RequestInit, hints: IFetchTransportHints): Promise<Result<Response>>;
|
|
4252
|
+
}
|
|
4253
|
+
|
|
4254
|
+
/**
|
|
4255
|
+
* Out-of-band information a transport may need that `RequestInit` cannot express.
|
|
4256
|
+
* @public
|
|
4257
|
+
*/
|
|
4258
|
+
declare interface IFetchTransportHints {
|
|
4259
|
+
/**
|
|
4260
|
+
* The address the address guard validated, to which this connection must be pinned.
|
|
4261
|
+
* A transport that cannot pin MUST fail rather than connect by hostname.
|
|
4262
|
+
*/
|
|
4263
|
+
readonly pinnedAddress?: string;
|
|
4264
|
+
}
|
|
4265
|
+
|
|
3578
4266
|
/**
|
|
3579
4267
|
* Provider-specific config for Gemini Flash Image.
|
|
3580
4268
|
* @public
|
|
@@ -3739,6 +4427,38 @@ declare interface IGrokImagineModelOptions extends INamedModelFamilyConfig {
|
|
|
3739
4427
|
readonly config: IGrokImagineImageGenerationConfig;
|
|
3740
4428
|
}
|
|
3741
4429
|
|
|
4430
|
+
/**
|
|
4431
|
+
* An address guard's decision about a hop.
|
|
4432
|
+
* @public
|
|
4433
|
+
*/
|
|
4434
|
+
declare interface IGuardVerdict {
|
|
4435
|
+
/**
|
|
4436
|
+
* The URL cleared for request. Guards may **normalize** it — lowercase the host, strip a
|
|
4437
|
+
* trailing dot, punycode an IDN — and the normalized URL is what gets requested. Guards
|
|
4438
|
+
* must not **retarget** it to a different destination.
|
|
4439
|
+
*
|
|
4440
|
+
* @remarks
|
|
4441
|
+
* That contract is documented rather than enforced: origin-equality enforcement would
|
|
4442
|
+
* reject exactly the normalizations the guard is supposed to perform. Guards are trusted
|
|
4443
|
+
* first-party code; a malicious in-process caller is out of this primitive's threat model.
|
|
4444
|
+
* The one check that is enforced is the scheme — a verdict URL whose scheme is not
|
|
4445
|
+
* `http:` or `https:` is rejected.
|
|
4446
|
+
*/
|
|
4447
|
+
readonly url: URL;
|
|
4448
|
+
/**
|
|
4449
|
+
* The address the guard validated and to which the connection SHOULD be pinned.
|
|
4450
|
+
*
|
|
4451
|
+
* @remarks
|
|
4452
|
+
* **Always `undefined` in this release** — reserved for the pinned-connect work that
|
|
4453
|
+
* closes the DNS-rebinding hole. A transport that receives a defined value it cannot
|
|
4454
|
+
* honor MUST fail rather than connect by hostname; {@link SaferFetch.platformFetchTransport}
|
|
4455
|
+
* does exactly that. Ignoring the hint would give a deployment that wired a pinning guard
|
|
4456
|
+
* but forgot the matching transport precisely the rebinding exposure it believed it had
|
|
4457
|
+
* closed, silently.
|
|
4458
|
+
*/
|
|
4459
|
+
readonly pinnedAddress?: string;
|
|
4460
|
+
}
|
|
4461
|
+
|
|
3742
4462
|
/**
|
|
3743
4463
|
* Output of {@link HpkeProvider.sealBase}.
|
|
3744
4464
|
*
|
|
@@ -4623,6 +5343,26 @@ declare interface IProviderListModelsParams {
|
|
|
4623
5343
|
readonly endpoint?: string;
|
|
4624
5344
|
}
|
|
4625
5345
|
|
|
5346
|
+
/**
|
|
5347
|
+
* The way an IPv6 address carried the IPv4 address whose classification was used.
|
|
5348
|
+
*
|
|
5349
|
+
* Each of these is a documented SSRF bypass: an address such as
|
|
5350
|
+
* `::ffff:169.254.169.254` is an IPv6 address by family, but the address the
|
|
5351
|
+
* connection actually reaches is the embedded IPv4 one, so the embedded
|
|
5352
|
+
* address is what must be classified.
|
|
5353
|
+
*
|
|
5354
|
+
* @public
|
|
5355
|
+
*/
|
|
5356
|
+
declare type Ipv4EmbeddingKind =
|
|
5357
|
+
/** `::ffff:0:0/96` — the IPv4-mapped IPv6 form. */
|
|
5358
|
+
'ipv4-mapped'
|
|
5359
|
+
/** `::/96` — the deprecated (RFC 4291) IPv4-compatible form. */
|
|
5360
|
+
| 'ipv4-compatible'
|
|
5361
|
+
/** `64:ff9b::/96` — the RFC 6052 well-known NAT64 prefix. */
|
|
5362
|
+
| 'nat64'
|
|
5363
|
+
/** `2002::/16` — the RFC 3056 6to4 form, whose IPv4 lives in bits 16..47. */
|
|
5364
|
+
| '6to4';
|
|
5365
|
+
|
|
4626
5366
|
/**
|
|
4627
5367
|
* Result of removing a secret from the key store.
|
|
4628
5368
|
* @public
|
|
@@ -4641,6 +5381,46 @@ declare interface IRemoveSecretResult {
|
|
|
4641
5381
|
readonly warning?: string;
|
|
4642
5382
|
}
|
|
4643
5383
|
|
|
5384
|
+
/**
|
|
5385
|
+
* Policy guard over the outbound request.
|
|
5386
|
+
*
|
|
5387
|
+
* @remarks
|
|
5388
|
+
* **Reject-only.** Silently rewriting a caller's headers or body inside a fetch primitive
|
|
5389
|
+
* means the caller no longer knows what was sent. To alter the request, return an explicit
|
|
5390
|
+
* replacement the caller could have observed.
|
|
5391
|
+
*
|
|
5392
|
+
* Runs **before** the address guard, so an address guard is always the last word before the
|
|
5393
|
+
* connect: a replacement request that changed the URL is still address-checked.
|
|
5394
|
+
* @public
|
|
5395
|
+
*/
|
|
5396
|
+
declare interface IRequestGuard {
|
|
5397
|
+
readonly name: string;
|
|
5398
|
+
check(request: ISaferFetchRequest, chain: ReadonlyArray<IRequestHop>): Promise<Result<ISaferFetchRequest>>;
|
|
5399
|
+
}
|
|
5400
|
+
|
|
5401
|
+
/**
|
|
5402
|
+
* One hop in a redirect chain. Entry 0 is the caller's original request.
|
|
5403
|
+
* @public
|
|
5404
|
+
*/
|
|
5405
|
+
declare interface IRequestHop {
|
|
5406
|
+
/** The URL under consideration for this hop. */
|
|
5407
|
+
readonly url: URL;
|
|
5408
|
+
/** The redirect status that produced the NEXT hop; absent on the current one. */
|
|
5409
|
+
readonly status?: number;
|
|
5410
|
+
/**
|
|
5411
|
+
* The address actually connected to on this hop, when address pinning was in effect.
|
|
5412
|
+
*
|
|
5413
|
+
* @remarks
|
|
5414
|
+
* Populated from the address guard's {@link SaferFetch.IGuardVerdict.pinnedAddress} once the
|
|
5415
|
+
* transport has accepted it — which is sound because a transport that cannot honor a pin is
|
|
5416
|
+
* required to fail rather than connect by hostname, so a completed request with a pin set is
|
|
5417
|
+
* evidence the pin held. **Undefined throughout this release**, since no shipped guard pins
|
|
5418
|
+
* and {@link SaferFetch.platformFetchTransport} cannot honor one; the field is where the
|
|
5419
|
+
* rebinding defense's per-hop evidence will live.
|
|
5420
|
+
*/
|
|
5421
|
+
readonly connectedAddress?: string;
|
|
5422
|
+
}
|
|
5423
|
+
|
|
4644
5424
|
/**
|
|
4645
5425
|
* Required version of options with all fields populated.
|
|
4646
5426
|
* @internal
|
|
@@ -4652,6 +5432,19 @@ declare interface IRequiredMustacheTemplateOptions {
|
|
|
4652
5432
|
readonly escape: MustacheEscapeStrategy;
|
|
4653
5433
|
}
|
|
4654
5434
|
|
|
5435
|
+
/**
|
|
5436
|
+
* The guards a call will actually use. Every field is concrete: defaults are applied once, at
|
|
5437
|
+
* the boundary, and nothing downstream branches on a guard's absence.
|
|
5438
|
+
* @public
|
|
5439
|
+
*/
|
|
5440
|
+
declare interface IResolvedGuards {
|
|
5441
|
+
/** Caller-supplied; there is no default. */
|
|
5442
|
+
readonly address: IAddressGuard;
|
|
5443
|
+
readonly request: IRequestGuard;
|
|
5444
|
+
readonly responseHeaders: IResponseHeadersGuard;
|
|
5445
|
+
readonly responseBody: IResponseBodyGuard;
|
|
5446
|
+
}
|
|
5447
|
+
|
|
4655
5448
|
/**
|
|
4656
5449
|
* The resolved, merged wire parameters for an image generation request.
|
|
4657
5450
|
* Built from the layered options and ready for provider-specific encoding.
|
|
@@ -4698,6 +5491,106 @@ declare interface IResolvedThinkingConfig {
|
|
|
4698
5491
|
readonly otherParams?: JsonObject;
|
|
4699
5492
|
}
|
|
4700
5493
|
|
|
5494
|
+
/**
|
|
5495
|
+
* Policy guard over the buffered response body.
|
|
5496
|
+
*
|
|
5497
|
+
* @remarks
|
|
5498
|
+
* Runs on the buffered body, after the size cap has been enforced — so it can never be handed
|
|
5499
|
+
* an unbounded stream.
|
|
5500
|
+
* @public
|
|
5501
|
+
*/
|
|
5502
|
+
declare interface IResponseBodyGuard {
|
|
5503
|
+
readonly name: string;
|
|
5504
|
+
check(body: Uint8Array, head: ISaferFetchResponseHead): Promise<Result<true>>;
|
|
5505
|
+
}
|
|
5506
|
+
|
|
5507
|
+
/**
|
|
5508
|
+
* Policy guard over the response head.
|
|
5509
|
+
*
|
|
5510
|
+
* @remarks
|
|
5511
|
+
* Runs before any body bytes are read, so a rejection costs a header comparison instead of a
|
|
5512
|
+
* partial body transfer. This is the right layer for content-type gating — see
|
|
5513
|
+
* {@link SaferFetch.allowContentTypes}.
|
|
5514
|
+
* @public
|
|
5515
|
+
*/
|
|
5516
|
+
declare interface IResponseHeadersGuard {
|
|
5517
|
+
readonly name: string;
|
|
5518
|
+
/**
|
|
5519
|
+
* The media types this guard accepts, when it is a content-type allowlist.
|
|
5520
|
+
*
|
|
5521
|
+
* @remarks
|
|
5522
|
+
* Present so that a rejection can be reported as `'unsupported-content-type'` carrying the
|
|
5523
|
+
* accepted list, rather than as an opaque policy rejection the caller has to parse a message
|
|
5524
|
+
* to understand. A guard that is not a content-type allowlist omits it and its rejections are
|
|
5525
|
+
* reported as `'blocked-by-guard'`.
|
|
5526
|
+
*/
|
|
5527
|
+
readonly acceptedContentTypes?: ReadonlyArray<string>;
|
|
5528
|
+
check(head: ISaferFetchResponseHead, chain: ReadonlyArray<IRequestHop>): Promise<Result<true>>;
|
|
5529
|
+
}
|
|
5530
|
+
|
|
5531
|
+
/**
|
|
5532
|
+
* Opt-in retry policy.
|
|
5533
|
+
*
|
|
5534
|
+
* @remarks
|
|
5535
|
+
* **Retry is off by default and has to be asked for.** A primitive that silently retries
|
|
5536
|
+
* changes the semantics of every call site and amplifies load against a service that is already
|
|
5537
|
+
* struggling.
|
|
5538
|
+
*
|
|
5539
|
+
* Two rules are load-bearing rather than incidental, and are worth reading before enabling this:
|
|
5540
|
+
*
|
|
5541
|
+
* **Every attempt re-runs the address guard from hop 0 — a full re-walk, never a resume and
|
|
5542
|
+
* never a cached verdict.** Reusing an earlier attempt's verdict would make retry its own
|
|
5543
|
+
* DNS-rebinding vector: an attacker needs the guard to pass once and a connect to land on a
|
|
5544
|
+
* private address, so `N` connects against **one** check multiplies their odds by the retry
|
|
5545
|
+
* count — and a retry delay is precisely when a short-TTL rebind lands. A resume is
|
|
5546
|
+
* independently wrong because the redirect chain is not stable across attempts: attempt 2's
|
|
5547
|
+
* server may answer with a different `Location`, so revalidating only the current hop would
|
|
5548
|
+
* validate a chain the request no longer follows. The consequence is that retries cost more
|
|
5549
|
+
* than a naive implementation would suggest — each carries full re-resolution — and that cost
|
|
5550
|
+
* is deliberate.
|
|
5551
|
+
*
|
|
5552
|
+
* **The overall deadline is the ceiling.** Retries consume `timeoutMs` and never reset it, so
|
|
5553
|
+
* `{ timeoutMs: 30_000, retry: { attempts: 3 } }` is a thirty-second call, not a two-minute
|
|
5554
|
+
* one. When the remaining budget is shorter than the computed delay, the call fails with the
|
|
5555
|
+
* attempt's own failure rather than sleeping past its deadline.
|
|
5556
|
+
* @public
|
|
5557
|
+
*/
|
|
5558
|
+
declare interface IRetryPolicy {
|
|
5559
|
+
/**
|
|
5560
|
+
* Additional attempts after the first, **not** a total. `attempts: 2` means up to three
|
|
5561
|
+
* requests. Must be a non-negative integer; `0` disables retry.
|
|
5562
|
+
*/
|
|
5563
|
+
readonly attempts: number;
|
|
5564
|
+
/**
|
|
5565
|
+
* First backoff delay, in milliseconds, doubled per attempt before jitter. Default
|
|
5566
|
+
* {@link SaferFetch.DEFAULT_RETRY_BASE_DELAY_MS}.
|
|
5567
|
+
*/
|
|
5568
|
+
readonly baseDelayMs?: number;
|
|
5569
|
+
/**
|
|
5570
|
+
* Ceiling on any single delay, in milliseconds. Default
|
|
5571
|
+
* {@link SaferFetch.DEFAULT_RETRY_MAX_DELAY_MS}. Also clamps a server-supplied
|
|
5572
|
+
* `Retry-After`, which is the security-relevant half of its job.
|
|
5573
|
+
*/
|
|
5574
|
+
readonly maxDelayMs?: number;
|
|
5575
|
+
/**
|
|
5576
|
+
* Retries `POST` / `PUT` / `PATCH` / `DELETE` as well as `GET` / `HEAD`. Default `false`.
|
|
5577
|
+
*
|
|
5578
|
+
* @remarks
|
|
5579
|
+
* Off by default because a timeout does not tell you whether the server processed the
|
|
5580
|
+
* request: a retried `POST` after a timeout can double-charge. Turn it on for endpoints you
|
|
5581
|
+
* know are idempotent — an idempotency key, a `PUT` of a whole resource — and not otherwise.
|
|
5582
|
+
*/
|
|
5583
|
+
readonly retryNonIdempotent?: boolean;
|
|
5584
|
+
/**
|
|
5585
|
+
* Honors a `Retry-After` header on `429` and `503`. Default `true`.
|
|
5586
|
+
*
|
|
5587
|
+
* @remarks
|
|
5588
|
+
* Always clamped to `maxDelayMs`, whatever this is set to. The header is chosen by the
|
|
5589
|
+
* server, which in this threat model may be the adversary.
|
|
5590
|
+
*/
|
|
5591
|
+
readonly respectRetryAfter?: boolean;
|
|
5592
|
+
}
|
|
5593
|
+
|
|
4701
5594
|
/**
|
|
4702
5595
|
* Determines whether a concrete (already-resolved) Anthropic model id uses the adaptive
|
|
4703
5596
|
* thinking wire shape (`thinking: { type: 'adaptive' }` + top-level `output_config.effort`)
|
|
@@ -4724,6 +5617,182 @@ declare interface IResolvedThinkingConfig {
|
|
|
4724
5617
|
*/
|
|
4725
5618
|
declare function isAdaptiveThinkingModel(descriptor: IAiProviderDescriptor, modelId: string): boolean;
|
|
4726
5619
|
|
|
5620
|
+
/**
|
|
5621
|
+
* Options for the validating form of {@link SaferFetch.saferFetchJson}.
|
|
5622
|
+
*
|
|
5623
|
+
* @remarks
|
|
5624
|
+
* `converter` is **required** here, and `T` is inferred from it. There is deliberately no way
|
|
5625
|
+
* to assert a `T` without supplying the converter that evidences it at runtime: a caller-named
|
|
5626
|
+
* type with nothing checking it is a claim the primitive cannot keep. Omit the whole options
|
|
5627
|
+
* type and the value comes back as `JsonValue`, which is what the wire actually guarantees.
|
|
5628
|
+
* @public
|
|
5629
|
+
*/
|
|
5630
|
+
declare interface ISaferFetchJsonOptions<T> extends ISaferFetchOptions {
|
|
5631
|
+
/** Applied to the parsed JSON, taking the caller from wire to validated `T` in one step. */
|
|
5632
|
+
readonly converter: Converter<T> | Validator<T>;
|
|
5633
|
+
}
|
|
5634
|
+
|
|
5635
|
+
/**
|
|
5636
|
+
* Options common to every safer-fetch entry point.
|
|
5637
|
+
* @public
|
|
5638
|
+
*/
|
|
5639
|
+
declare interface ISaferFetchOptions {
|
|
5640
|
+
/**
|
|
5641
|
+
* **Required, with no default.** A passthrough default here would be exactly the failure
|
|
5642
|
+
* this primitive exists to avoid — advertising a guarantee it does not have. Requiring it
|
|
5643
|
+
* makes omission a *compile* error rather than a runtime surprise or a lint rule, and makes
|
|
5644
|
+
* every call site's posture greppable in one search with no ambient default to overlook.
|
|
5645
|
+
*
|
|
5646
|
+
* Use a named factory. `allowAnyAddress()` is the explicit, deliberately uncomfortable
|
|
5647
|
+
* opt-out, and is the only correct choice in a browser, where neither DNS resolution nor
|
|
5648
|
+
* redirect interposition exists.
|
|
5649
|
+
*/
|
|
5650
|
+
readonly addressGuard: IAddressGuard;
|
|
5651
|
+
/**
|
|
5652
|
+
* Policy guard over the outbound request. Optional here and non-optional once resolved —
|
|
5653
|
+
* it defaults to silent passthrough, applied once at the boundary, so no downstream code
|
|
5654
|
+
* path branches on a guard's absence.
|
|
5655
|
+
*/
|
|
5656
|
+
readonly requestGuard?: IRequestGuard;
|
|
5657
|
+
/**
|
|
5658
|
+
* Policy guard over the response head. Defaults to silent passthrough. Content-type gating
|
|
5659
|
+
* lives here — see {@link SaferFetch.allowContentTypes}; it is deliberately not a separate
|
|
5660
|
+
* option, which would be a second mechanism for one job.
|
|
5661
|
+
*/
|
|
5662
|
+
readonly responseHeadersGuard?: IResponseHeadersGuard;
|
|
5663
|
+
/** Policy guard over the buffered body. Defaults to silent passthrough. */
|
|
5664
|
+
readonly responseBodyGuard?: IResponseBodyGuard;
|
|
5665
|
+
/** Defaults to `'GET'`. */
|
|
5666
|
+
readonly method?: SaferFetchMethod;
|
|
5667
|
+
/** Request headers. Names are lowercased before the request guard sees them. */
|
|
5668
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
5669
|
+
/** Request body. Not permitted with `'GET'` or `'HEAD'`. */
|
|
5670
|
+
readonly body?: string | Uint8Array;
|
|
5671
|
+
/**
|
|
5672
|
+
* Overall deadline in milliseconds, covering the connect, the response headers, and the
|
|
5673
|
+
* body read. Default {@link SaferFetch.DEFAULT_TIMEOUT_MS}. This is the deadline that stops
|
|
5674
|
+
* a response dribbling one byte every 25 seconds, which passes every per-read check.
|
|
5675
|
+
*/
|
|
5676
|
+
readonly timeoutMs?: number;
|
|
5677
|
+
/**
|
|
5678
|
+
* Deadline to first response headers, in milliseconds. Default
|
|
5679
|
+
* {@link SaferFetch.DEFAULT_HEADERS_TIMEOUT_MS}. Distinguishes "the host is not answering"
|
|
5680
|
+
* from "the host is answering slowly", which the failure taxonomy then reports as
|
|
5681
|
+
* `timeout.phase`.
|
|
5682
|
+
*
|
|
5683
|
+
* @remarks
|
|
5684
|
+
* Measured from the start of the attempt, which includes guard evaluation — an address guard
|
|
5685
|
+
* that resolves DNS spends this budget too. That is deliberate: the deadline bounds the time
|
|
5686
|
+
* a caller waits for a usable response, not the time one layer of the implementation spends.
|
|
5687
|
+
*/
|
|
5688
|
+
readonly headersTimeoutMs?: number;
|
|
5689
|
+
/**
|
|
5690
|
+
* Cap on decoded response bytes. Default {@link SaferFetch.DEFAULT_MAX_RESPONSE_BYTES}
|
|
5691
|
+
* (5 MiB).
|
|
5692
|
+
*
|
|
5693
|
+
* @remarks
|
|
5694
|
+
* **This knob is meant to be reached for.** 5 MiB is roughly an order of magnitude above a
|
|
5695
|
+
* realistic JSON API response and is generous for text, but real documents clear it — raise
|
|
5696
|
+
* it per call for the calls that need it rather than treating the default as a ceiling. The
|
|
5697
|
+
* cap is enforced by counting decoded bytes during the read, so raising it raises the
|
|
5698
|
+
* buffer the process may be asked to hold; size it against the heap you are willing to
|
|
5699
|
+
* spend, not against the largest document you can imagine.
|
|
5700
|
+
*/
|
|
5701
|
+
readonly maxResponseBytes?: number;
|
|
5702
|
+
/**
|
|
5703
|
+
* How redirects are handled. Defaults to `'reject'`.
|
|
5704
|
+
*
|
|
5705
|
+
* @remarks
|
|
5706
|
+
* The conservative default is deliberate, and is the same polarity as `addressGuard` having no
|
|
5707
|
+
* default: one core serves both runtimes, `'reject'` is the only mode whose guarantee is
|
|
5708
|
+
* identical on each, and following a redirect chain into hosts the caller never named is a
|
|
5709
|
+
* posture worth spelling at the call site. Callers ingesting real-world URLs want
|
|
5710
|
+
* `'validate-each-hop'`.
|
|
5711
|
+
*/
|
|
5712
|
+
readonly redirectPolicy?: SaferFetchRedirectPolicy;
|
|
5713
|
+
/**
|
|
5714
|
+
* Cap on redirect hops followed under `'validate-each-hop'`. Default
|
|
5715
|
+
* {@link SaferFetch.DEFAULT_MAX_REDIRECTS} (5). Must be a non-negative integer; `0` refuses
|
|
5716
|
+
* to follow any redirect.
|
|
5717
|
+
*/
|
|
5718
|
+
readonly maxRedirects?: number;
|
|
5719
|
+
/**
|
|
5720
|
+
* Additional header names to drop on a cross-origin redirect hop, on top of the always-dropped
|
|
5721
|
+
* `authorization`, `cookie` and `proxy-authorization`
|
|
5722
|
+
* ({@link SaferFetch.ALWAYS_STRIPPED_HEADERS}).
|
|
5723
|
+
*
|
|
5724
|
+
* @remarks
|
|
5725
|
+
* Matching is case-insensitive. Use this for bearer-equivalent headers a deployment invented —
|
|
5726
|
+
* `x-api-key`, `x-auth-token`, a signed-request header. Stripping is **monotonic**: once a hop
|
|
5727
|
+
* has left an origin the headers are gone for the rest of the chain, so an `A` → `B` → `A`
|
|
5728
|
+
* chain does not hand the credential back to `A` after `B` has watched it leave.
|
|
5729
|
+
*/
|
|
5730
|
+
readonly sensitiveHeaders?: ReadonlyArray<string>;
|
|
5731
|
+
/**
|
|
5732
|
+
* Opt-in retry. **Off by default** — a primitive that silently retries changes the semantics
|
|
5733
|
+
* of every call site and amplifies load against a service that is already struggling.
|
|
5734
|
+
*
|
|
5735
|
+
* @remarks
|
|
5736
|
+
* Retries consume `timeoutMs` and never reset it, so enabling retry does not extend the
|
|
5737
|
+
* deadline; and every attempt re-runs the address guard from hop 0 as a full re-walk, never a
|
|
5738
|
+
* resume and never a cached verdict. Both rules are load-bearing rather than incidental — see
|
|
5739
|
+
* {@link SaferFetch.IRetryPolicy}, which states why.
|
|
5740
|
+
*/
|
|
5741
|
+
readonly retry?: IRetryPolicy;
|
|
5742
|
+
/** Defaults to {@link SaferFetch.platformFetchTransport}. */
|
|
5743
|
+
readonly transport?: IFetchTransport;
|
|
5744
|
+
/** Caller's cancellation signal. Reported as `'aborted'`, never as `'timeout'`. */
|
|
5745
|
+
readonly signal?: AbortSignal;
|
|
5746
|
+
/** Diagnostics sink. Defaults to a no-op logger. */
|
|
5747
|
+
readonly logger?: Logging.ILogger;
|
|
5748
|
+
}
|
|
5749
|
+
|
|
5750
|
+
/**
|
|
5751
|
+
* The request a safer-fetch call is about to issue.
|
|
5752
|
+
* @public
|
|
5753
|
+
*/
|
|
5754
|
+
declare interface ISaferFetchRequest {
|
|
5755
|
+
readonly url: URL;
|
|
5756
|
+
readonly method: SaferFetchMethod;
|
|
5757
|
+
/** Header names are lowercased. */
|
|
5758
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
5759
|
+
readonly body?: string | Uint8Array;
|
|
5760
|
+
}
|
|
5761
|
+
|
|
5762
|
+
/**
|
|
5763
|
+
* A successful safer-fetch response.
|
|
5764
|
+
* @public
|
|
5765
|
+
*/
|
|
5766
|
+
declare interface ISaferFetchResponse<T> {
|
|
5767
|
+
readonly value: T;
|
|
5768
|
+
readonly status: number;
|
|
5769
|
+
/** Header names are lowercased. */
|
|
5770
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
5771
|
+
/**
|
|
5772
|
+
* Every URL actually requested, in order, as cleared by the address guard. `[0]` is the
|
|
5773
|
+
* caller's; later entries are redirect hops. A caller that allowlisted `api.example.com`
|
|
5774
|
+
* and was redirected to `cdn.example.com` usually wants to know.
|
|
5775
|
+
*/
|
|
5776
|
+
readonly urlChain: ReadonlyArray<string>;
|
|
5777
|
+
/** Decoded bytes read from the response body. */
|
|
5778
|
+
readonly bytesRead: number;
|
|
5779
|
+
}
|
|
5780
|
+
|
|
5781
|
+
/**
|
|
5782
|
+
* Response status and headers, available before any body bytes are read.
|
|
5783
|
+
* @public
|
|
5784
|
+
*/
|
|
5785
|
+
declare interface ISaferFetchResponseHead {
|
|
5786
|
+
readonly status: number;
|
|
5787
|
+
readonly statusText: string;
|
|
5788
|
+
/** Header names are lowercased. */
|
|
5789
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
5790
|
+
/** Raw `content-type` header value, including any parameters. */
|
|
5791
|
+
readonly contentType?: string;
|
|
5792
|
+
/** Parsed `content-length`, present only when the header was sent and is a valid count. */
|
|
5793
|
+
readonly contentLength?: number;
|
|
5794
|
+
}
|
|
5795
|
+
|
|
4727
5796
|
/**
|
|
4728
5797
|
* Checks if a JSON object appears to be an encrypted file.
|
|
4729
5798
|
* Uses the format field as a discriminator.
|
|
@@ -6365,6 +7434,24 @@ declare class NodeCryptoProvider implements ICryptoProvider {
|
|
|
6365
7434
|
*/
|
|
6366
7435
|
declare const nodeCryptoProvider: NodeCryptoProvider;
|
|
6367
7436
|
|
|
7437
|
+
/**
|
|
7438
|
+
* The default {@link SaferFetch.HostResolver}: `node:dns`'s `lookup`, returning every address.
|
|
7439
|
+
*
|
|
7440
|
+
* @remarks
|
|
7441
|
+
* `lookup` rather than `resolve4`/`resolve6` deliberately. `lookup` goes through the operating
|
|
7442
|
+
* system's resolver — the same path `fetch`'s connect takes — so it sees `/etc/hosts`, `nsswitch`
|
|
7443
|
+
* ordering, and any local override. `resolve4` queries DNS directly and would miss a
|
|
7444
|
+
* `hosts`-file entry pointing an allowlisted name at `127.0.0.1`, which is a bypass rather than
|
|
7445
|
+
* a curiosity.
|
|
7446
|
+
*
|
|
7447
|
+
* `all: true` because the list contract is reject-if-any: a name resolving to one public and one
|
|
7448
|
+
* private address must be refused, and asking for one address would hide the second.
|
|
7449
|
+
*
|
|
7450
|
+
* A rejected lookup (`ENOTFOUND`, `EAI_AGAIN`, …) becomes a `Failure`, never a throw.
|
|
7451
|
+
* @public
|
|
7452
|
+
*/
|
|
7453
|
+
declare const nodeHostResolver: HostResolver;
|
|
7454
|
+
|
|
6368
7455
|
/**
|
|
6369
7456
|
* Model IDs for OpenAI thinking-capable models.
|
|
6370
7457
|
* @public
|
|
@@ -6396,6 +7483,23 @@ declare function parseRecordJarLines(lines: string[], options?: JarRecordParserO
|
|
|
6396
7483
|
*/
|
|
6397
7484
|
declare const pbkdf2KeyDerivationParams: Converter<IPbkdf2KeyDerivationParams>;
|
|
6398
7485
|
|
|
7486
|
+
/**
|
|
7487
|
+
* The {@link SaferFetch.IFetchTransport | transport} that wraps `globalThis.fetch`.
|
|
7488
|
+
*
|
|
7489
|
+
* @remarks
|
|
7490
|
+
* **Fails loudly when asked to honor a `pinnedAddress`.** `globalThis.fetch` connects by
|
|
7491
|
+
* hostname and re-resolves DNS itself, so it cannot honor a pin. Ignoring the hint instead
|
|
7492
|
+
* would mean that a deployment which wired an address-pinning guard but forgot the matching
|
|
7493
|
+
* pinning transport gets exactly the DNS-rebinding exposure it believed it had closed, and
|
|
7494
|
+
* gets it silently. A primitive that advertises a guarantee it does not have is worse than no
|
|
7495
|
+
* primitive — and that applies to future versions of itself.
|
|
7496
|
+
*
|
|
7497
|
+
* No guard in this release populates `pinnedAddress`, so the interlock is dormant. It is here
|
|
7498
|
+
* so that populating it later is an additive change that cannot be made unsafely.
|
|
7499
|
+
* @public
|
|
7500
|
+
*/
|
|
7501
|
+
declare const platformFetchTransport: IFetchTransport;
|
|
7502
|
+
|
|
6399
7503
|
/**
|
|
6400
7504
|
* Returns the canonical `CryptoUtils.KeyStore.KeyStore` secret name for a provider's API key,
|
|
6401
7505
|
* of the form `provider:<providerId>`. Apps that store provider API keys in a `KeyStore`
|
|
@@ -6594,6 +7698,13 @@ declare namespace RecordJar {
|
|
|
6594
7698
|
}
|
|
6595
7699
|
export { RecordJar }
|
|
6596
7700
|
|
|
7701
|
+
/**
|
|
7702
|
+
* Redirect statuses this primitive recognizes as redirects. Other 3xx statuses (`300`, `304`,
|
|
7703
|
+
* …) do not direct the client at a new URL and are reported as ordinary non-2xx responses.
|
|
7704
|
+
* @public
|
|
7705
|
+
*/
|
|
7706
|
+
declare const REDIRECT_STATUSES: ReadonlyArray<number>;
|
|
7707
|
+
|
|
6597
7708
|
/**
|
|
6598
7709
|
* Resolves the effective tools for a completion call.
|
|
6599
7710
|
*
|
|
@@ -6759,6 +7870,220 @@ declare function resolveModelAlias(descriptor: IAiProviderDescriptor, model: str
|
|
|
6759
7870
|
*/
|
|
6760
7871
|
declare function resolveProviderModel(descriptor: IAiProviderDescriptor, modelOverride: ModelSpec | undefined, context?: ModelSpecKey): Result<string>;
|
|
6761
7872
|
|
|
7873
|
+
/**
|
|
7874
|
+
* Statuses whose `Retry-After` header is honored.
|
|
7875
|
+
*
|
|
7876
|
+
* @remarks
|
|
7877
|
+
* `429` and `503` are the two the header is specified for and the two where it carries real
|
|
7878
|
+
* scheduling information. Honoring it on a `500` would let any failing endpoint dictate the
|
|
7879
|
+
* caller's schedule for no benefit.
|
|
7880
|
+
* @public
|
|
7881
|
+
*/
|
|
7882
|
+
declare const RETRY_AFTER_STATUSES: ReadonlyArray<number>;
|
|
7883
|
+
|
|
7884
|
+
/**
|
|
7885
|
+
* HTTP statuses a {@link SaferFetch.IRetryPolicy | retry policy} will retry.
|
|
7886
|
+
*
|
|
7887
|
+
* @remarks
|
|
7888
|
+
* Every other status — including every other 4xx — is terminal. A `401`, a `403` and a `404`
|
|
7889
|
+
* mean the same thing on the second attempt as on the first, and retrying them turns a client
|
|
7890
|
+
* bug into load against a service that already said no.
|
|
7891
|
+
* @public
|
|
7892
|
+
*/
|
|
7893
|
+
declare const RETRYABLE_HTTP_STATUSES: ReadonlyArray<number>;
|
|
7894
|
+
|
|
7895
|
+
declare namespace SaferFetch {
|
|
7896
|
+
export {
|
|
7897
|
+
ALWAYS_STRIPPED_HEADERS,
|
|
7898
|
+
DEFAULT_HEADERS_TIMEOUT_MS,
|
|
7899
|
+
DEFAULT_MAX_REDIRECTS,
|
|
7900
|
+
DEFAULT_MAX_RESPONSE_BYTES,
|
|
7901
|
+
DEFAULT_RETRY_BASE_DELAY_MS,
|
|
7902
|
+
DEFAULT_RETRY_MAX_DELAY_MS,
|
|
7903
|
+
DEFAULT_TIMEOUT_MS,
|
|
7904
|
+
IDEMPOTENT_METHODS,
|
|
7905
|
+
REDIRECT_STATUSES,
|
|
7906
|
+
RETRY_AFTER_STATUSES,
|
|
7907
|
+
RETRYABLE_HTTP_STATUSES,
|
|
7908
|
+
SUPPORTED_SCHEMES,
|
|
7909
|
+
FetchFailureReason,
|
|
7910
|
+
FetchTimeoutPhase,
|
|
7911
|
+
IRetryPolicy,
|
|
7912
|
+
IAddressGuard,
|
|
7913
|
+
IFetchTransport,
|
|
7914
|
+
IFetchTransportHints,
|
|
7915
|
+
IGuardVerdict,
|
|
7916
|
+
IRequestGuard,
|
|
7917
|
+
IRequestHop,
|
|
7918
|
+
IResolvedGuards,
|
|
7919
|
+
IResponseBodyGuard,
|
|
7920
|
+
IResponseHeadersGuard,
|
|
7921
|
+
ISaferFetchOptions,
|
|
7922
|
+
ISaferFetchRequest,
|
|
7923
|
+
ISaferFetchResponse,
|
|
7924
|
+
ISaferFetchResponseHead,
|
|
7925
|
+
SaferFetchMethod,
|
|
7926
|
+
SaferFetchRedirectPolicy,
|
|
7927
|
+
allowAnyAddress,
|
|
7928
|
+
allowContentTypes,
|
|
7929
|
+
platformFetchTransport,
|
|
7930
|
+
saferFetchBytes,
|
|
7931
|
+
saferFetchJson,
|
|
7932
|
+
saferFetchText,
|
|
7933
|
+
ISaferFetchJsonOptions,
|
|
7934
|
+
allowAnyAddressPolicy,
|
|
7935
|
+
blockPrivateNetworksPolicy,
|
|
7936
|
+
IAddressCheckVerdict,
|
|
7937
|
+
IAddressPolicy,
|
|
7938
|
+
IBlockPrivateNetworksOptions,
|
|
7939
|
+
blockPrivateNetworks,
|
|
7940
|
+
nodeHostResolver,
|
|
7941
|
+
HostResolver,
|
|
7942
|
+
IBlockPrivateNetworksGuardOptions,
|
|
7943
|
+
classifyAddress,
|
|
7944
|
+
AddressClassification,
|
|
7945
|
+
AddressFamily,
|
|
7946
|
+
IClassifiedAddress,
|
|
7947
|
+
IEmbeddedIpv4,
|
|
7948
|
+
Ipv4EmbeddingKind
|
|
7949
|
+
}
|
|
7950
|
+
}
|
|
7951
|
+
export { SaferFetch }
|
|
7952
|
+
|
|
7953
|
+
/**
|
|
7954
|
+
* Fetches a URL and returns the raw response bytes, subject to the scheme check, the address
|
|
7955
|
+
* guard, the request and response guards, the deadlines, and the response size cap.
|
|
7956
|
+
*
|
|
7957
|
+
* @remarks
|
|
7958
|
+
* **What this does not protect against.** It is not a sandbox: a caller who wants to reach
|
|
7959
|
+
* `127.0.0.1` can call `globalThis.fetch` directly, so this guards untrusted *data*, never
|
|
7960
|
+
* untrusted *code in the same process*. It does no certificate pinning and adds nothing
|
|
7961
|
+
* against a network-position attacker — TLS is that control. It does not resist DNS rebinding:
|
|
7962
|
+
* an address guard validates a resolved address and the transport then re-resolves, so a
|
|
7963
|
+
* hostile resolver can answer the two lookups differently. And it inspects nothing in the
|
|
7964
|
+
* response body. If your deployment has an egress proxy or firewall, that control is strictly
|
|
7965
|
+
* stronger than this one; this is defense in depth for deployments that do not.
|
|
7966
|
+
*
|
|
7967
|
+
* **The failure detail is an internal-network scanning oracle.** Log it; do not echo it, or
|
|
7968
|
+
* any string derived from it, to an untrusted caller. The detail is structured precisely so
|
|
7969
|
+
* that mapping it to a coarse public code is trivial.
|
|
7970
|
+
*
|
|
7971
|
+
* **Redirects are rejected unless you ask for them.** `redirectPolicy: 'validate-each-hop'`
|
|
7972
|
+
* follows them, runs the address guard on every hop before any connection, and drops credential
|
|
7973
|
+
* headers the first time the chain leaves an origin — and never restores them, so an
|
|
7974
|
+
* `A` → `B` → `A` chain does not hand the token back to `A`.
|
|
7975
|
+
*
|
|
7976
|
+
* @param url - The URL to fetch. Only `http:` and `https:` are ever requested; every other
|
|
7977
|
+
* scheme fails as `'invalid-url'`.
|
|
7978
|
+
* @param options - Call options. `addressGuard` is required and has no default — use
|
|
7979
|
+
* `blockPrivateNetworks()` on Node, or `allowAnyAddress()` to name the absence of the
|
|
7980
|
+
* guarantee at the call site. `maxResponseBytes` defaults to 5 MiB and is meant to be tuned
|
|
7981
|
+
* per call: real documents clear that, and raising it for the calls that need it is the
|
|
7982
|
+
* intended use, not a workaround.
|
|
7983
|
+
* @public
|
|
7984
|
+
*/
|
|
7985
|
+
declare function saferFetchBytes(url: string | URL, options: ISaferFetchOptions): Promise<DetailedResult<ISaferFetchResponse<Uint8Array>, FetchFailureReason>>;
|
|
7986
|
+
|
|
7987
|
+
/**
|
|
7988
|
+
* Fetches a URL and parses the response body as JSON, yielding the raw `JsonValue` for the
|
|
7989
|
+
* caller to validate.
|
|
7990
|
+
*
|
|
7991
|
+
* @remarks
|
|
7992
|
+
* **This does not gate on `Content-Type` by itself.** A server returning an HTML error page
|
|
7993
|
+
* with a `200` will be decoded and parsed, and fail as `'parse'` — twenty frames from where it
|
|
7994
|
+
* actually went wrong. Pass `responseHeadersGuard: allowContentTypes(['application/json'])`
|
|
7995
|
+
* to reject on the header instead: it costs a header comparison rather than a body transfer,
|
|
7996
|
+
* and the failure names the type. Content-type gating is a guard rather than an option so that
|
|
7997
|
+
* there is exactly one mechanism for it.
|
|
7998
|
+
*
|
|
7999
|
+
* See {@link SaferFetch.saferFetchBytes} for what this primitive does **not** protect against,
|
|
8000
|
+
* and for the warning about echoing failure detail to untrusted callers.
|
|
8001
|
+
*
|
|
8002
|
+
* @param url - The URL to fetch.
|
|
8003
|
+
* @param options - Call options. `addressGuard` is required. `maxResponseBytes` defaults to
|
|
8004
|
+
* 5 MiB and is meant to be tuned per call.
|
|
8005
|
+
* @public
|
|
8006
|
+
*/
|
|
8007
|
+
declare function saferFetchJson(url: string | URL, options: ISaferFetchOptions): Promise<DetailedResult<ISaferFetchResponse<JsonValue>, FetchFailureReason>>;
|
|
8008
|
+
|
|
8009
|
+
/**
|
|
8010
|
+
* Fetches a URL, parses the response body as JSON, and runs it through the supplied converter
|
|
8011
|
+
* or validator so the caller reaches a validated `T` in one step.
|
|
8012
|
+
*
|
|
8013
|
+
* @remarks
|
|
8014
|
+
* `T` is inferred from `converter` and is never caller-asserted, so the returned type is
|
|
8015
|
+
* evidenced at runtime rather than claimed.
|
|
8016
|
+
*
|
|
8017
|
+
* See {@link SaferFetch.saferFetchBytes} for what this primitive does **not** protect against,
|
|
8018
|
+
* and for the warning about echoing failure detail to untrusted callers.
|
|
8019
|
+
*
|
|
8020
|
+
* @param url - The URL to fetch.
|
|
8021
|
+
* @param options - Call options plus the required `converter`. `addressGuard` is required.
|
|
8022
|
+
* `maxResponseBytes` defaults to 5 MiB and is meant to be tuned per call.
|
|
8023
|
+
* @public
|
|
8024
|
+
*/
|
|
8025
|
+
declare function saferFetchJson<T>(url: string | URL, options: ISaferFetchJsonOptions<T>): Promise<DetailedResult<ISaferFetchResponse<T>, FetchFailureReason>>;
|
|
8026
|
+
|
|
8027
|
+
/**
|
|
8028
|
+
* HTTP methods a safer-fetch call may use.
|
|
8029
|
+
* @public
|
|
8030
|
+
*/
|
|
8031
|
+
declare type SaferFetchMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
8032
|
+
|
|
8033
|
+
/**
|
|
8034
|
+
* How redirects are handled.
|
|
8035
|
+
*
|
|
8036
|
+
* @remarks
|
|
8037
|
+
* `'reject'` fails on any redirect status. It is the default, and the only mode with an
|
|
8038
|
+
* equivalent guarantee on every runtime — a browser cannot inspect a redirect hop at all, so a
|
|
8039
|
+
* per-hop revalidating mode is not implementable there.
|
|
8040
|
+
*
|
|
8041
|
+
* The *guarantee* is equivalent on both runtimes; the failure **reason** is not. Every call uses
|
|
8042
|
+
* `redirect: 'manual'`, so on Node a rejected redirect surfaces as `'redirect-rejected'` carrying
|
|
8043
|
+
* the status, while in a browser the response is opaque — `type` is `'opaqueredirect'` and
|
|
8044
|
+
* `status` is `0`, so there is no status to report — and the same redirect surfaces as
|
|
8045
|
+
* `'redirect-opaque'`. Callers that branch on the reason under `'reject'` must handle both.
|
|
8046
|
+
*
|
|
8047
|
+
* `'validate-each-hop'` follows redirects with `redirect: 'manual'` and runs the **full address
|
|
8048
|
+
* guard on every hop before any connection is made**, resolving `Location` against the hop that
|
|
8049
|
+
* sent it. Redirect handling and the address check are one mechanism, not two: a guard that
|
|
8050
|
+
* validated only the caller's URL is defeated by a single `302` to `http://169.254.169.254/`.
|
|
8051
|
+
* Following hops also makes this primitive responsible for credential stripping — see
|
|
8052
|
+
* `ISaferFetchOptions.sensitiveHeaders`.
|
|
8053
|
+
*
|
|
8054
|
+
* **`'validate-each-hop'` is accepted by the browser barrel but cannot succeed there, and fails
|
|
8055
|
+
* loudly rather than degrading.** The type is shared because one core serves both runtimes; the
|
|
8056
|
+
* runtime is not. A browser's `redirect: 'manual'` yields an opaque response — `type` is
|
|
8057
|
+
* `'opaqueredirect'`, `status` is `0`, and `Location` is not readable — so the first redirect
|
|
8058
|
+
* fails as `'redirect-opaque'`. That is the honest outcome: the hop information does not exist
|
|
8059
|
+
* on the browser side of the API, so there is nothing to guard and nothing to follow. Use
|
|
8060
|
+
* `'reject'` there, or handle `'redirect-opaque'`.
|
|
8061
|
+
*
|
|
8062
|
+
* A mode that defers to the platform's own redirect following is deliberately absent on Node:
|
|
8063
|
+
* it would put hops on the wire that the guard never saw.
|
|
8064
|
+
* @public
|
|
8065
|
+
*/
|
|
8066
|
+
declare type SaferFetchRedirectPolicy = 'reject' | 'validate-each-hop';
|
|
8067
|
+
|
|
8068
|
+
/**
|
|
8069
|
+
* Fetches a URL and decodes the response body as text.
|
|
8070
|
+
*
|
|
8071
|
+
* @remarks
|
|
8072
|
+
* The charset comes from the `Content-Type` parameter and defaults to UTF-8. Decoding is
|
|
8073
|
+
* strict: an unknown charset, or a byte sequence that is not valid in the declared one, fails
|
|
8074
|
+
* as `'decode'` rather than silently producing mojibake that some caller downstream treats as
|
|
8075
|
+
* data.
|
|
8076
|
+
*
|
|
8077
|
+
* See {@link SaferFetch.saferFetchBytes} for what this primitive does **not** protect against,
|
|
8078
|
+
* and for the warning about echoing failure detail to untrusted callers.
|
|
8079
|
+
*
|
|
8080
|
+
* @param url - The URL to fetch.
|
|
8081
|
+
* @param options - Call options. `addressGuard` is required. `maxResponseBytes` defaults to
|
|
8082
|
+
* 5 MiB and is meant to be tuned per call.
|
|
8083
|
+
* @public
|
|
8084
|
+
*/
|
|
8085
|
+
declare function saferFetchText(url: string | URL, options: ISaferFetchOptions): Promise<DetailedResult<ISaferFetchResponse<string>, FetchFailureReason>>;
|
|
8086
|
+
|
|
6762
8087
|
/**
|
|
6763
8088
|
* Function type for dynamic secret retrieval.
|
|
6764
8089
|
* @public
|
|
@@ -6811,6 +8136,18 @@ declare const SMART_JSON_PROMPT_HINT: string;
|
|
|
6811
8136
|
*/
|
|
6812
8137
|
declare function spkiToRawX25519(spki: Uint8Array): Result<Uint8Array>;
|
|
6813
8138
|
|
|
8139
|
+
/**
|
|
8140
|
+
* URL schemes this primitive will ever request.
|
|
8141
|
+
*
|
|
8142
|
+
* @remarks
|
|
8143
|
+
* Core rejects everything else — `file:`, `data:`, `blob:`, `ftp:`, `gopher:`, `ws:` — outright
|
|
8144
|
+
* and at every hop, because none of them is a legitimate network fetch and each is a standard
|
|
8145
|
+
* SSRF payload. Deciding between `http:` and `https:`, and which ports are acceptable, belongs
|
|
8146
|
+
* to the address guard, which is why core does not narrow this further.
|
|
8147
|
+
* @public
|
|
8148
|
+
*/
|
|
8149
|
+
declare const SUPPORTED_SCHEMES: ReadonlyArray<string>;
|
|
8150
|
+
|
|
6814
8151
|
/**
|
|
6815
8152
|
* Whether a provider declares any embedding capability at all.
|
|
6816
8153
|
*
|