@rexezuge/shared 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/dist/Base64.d.ts +52 -0
  3. package/dist/Base64.d.ts.map +1 -0
  4. package/dist/Base64.js +81 -0
  5. package/dist/Base64.js.map +1 -0
  6. package/dist/Clock.d.ts +56 -0
  7. package/dist/Clock.d.ts.map +1 -0
  8. package/dist/Clock.js +59 -0
  9. package/dist/Clock.js.map +1 -0
  10. package/dist/CryptoUtil.d.ts +79 -0
  11. package/dist/CryptoUtil.d.ts.map +1 -0
  12. package/dist/CryptoUtil.js +97 -0
  13. package/dist/CryptoUtil.js.map +1 -0
  14. package/dist/CursorCodec.d.ts +61 -0
  15. package/dist/CursorCodec.d.ts.map +1 -0
  16. package/dist/CursorCodec.js +60 -0
  17. package/dist/CursorCodec.js.map +1 -0
  18. package/dist/EmailUtil.d.ts +29 -0
  19. package/dist/EmailUtil.d.ts.map +1 -0
  20. package/dist/EmailUtil.js +31 -0
  21. package/dist/EmailUtil.js.map +1 -0
  22. package/dist/ErrorSanitizationUtil.d.ts +69 -0
  23. package/dist/ErrorSanitizationUtil.d.ts.map +1 -0
  24. package/dist/ErrorSanitizationUtil.js +98 -0
  25. package/dist/ErrorSanitizationUtil.js.map +1 -0
  26. package/dist/IdGenerator.d.ts +27 -0
  27. package/dist/IdGenerator.d.ts.map +1 -0
  28. package/dist/IdGenerator.js +28 -0
  29. package/dist/IdGenerator.js.map +1 -0
  30. package/dist/Identity.d.ts +36 -0
  31. package/dist/Identity.d.ts.map +1 -0
  32. package/dist/Identity.js +39 -0
  33. package/dist/Identity.js.map +1 -0
  34. package/dist/LanguageTag.d.ts +25 -0
  35. package/dist/LanguageTag.d.ts.map +1 -0
  36. package/dist/LanguageTag.js +29 -0
  37. package/dist/LanguageTag.js.map +1 -0
  38. package/dist/LocaleUtil.d.ts +35 -0
  39. package/dist/LocaleUtil.d.ts.map +1 -0
  40. package/dist/LocaleUtil.js +97 -0
  41. package/dist/LocaleUtil.js.map +1 -0
  42. package/dist/PasswordFingerprint.d.ts +30 -0
  43. package/dist/PasswordFingerprint.d.ts.map +1 -0
  44. package/dist/PasswordFingerprint.js +33 -0
  45. package/dist/PasswordFingerprint.js.map +1 -0
  46. package/dist/RemoteUrlPolicy.d.ts +121 -0
  47. package/dist/RemoteUrlPolicy.d.ts.map +1 -0
  48. package/dist/RemoteUrlPolicy.js +339 -0
  49. package/dist/RemoteUrlPolicy.js.map +1 -0
  50. package/dist/Result.d.ts +32 -0
  51. package/dist/Result.d.ts.map +1 -0
  52. package/dist/Result.js +19 -0
  53. package/dist/Result.js.map +1 -0
  54. package/dist/SubrequestMeter.d.ts +204 -0
  55. package/dist/SubrequestMeter.d.ts.map +1 -0
  56. package/dist/SubrequestMeter.js +169 -0
  57. package/dist/SubrequestMeter.js.map +1 -0
  58. package/dist/TimeZoneUtil.d.ts +47 -0
  59. package/dist/TimeZoneUtil.d.ts.map +1 -0
  60. package/dist/TimeZoneUtil.js +101 -0
  61. package/dist/TimeZoneUtil.js.map +1 -0
  62. package/dist/TimestampUtil.d.ts +53 -0
  63. package/dist/TimestampUtil.d.ts.map +1 -0
  64. package/dist/TimestampUtil.js +69 -0
  65. package/dist/TimestampUtil.js.map +1 -0
  66. package/dist/TokenHashUtil.d.ts +34 -0
  67. package/dist/TokenHashUtil.d.ts.map +1 -0
  68. package/dist/TokenHashUtil.js +37 -0
  69. package/dist/TokenHashUtil.js.map +1 -0
  70. package/dist/UUIDUtil.d.ts +70 -0
  71. package/dist/UUIDUtil.d.ts.map +1 -0
  72. package/dist/UUIDUtil.js +79 -0
  73. package/dist/UUIDUtil.js.map +1 -0
  74. package/dist/index.d.ts +38 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +42 -0
  77. package/dist/index.js.map +1 -0
  78. package/package.json +34 -3
  79. package/README.md +0 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasswordFingerprint.js","sourceRoot":"","sources":["../src/PasswordFingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,KAAK,UAAU,mBAAmB,CAAC,QAAgB;IACjD,OAAO,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;AACxC,CAAC;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Egress policy and SSRF host predicates for user-supplied URLs.
3
+ *
4
+ * ## Provenance
5
+ *
6
+ * Merged from Durable-DAV's `packages/shared/src/net/RemoteUrlPolicy.ts` and the
7
+ * `SsrfHosts.ts` of Durable-DAV-Router, Edge-Git and Edge-Sonic. The two were the
8
+ * same problem solved twice: Durable-DAV's `RemoteUrlPolicy` is the full
9
+ * validate-and-canonicalize policy (HTTPS-only, public-address-only, operator
10
+ * allow-list, redirect re-validation); the Router/Edge-Git/Edge-Sonic
11
+ * `SsrfHosts` are the lower-level "is this host private?" predicate the policy in
12
+ * each repo leaned on. Canonical name is **`RemoteUrlPolicy`** (Durable-DAV's);
13
+ * the host predicate it converges onto is exported as **`isPrivateOrInternalHost`**
14
+ * (the common `isSsrfBlockedHost`-style composite from SsrfHosts).
15
+ *
16
+ * Scheduled replication / webhook delivery / git import turns a caller into
17
+ * someone who can name a host this Worker connects to. Without a policy that is
18
+ * server-side request forgery against Cloudflare's own egress: the metadata
19
+ * service, the loopback interface, and anything reachable on a private network the
20
+ * Worker happens to share.
21
+ *
22
+ * ## Design decisions
23
+ *
24
+ * - **The octet-accurate IP checks win over SsrfHosts' naive string prefixes.**
25
+ * SsrfHosts' `isBlockedIpv6Host` matched `startsWith('fc')`/`'fd')`, which also
26
+ * matches a *hostname* like `fcb.example.com`; Durable-DAV's `isBlockedIpv6` /
27
+ * `isBlockedIpv4` parse the address (and decode IPv4-mapped IPv6) instead. The
28
+ * kit keeps the rigorous form for both the URL policy and the exported
29
+ * predicate, so there is one correct answer rather than two that drift.
30
+ * - **`isPrivateOrInternalHost` is strictly safer than either source.** It folds
31
+ * in SsrfHosts' raw-hostname handling (bare-integer/octal/hex/`0x`-dotted forms,
32
+ * trailing-dot and bracket stripping, localhost names and `.local`/`.internal`/
33
+ * `.home.arpa` suffixes) that Durable-DAV's URL-path checks could skip because
34
+ * `new URL` had already canonicalized the host. A public IPv6 literal is allowed
35
+ * exactly as `normalizeRemoteUrl` allows it; every private/loopback/unique-local/
36
+ * link-local/zone/mapped form is refused.
37
+ *
38
+ * ## Known gap, stated rather than hidden
39
+ *
40
+ * DNS rebinding is not covered. A host that resolves to a public address at
41
+ * validation time and a private one when the connection is made defeats every
42
+ * check here. Closing it needs a resolution-aware fetch, which the platform does
43
+ * not expose to a Worker. The upheld rule for such a deployment is to pin an
44
+ * origin allow-list.
45
+ */
46
+ declare const MAX_REMOTE_URL_LENGTH = 2048;
47
+ declare const MAX_REDIRECTS = 3;
48
+ /**
49
+ * Host suffixes that never name a real internet peer.
50
+ */
51
+ declare const BLOCKED_HOST_SUFFIXES: readonly [".localhost", ".local", ".internal", ".home.arpa"];
52
+ /**
53
+ * Hostnames that resolve inward by definition.
54
+ */
55
+ declare const BLOCKED_HOSTNAMES: Set<string>;
56
+ type UrlPolicyOptions = {
57
+ /**
58
+ * Hostnames (exact, or `.suffix` form) permitted regardless of the address
59
+ * rules. Only ever populated from operator configuration, never from the
60
+ * request body.
61
+ */
62
+ allowedHosts?: readonly string[];
63
+ };
64
+ declare class RemoteUrlRejectedError extends Error {
65
+ constructor(message: string);
66
+ }
67
+ /**
68
+ * Strip one enclosing `[`…`]` pair (an IPv6 literal's URL brackets).
69
+ */
70
+ declare function stripBrackets(host: string): string;
71
+ /**
72
+ * Strip a bracketed wrapper and every trailing root-label dot.
73
+ *
74
+ * A fully-qualified name ends in a root label (`example.com.`) and `new URL` keeps
75
+ * it; RFC 4918 §9.1.2 clients vary on sending it, so the canonical form is compared.
76
+ */
77
+ declare function stripTrailingDot(host: string): string;
78
+ /**
79
+ * True when a URL host resolves to this machine or a private network.
80
+ *
81
+ * Intended for user-supplied URLs that the server will subsequently fetch. Such a
82
+ * URL turns the process into a proxy into its own network: without this, a
83
+ * user-supplied `baseUrl` can point at cloud metadata, a database host, or a
84
+ * loopback admin port, and the response is readable by the caller.
85
+ *
86
+ * Encoded-numeric hosts are included because `http://2130706433/` and
87
+ * `http://0177.0.0.1/` are ordinary-looking strings that name `127.0.0.1` after
88
+ * parsing.
89
+ *
90
+ * A *strict, safer* predicate: an empty host, any private/loopback/link-local/
91
+ * unique-local/mapped form, any localhost/suffix name, any obfuscated numeric
92
+ * form, and any `:`-bearing host that is not a public IPv6 literal all return
93
+ * true. It is a *syntactic* check — a public hostname can still resolve to a
94
+ * private address (DNS rebinding) — so a hard-guarantee deployment additionally
95
+ * pins an allow-list (see the file header).
96
+ */
97
+ declare function isPrivateOrInternalHost(hostname: string): boolean;
98
+ /**
99
+ * Validate and canonicalise a remote URL.
100
+ *
101
+ * Throws `RemoteUrlRejectedError` with a message meant for the owner: every
102
+ * rejection here is a configuration mistake or an attack, and saying which one
103
+ * is what makes the difference between a fixable setup error and a support
104
+ * ticket. Query and fragment are dropped — a WebDAV base URL has no use for
105
+ * either, and keeping them would let `?a=b` smuggle a second meaning past the
106
+ * path checks the sync engine does.
107
+ */
108
+ declare function normalizeRemoteUrl(raw: unknown, options?: UrlPolicyOptions): string;
109
+ /**
110
+ * Re-validate the target of a redirect.
111
+ *
112
+ * Called for every hop rather than once at the start. Validating the configured
113
+ * URL and then following `Location` unchecked is a classic TOCTOU: the first
114
+ * hop is public, the second is `169.254.169.254`, and the policy has already
115
+ * returned by then.
116
+ */
117
+ declare function resolveRedirectUrl(baseUrl: string, location: string, options?: UrlPolicyOptions): string;
118
+ declare function parseAllowedHosts(raw: unknown): string[];
119
+ export { normalizeRemoteUrl, resolveRedirectUrl, parseAllowedHosts, isPrivateOrInternalHost, stripBrackets, stripTrailingDot, RemoteUrlRejectedError, MAX_REMOTE_URL_LENGTH, MAX_REDIRECTS, BLOCKED_HOST_SUFFIXES, BLOCKED_HOSTNAMES, };
120
+ export type { UrlPolicyOptions };
121
+ //# sourceMappingURL=RemoteUrlPolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RemoteUrlPolicy.d.ts","sourceRoot":"","sources":["../src/RemoteUrlPolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,QAAA,MAAM,qBAAqB,OAAO,CAAC;AACnC,QAAA,MAAM,aAAa,IAAI,CAAC;AAExB;;GAEG;AACH,QAAA,MAAM,qBAAqB,8DAA+D,CAAC;AAE3F;;GAEG;AACH,QAAA,MAAM,iBAAiB,aAAmF,CAAC;AAE3G,KAAK,gBAAgB,GAAG;IACtB;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC,CAAC;AAEF,cAAM,sBAAuB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AA8GD;;GAEG;AACH,iBAAS,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG3C;AAED;;;;;GAKG;AACH,iBAAS,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK9C;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,iBAAS,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAa1D;AAED;;;;;;;;;GASG;AACH,iBAAS,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CA4ChF;AAED;;;;;;;GAOG;AACH,iBAAS,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAgBrG;AAED,iBAAS,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAE,CAKjD;AAED,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,GAClB,CAAC;AACF,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Egress policy and SSRF host predicates for user-supplied URLs.
3
+ *
4
+ * ## Provenance
5
+ *
6
+ * Merged from Durable-DAV's `packages/shared/src/net/RemoteUrlPolicy.ts` and the
7
+ * `SsrfHosts.ts` of Durable-DAV-Router, Edge-Git and Edge-Sonic. The two were the
8
+ * same problem solved twice: Durable-DAV's `RemoteUrlPolicy` is the full
9
+ * validate-and-canonicalize policy (HTTPS-only, public-address-only, operator
10
+ * allow-list, redirect re-validation); the Router/Edge-Git/Edge-Sonic
11
+ * `SsrfHosts` are the lower-level "is this host private?" predicate the policy in
12
+ * each repo leaned on. Canonical name is **`RemoteUrlPolicy`** (Durable-DAV's);
13
+ * the host predicate it converges onto is exported as **`isPrivateOrInternalHost`**
14
+ * (the common `isSsrfBlockedHost`-style composite from SsrfHosts).
15
+ *
16
+ * Scheduled replication / webhook delivery / git import turns a caller into
17
+ * someone who can name a host this Worker connects to. Without a policy that is
18
+ * server-side request forgery against Cloudflare's own egress: the metadata
19
+ * service, the loopback interface, and anything reachable on a private network the
20
+ * Worker happens to share.
21
+ *
22
+ * ## Design decisions
23
+ *
24
+ * - **The octet-accurate IP checks win over SsrfHosts' naive string prefixes.**
25
+ * SsrfHosts' `isBlockedIpv6Host` matched `startsWith('fc')`/`'fd')`, which also
26
+ * matches a *hostname* like `fcb.example.com`; Durable-DAV's `isBlockedIpv6` /
27
+ * `isBlockedIpv4` parse the address (and decode IPv4-mapped IPv6) instead. The
28
+ * kit keeps the rigorous form for both the URL policy and the exported
29
+ * predicate, so there is one correct answer rather than two that drift.
30
+ * - **`isPrivateOrInternalHost` is strictly safer than either source.** It folds
31
+ * in SsrfHosts' raw-hostname handling (bare-integer/octal/hex/`0x`-dotted forms,
32
+ * trailing-dot and bracket stripping, localhost names and `.local`/`.internal`/
33
+ * `.home.arpa` suffixes) that Durable-DAV's URL-path checks could skip because
34
+ * `new URL` had already canonicalized the host. A public IPv6 literal is allowed
35
+ * exactly as `normalizeRemoteUrl` allows it; every private/loopback/unique-local/
36
+ * link-local/zone/mapped form is refused.
37
+ *
38
+ * ## Known gap, stated rather than hidden
39
+ *
40
+ * DNS rebinding is not covered. A host that resolves to a public address at
41
+ * validation time and a private one when the connection is made defeats every
42
+ * check here. Closing it needs a resolution-aware fetch, which the platform does
43
+ * not expose to a Worker. The upheld rule for such a deployment is to pin an
44
+ * origin allow-list.
45
+ */
46
+ const MAX_REMOTE_URL_LENGTH = 2048;
47
+ const MAX_REDIRECTS = 3;
48
+ /**
49
+ * Host suffixes that never name a real internet peer.
50
+ */
51
+ const BLOCKED_HOST_SUFFIXES = ['.localhost', '.local', '.internal', '.home.arpa'];
52
+ /**
53
+ * Hostnames that resolve inward by definition.
54
+ */
55
+ const BLOCKED_HOSTNAMES = new Set(['localhost', 'localhost.localdomain', 'ip6-localhost', 'ip6-loopback']);
56
+ class RemoteUrlRejectedError extends Error {
57
+ constructor(message) {
58
+ super(message);
59
+ this.name = 'RemoteUrlRejectedError';
60
+ }
61
+ }
62
+ function isIpv4Literal(host) {
63
+ return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(host);
64
+ }
65
+ function ipv4Octets(host) {
66
+ const parts = host.split('.');
67
+ if (parts.length !== 4)
68
+ return null;
69
+ const octets = [];
70
+ for (const part of parts) {
71
+ if (!/^\d{1,3}$/.test(part))
72
+ return null;
73
+ const value = Number(part);
74
+ if (value > 255)
75
+ return null;
76
+ octets.push(value);
77
+ }
78
+ return octets;
79
+ }
80
+ /**
81
+ * Is this IPv4 literal in a range a Worker must never reach?
82
+ *
83
+ * `0.0.0.0/8` and `169.254.0.0/16` matter as much as RFC 1918: the first is
84
+ * "this host" on Linux, the second is the cloud metadata endpoint.
85
+ */
86
+ function isBlockedIpv4(octets) {
87
+ const [a = 0, b = 0] = octets;
88
+ if ([0, 10, 127].includes(a))
89
+ return true;
90
+ if (a === 169 && b === 254)
91
+ return true;
92
+ if (a === 172 && b >= 16 && b <= 31)
93
+ return true;
94
+ if (a === 192 && b === 168)
95
+ return true;
96
+ if (a === 100 && b >= 64 && b <= 127)
97
+ return true;
98
+ return false;
99
+ }
100
+ /**
101
+ * Decode an IPv4-mapped IPv6 address back into its four octets, or null.
102
+ *
103
+ * Both spellings have to be handled. `new URL` canonicalizes
104
+ * `https://[::ffff:10.0.0.1]/` to the hex form `[::ffff:a00:1]`, so a check
105
+ * written only against the dotted text never fires — and that address connects to
106
+ * a private network.
107
+ */
108
+ function mappedIpv4Octets(literal) {
109
+ const dotted = /^::ffff:\d{1,3}(?:\.\d{1,3}){3}$/.exec(literal);
110
+ const dottedText = literal.slice('::ffff:'.length);
111
+ if (dotted !== null)
112
+ return ipv4Octets(dottedText);
113
+ const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(literal);
114
+ if (!hex?.[1] || !hex[2])
115
+ return null;
116
+ const high = Number.parseInt(hex[1], 16);
117
+ const low = Number.parseInt(hex[2], 16);
118
+ return !Number.isFinite(high) || !Number.isFinite(low) ? null : [(high >> 8) & 0xff, high & 0xff, (low >> 8) & 0xff, low & 0xff];
119
+ }
120
+ /**
121
+ * Is this IPv6 literal private, loopback, or an IPv4 address in disguise? `literal`
122
+ * is the **unbracketed** inner form (e.g. `::1`, `fe80::1`).
123
+ *
124
+ * The last case is the one that matters: `::ffff:127.0.0.1` is a perfectly valid
125
+ * URL and connects to loopback, so the embedded IPv4 must be re-checked rather
126
+ * than the literal waved through because it "is" IPv6.
127
+ */
128
+ function isBlockedIpv6(literal) {
129
+ const lower = literal.toLowerCase();
130
+ if (lower === '::' || lower === '::1')
131
+ return true;
132
+ // Zone ids (`fe80::1%eth0`) name a local interface directly.
133
+ if (lower.includes('%'))
134
+ return true;
135
+ const mapped = mappedIpv4Octets(lower);
136
+ if (mapped !== null)
137
+ return isBlockedIpv4(mapped);
138
+ const head = lower.split(':', 1)[0] ?? '';
139
+ if (head === '')
140
+ return false;
141
+ const group = Number.parseInt(head.padStart(4, '0').slice(0, 4), 16);
142
+ if (!Number.isFinite(group))
143
+ return false;
144
+ // fc00::/7 unique-local, fe80::/10 link-local.
145
+ return (group & 0xfe_00) === 0xfc_00 || (group & 0xff_c0) === 0xfe_80;
146
+ }
147
+ /**
148
+ * Does this host name a numeric address in a non-canonical spelling? The strict
149
+ * superset of Durable-DAV's `looksLikeObfuscatedNumericHost` and SsrfHosts'
150
+ * `isEncodedNumericHost`, for the raw-hostname predicate.
151
+ *
152
+ * `2130706433`, `0177.0.0.1`, `0x7f000001` and `0x7f.1` all name `127.0.0.1`.
153
+ * Through `new URL` these arrive already canonicalized (so `isBlockedIpv4` catches
154
+ * them); a hostname handed to `isPrivateOrInternalHost` need not have gone through
155
+ * `URL`, so a bare integer/octal/hex form is caught here directly. A legitimate
156
+ * internet hostname is never all-digits, so over-blocking is not a risk.
157
+ */
158
+ function looksLikeObfuscatedNumericHost(host) {
159
+ if (isIpv4Literal(host) || host.startsWith('[') || !/^[0-9a-fx.]+$/i.test(host))
160
+ return false;
161
+ return (/^0x[0-9a-f]+$/i.test(host) ||
162
+ /^\d+$/.test(host) ||
163
+ /^0[0-7]+(?:\.0[0-7]+)*$/.test(host) ||
164
+ // An all-digits-and-dots host that is not a canonical dotted-quad — e.g.
165
+ // `0177.0.0.1` or `0200.0250.0001.0001` — is an obfuscated numeric address.
166
+ /^[\d.]+$/.test(host) ||
167
+ host.split('.').some((group) => /^0x[0-9a-f]+$/i.test(group)));
168
+ }
169
+ function hostIsAllowed(host, allowedHosts) {
170
+ const lower = host.toLowerCase();
171
+ return allowedHosts.some((entry) => {
172
+ const candidate = entry.trim().toLowerCase();
173
+ if (candidate === '')
174
+ return false;
175
+ return candidate.startsWith('.') ? lower.endsWith(candidate) : lower === candidate;
176
+ });
177
+ }
178
+ /**
179
+ * Strip one enclosing `[`…`]` pair (an IPv6 literal's URL brackets).
180
+ */
181
+ function stripBrackets(host) {
182
+ const h = host.trim();
183
+ return h.startsWith('[') && h.endsWith(']') ? h.slice(1, -1) : h;
184
+ }
185
+ /**
186
+ * Strip a bracketed wrapper and every trailing root-label dot.
187
+ *
188
+ * A fully-qualified name ends in a root label (`example.com.`) and `new URL` keeps
189
+ * it; RFC 4918 §9.1.2 clients vary on sending it, so the canonical form is compared.
190
+ */
191
+ function stripTrailingDot(host) {
192
+ const unbracketed = stripBrackets(host);
193
+ let end = unbracketed.length;
194
+ while (end > 0 && unbracketed.charAt(end - 1) === '.')
195
+ end -= 1;
196
+ return unbracketed.slice(0, end);
197
+ }
198
+ /**
199
+ * Host names that mean "this machine or a local suffix", not an internet peer.
200
+ */
201
+ function isLocalhostName(host) {
202
+ return BLOCKED_HOSTNAMES.has(host) || host.endsWith('.localhost');
203
+ }
204
+ /**
205
+ * True when a URL host resolves to this machine or a private network.
206
+ *
207
+ * Intended for user-supplied URLs that the server will subsequently fetch. Such a
208
+ * URL turns the process into a proxy into its own network: without this, a
209
+ * user-supplied `baseUrl` can point at cloud metadata, a database host, or a
210
+ * loopback admin port, and the response is readable by the caller.
211
+ *
212
+ * Encoded-numeric hosts are included because `http://2130706433/` and
213
+ * `http://0177.0.0.1/` are ordinary-looking strings that name `127.0.0.1` after
214
+ * parsing.
215
+ *
216
+ * A *strict, safer* predicate: an empty host, any private/loopback/link-local/
217
+ * unique-local/mapped form, any localhost/suffix name, any obfuscated numeric
218
+ * form, and any `:`-bearing host that is not a public IPv6 literal all return
219
+ * true. It is a *syntactic* check — a public hostname can still resolve to a
220
+ * private address (DNS rebinding) — so a hard-guarantee deployment additionally
221
+ * pins an allow-list (see the file header).
222
+ */
223
+ function isPrivateOrInternalHost(hostname) {
224
+ const host = stripTrailingDot(hostname).toLowerCase();
225
+ if (host.length === 0)
226
+ return true;
227
+ if (isLocalhostName(host))
228
+ return true;
229
+ if (BLOCKED_HOST_SUFFIXES.some((suffix) => host.endsWith(suffix)))
230
+ return true;
231
+ if (isIpv4Literal(host)) {
232
+ const octets = ipv4Octets(host);
233
+ return octets === null || isBlockedIpv4(octets);
234
+ }
235
+ if (host.includes(':'))
236
+ return isBlockedIpv6(host);
237
+ if (looksLikeObfuscatedNumericHost(host))
238
+ return true;
239
+ return false;
240
+ }
241
+ /**
242
+ * Validate and canonicalise a remote URL.
243
+ *
244
+ * Throws `RemoteUrlRejectedError` with a message meant for the owner: every
245
+ * rejection here is a configuration mistake or an attack, and saying which one
246
+ * is what makes the difference between a fixable setup error and a support
247
+ * ticket. Query and fragment are dropped — a WebDAV base URL has no use for
248
+ * either, and keeping them would let `?a=b` smuggle a second meaning past the
249
+ * path checks the sync engine does.
250
+ */
251
+ function normalizeRemoteUrl(raw, options = {}) {
252
+ if (typeof raw !== 'string')
253
+ throw new RemoteUrlRejectedError('remoteUrl must be a string');
254
+ const trimmed = raw.trim();
255
+ if (trimmed === '')
256
+ throw new RemoteUrlRejectedError('remoteUrl is required');
257
+ if (trimmed.length > MAX_REMOTE_URL_LENGTH)
258
+ throw new RemoteUrlRejectedError(`remoteUrl must be at most ${MAX_REMOTE_URL_LENGTH} characters`);
259
+ let url;
260
+ try {
261
+ url = new URL(trimmed);
262
+ }
263
+ catch {
264
+ throw new RemoteUrlRejectedError('remoteUrl is not a valid absolute URL');
265
+ }
266
+ if (url.protocol !== 'https:')
267
+ throw new RemoteUrlRejectedError('remoteUrl must use https');
268
+ if (url.username !== '' || url.password !== '')
269
+ throw new RemoteUrlRejectedError('remoteUrl must not embed credentials; use the auth fields instead');
270
+ if (url.port !== '' && url.port !== '443')
271
+ throw new RemoteUrlRejectedError('remoteUrl must use the default https port');
272
+ const host = url.hostname.toLowerCase();
273
+ if (host === '')
274
+ throw new RemoteUrlRejectedError('remoteUrl must include a host');
275
+ if (hostIsAllowed(host, options.allowedHosts ?? [])) {
276
+ url.search = '';
277
+ url.hash = '';
278
+ return url.href;
279
+ }
280
+ if (BLOCKED_HOSTNAMES.has(host))
281
+ throw new RemoteUrlRejectedError(`remoteUrl host ${host} is not routable`);
282
+ for (const suffix of BLOCKED_HOST_SUFFIXES) {
283
+ if (host.endsWith(suffix))
284
+ throw new RemoteUrlRejectedError(`remoteUrl host ${host} is not a public hostname`);
285
+ }
286
+ if (looksLikeObfuscatedNumericHost(host))
287
+ throw new RemoteUrlRejectedError(`remoteUrl host ${host} is a numeric address in a non-canonical form`);
288
+ if (isIpv4Literal(host)) {
289
+ const octets = ipv4Octets(host);
290
+ if (octets === null || isBlockedIpv4(octets))
291
+ throw new RemoteUrlRejectedError(`remoteUrl host ${host} is not a public address`);
292
+ }
293
+ else if (host.startsWith('[')) {
294
+ if (isBlockedIpv6(host.slice(1, -1)))
295
+ throw new RemoteUrlRejectedError(`remoteUrl host ${host} is not a public address`);
296
+ }
297
+ else if (!host.includes('.')) {
298
+ // A single-label name can only be resolved through a local search domain.
299
+ throw new RemoteUrlRejectedError(`remoteUrl host ${host} is not a fully qualified hostname`);
300
+ }
301
+ url.search = '';
302
+ url.hash = '';
303
+ return url.href;
304
+ }
305
+ /**
306
+ * Re-validate the target of a redirect.
307
+ *
308
+ * Called for every hop rather than once at the start. Validating the configured
309
+ * URL and then following `Location` unchecked is a classic TOCTOU: the first
310
+ * hop is public, the second is `169.254.169.254`, and the policy has already
311
+ * returned by then.
312
+ */
313
+ function resolveRedirectUrl(baseUrl, location, options = {}) {
314
+ let next;
315
+ try {
316
+ next = new URL(location, baseUrl);
317
+ }
318
+ catch {
319
+ throw new RemoteUrlRejectedError('redirect target is not a valid URL');
320
+ }
321
+ if (next.protocol !== 'https:')
322
+ throw new RemoteUrlRejectedError('redirect target must use https');
323
+ // Same host: no need to re-run the whole policy, and a redirect back to the
324
+ // origin's own host is the overwhelmingly common case.
325
+ if (next.host.toLowerCase() === new URL(baseUrl).host.toLowerCase()) {
326
+ next.search = '';
327
+ next.hash = '';
328
+ return next.href;
329
+ }
330
+ return normalizeRemoteUrl(next.href, options);
331
+ }
332
+ function parseAllowedHosts(raw) {
333
+ return typeof raw !== 'string' || raw.trim() === '' ? [] : raw
334
+ .split(',')
335
+ .map((entry) => entry.trim())
336
+ .filter((entry) => entry !== '');
337
+ }
338
+ export { normalizeRemoteUrl, resolveRedirectUrl, parseAllowedHosts, isPrivateOrInternalHost, stripBrackets, stripTrailingDot, RemoteUrlRejectedError, MAX_REMOTE_URL_LENGTH, MAX_REDIRECTS, BLOCKED_HOST_SUFFIXES, BLOCKED_HOSTNAMES, };
339
+ //# sourceMappingURL=RemoteUrlPolicy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RemoteUrlPolicy.js","sourceRoot":"","sources":["../src/RemoteUrlPolicy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Minimal `Result` type for explicit error handling without exceptions.
3
+ *
4
+ * ## Provenance
5
+ *
6
+ * Converged from Edge-Git's `Result.ts` and ChordDHT-Tracker's `Result.ts`. The
7
+ * two are byte-identical apart from a doc comment (Edge-Git added the header);
8
+ * ChordDHT is the origin, Edge-Git the copy. Mail-Otter and the DAV repos are
9
+ * the kind of repo this was ported *to*, so Edge-Git's documented form is kept
10
+ * verbatim.
11
+ *
12
+ * When two variants are identical there is nothing stricter to choose; the
13
+ * supported shape is `{ ok: true, value }` / `{ ok: false, error }` frozen at the
14
+ * type level rather than thrown.
15
+ */
16
+ type Ok<T> = {
17
+ readonly ok: true;
18
+ readonly value: T;
19
+ };
20
+ type Err<E> = {
21
+ readonly ok: false;
22
+ readonly error: E;
23
+ };
24
+ type Result<T, E = Error> = Ok<T> | Err<E>;
25
+ declare function ok<T>(value: T): Result<T, never>;
26
+ declare function err<E>(error: E): Result<never, E>;
27
+ declare function isOk<T, E>(result: Result<T, E>): result is Ok<T>;
28
+ declare function mapResult<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;
29
+ declare function getOrThrow<T, E = Error>(result: Result<T, E>): T;
30
+ export { err, getOrThrow, isOk, mapResult, ok };
31
+ export type { Err, Ok, Result };
32
+ //# sourceMappingURL=Result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Result.d.ts","sourceRoot":"","sources":["../src/Result.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,KAAK,EAAE,CAAC,CAAC,IAAI;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AACtD,KAAK,GAAG,CAAC,CAAC,IAAI;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AACxD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAE3C,iBAAS,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAEzC;AAED,iBAAS,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAE1C;AAED,iBAAS,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,CAEzD;AAED,iBAAS,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAEnF;AAED,iBAAS,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAGzD;AAED,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;AAChD,YAAY,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC"}
package/dist/Result.js ADDED
@@ -0,0 +1,19 @@
1
+ function ok(value) {
2
+ return { ok: true, value };
3
+ }
4
+ function err(error) {
5
+ return { ok: false, error };
6
+ }
7
+ function isOk(result) {
8
+ return result.ok;
9
+ }
10
+ function mapResult(result, fn) {
11
+ return result.ok ? ok(fn(result.value)) : result;
12
+ }
13
+ function getOrThrow(result) {
14
+ if (!result.ok)
15
+ throw result.error;
16
+ return result.value;
17
+ }
18
+ export { err, getOrThrow, isOk, mapResult, ok };
19
+ //# sourceMappingURL=Result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Result.js","sourceRoot":"","sources":["../src/Result.ts"],"names":[],"mappings":"AAmBA,SAAS,EAAE,CAAI,KAAQ;IACrB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED,SAAS,GAAG,CAAI,KAAQ;IACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED,SAAS,IAAI,CAAO,MAAoB;IACtC,OAAO,MAAM,CAAC,EAAE,CAAC;AACnB,CAAC;AAED,SAAS,SAAS,CAAU,MAAoB,EAAE,EAAmB;IACnE,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACnD,CAAC;AAED,SAAS,UAAU,CAAe,MAAoB;IACpD,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,MAAM,CAAC,KAAK,CAAC;IACnC,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,CAAC;AAED,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC"}