@atproto-labs/fetch-node 0.1.7 → 0.1.9

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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @atproto-labs/fetch-node
2
2
 
3
+ ## 0.1.9
4
+
5
+ ### Patch Changes
6
+
7
+ - [#3821](https://github.com/bluesky-social/atproto/pull/3821) [`5050b6550`](https://github.com/bluesky-social/atproto/commit/5050b6550e07e71b0a524eda0b71b837583294d4) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Allow explicit `follow` mode in safe fetch wrap.
8
+
9
+ - [#3819](https://github.com/bluesky-social/atproto/pull/3819) [`36dbd4155`](https://github.com/bluesky-social/atproto/commit/36dbd41551f74052a3f584719a1a7edd86eca201) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Fix potential memory leak
10
+
11
+ - [#3821](https://github.com/bluesky-social/atproto/pull/3821) [`5050b6550`](https://github.com/bluesky-social/atproto/commit/5050b6550e07e71b0a524eda0b71b837583294d4) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Allow disabling the need for an explicit `redirect` mode
12
+
13
+ - [#3818](https://github.com/bluesky-social/atproto/pull/3818) [`43861a452`](https://github.com/bluesky-social/atproto/commit/43861a452b70268e738ef12033297cddacbe25d4) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove dependency on the Public Suffix List
14
+
15
+ - Updated dependencies [[`5050b6550`](https://github.com/bluesky-social/atproto/commit/5050b6550e07e71b0a524eda0b71b837583294d4), [`5050b6550`](https://github.com/bluesky-social/atproto/commit/5050b6550e07e71b0a524eda0b71b837583294d4)]:
16
+ - @atproto-labs/pipe@0.1.1
17
+ - @atproto-labs/fetch@0.2.3
18
+
19
+ ## 0.1.8
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [[`850e39843`](https://github.com/bluesky-social/atproto/commit/850e39843cb0ec9ea716675f7568c0c601f45e29), [`850e39843`](https://github.com/bluesky-social/atproto/commit/850e39843cb0ec9ea716675f7568c0c601f45e29)]:
24
+ - @atproto-labs/fetch@0.2.2
25
+
3
26
  ## 0.1.7
4
27
 
5
28
  ### Patch Changes
package/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  Dual MIT/Apache-2.0 License
2
2
 
3
- Copyright (c) 2022-2024 Bluesky PBC, and Contributors
3
+ Copyright (c) 2022-2025 Bluesky Social PBC, and Contributors
4
4
 
5
5
  Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
6
6
 
package/dist/safe.d.ts CHANGED
@@ -7,7 +7,14 @@ export type SafeFetchWrapOptions = NonNullable<Parameters<typeof safeFetchWrap>[
7
7
  * @see {@link https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html}
8
8
  */
9
9
  export declare function safeFetchWrap({ fetch, responseMaxSize, // 512kB
10
- ssrfProtection, allowCustomPort, allowData, allowHttp, allowIpHost, allowPrivateIps, timeout, forbiddenDomainNames, }?: {
10
+ ssrfProtection, allowCustomPort, allowData, allowHttp, allowIpHost, allowPrivateIps, timeout, forbiddenDomainNames,
11
+ /**
12
+ * When `false`, a {@link RequestInit['redirect']} value must be explicitly
13
+ * provided or the request will fail.
14
+ *
15
+ * @default false
16
+ */
17
+ allowImplicitRedirect, }?: {
11
18
  fetch?: Fetch | undefined;
12
19
  responseMaxSize?: number | undefined;
13
20
  ssrfProtection?: boolean | undefined;
@@ -18,5 +25,6 @@ ssrfProtection, allowCustomPort, allowData, allowHttp, allowIpHost, allowPrivate
18
25
  allowPrivateIps?: boolean | undefined;
19
26
  timeout?: number | undefined;
20
27
  forbiddenDomainNames?: Iterable<string> | undefined;
21
- }): Fetch<unknown>;
28
+ allowImplicitRedirect?: boolean | undefined;
29
+ }): (input: string | URL | Request, init?: RequestInit | undefined) => Promise<Response>;
22
30
  //# sourceMappingURL=safe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"safe.d.ts","sourceRoot":"","sources":["../src/safe.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EASN,MAAM,qBAAqB,CAAA;AAI5B,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAC5C,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CACpC,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAiC,EACjC,eAA4B,EAAE,QAAQ;AACtC,cAAqB,EACrB,eAAiC,EACjC,SAAiB,EACjB,SAA2B,EAC3B,WAAkB,EAClB,eAAiC,EACjC,OAAc,EACd,oBAAyE,GAC1E;;;;;;;;;;;CAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAsDtB"}
1
+ {"version":3,"file":"safe.d.ts","sourceRoot":"","sources":["../src/safe.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EAQN,MAAM,qBAAqB,CAAA;AAI5B,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAC5C,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CACpC,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAiC,EACjC,eAA4B,EAAE,QAAQ;AACtC,cAAqB,EACrB,eAAiC,EACjC,SAAiB,EACjB,SAA2B,EAC3B,WAAkB,EAClB,eAAiC,EACjC,OAAc,EACd,oBAAyE;AACzE;;;;;GAKG;AACH,qBAA6B,GAC9B;;;;;;;;;;;;CAAK,wFAoDL"}
package/dist/safe.js CHANGED
@@ -11,12 +11,19 @@ const unicast_js_1 = require("./unicast.js");
11
11
  * @see {@link https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html}
12
12
  */
13
13
  function safeFetchWrap({ fetch = globalThis.fetch, responseMaxSize = 512 * 1024, // 512kB
14
- ssrfProtection = true, allowCustomPort = !ssrfProtection, allowData = false, allowHttp = !ssrfProtection, allowIpHost = true, allowPrivateIps = !ssrfProtection, timeout = 10e3, forbiddenDomainNames = fetch_1.DEFAULT_FORBIDDEN_DOMAIN_NAMES, } = {}) {
15
- return (0, fetch_1.toRequestTransformer)((0, pipe_1.pipe)(
14
+ ssrfProtection = true, allowCustomPort = !ssrfProtection, allowData = false, allowHttp = !ssrfProtection, allowIpHost = true, allowPrivateIps = !ssrfProtection, timeout = 10e3, forbiddenDomainNames = fetch_1.DEFAULT_FORBIDDEN_DOMAIN_NAMES,
15
+ /**
16
+ * When `false`, a {@link RequestInit['redirect']} value must be explicitly
17
+ * provided or the request will fail.
18
+ *
19
+ * @default false
20
+ */
21
+ allowImplicitRedirect = false, } = {}) {
22
+ return (0, pipe_1.pipe)(
16
23
  /**
17
- * Disable HTTP redirects
24
+ * Require explicit {@link RequestInit['redirect']} mode
18
25
  */
19
- (0, fetch_1.redirectCheckRequestTransform)(),
26
+ allowImplicitRedirect ? fetch_1.asRequest : (0, fetch_1.explicitRedirectCheckRequestTransform)(),
20
27
  /**
21
28
  * Only requests that will be issued with a "Host" header are allowed.
22
29
  */
@@ -53,6 +60,6 @@ ssrfProtection = true, allowCustomPort = !ssrfProtection, allowData = false, all
53
60
  * Since we will be fetching user owned data, we need to make sure that an
54
61
  * attacker cannot force us to download a large amounts of data.
55
62
  */
56
- (0, fetch_1.fetchMaxSizeProcessor)(responseMaxSize)));
63
+ (0, fetch_1.fetchMaxSizeProcessor)(responseMaxSize));
57
64
  }
58
65
  //# sourceMappingURL=safe.js.map
package/dist/safe.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"safe.js","sourceRoot":"","sources":["../src/safe.ts"],"names":[],"mappings":";;AAyBA,sCAiEC;AA1FD,+CAW4B;AAC5B,6CAAyC;AACzC,6CAA+C;AAM/C;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,EAC5B,KAAK,GAAG,UAAU,CAAC,KAAc,EACjC,eAAe,GAAG,GAAG,GAAG,IAAI,EAAE,QAAQ;AACtC,cAAc,GAAG,IAAI,EACrB,eAAe,GAAG,CAAC,cAAc,EACjC,SAAS,GAAG,KAAK,EACjB,SAAS,GAAG,CAAC,cAAc,EAC3B,WAAW,GAAG,IAAI,EAClB,eAAe,GAAG,CAAC,cAAc,EACjC,OAAO,GAAG,IAAI,EACd,oBAAoB,GAAG,sCAAkD,MACvE,EAAE;IACJ,OAAO,IAAA,4BAAoB,EACzB,IAAA,WAAI;IACF;;OAEG;IACH,IAAA,qCAA6B,GAAE;IAE/B;;OAEG;IACH,WAAW,CAAC,CAAC,CAAC,iBAAS,CAAC,CAAC,CAAC,IAAA,kCAA0B,GAAE;IAEtD;;OAEG;IACH,IAAA,qCAA6B,EAAC;QAC5B,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,SAAS;QAClB,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,SAAS,IAAI,EAAE,eAAe,EAAE;QACzC,QAAQ,EAAE,EAAE,eAAe,EAAE;KAC9B,CAAC;IAEF;;;;;OAKG;IACH,IAAA,2CAAmC,EAAC,oBAAoB,CAAC;IAEzD;;;OAGG;IACH,IAAA,kBAAU,EACR,OAAO;IAEP;;;;OAIG;IACH,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAA,6BAAgB,EAAC,EAAE,KAAK,EAAE,CAAC,CACtD;IAED;;;OAGG;IACH,IAAA,6BAAqB,EAAC,eAAe,CAAC,CACvC,CACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"safe.js","sourceRoot":"","sources":["../src/safe.ts"],"names":[],"mappings":";;AAwBA,sCAsEC;AA9FD,+CAU4B;AAC5B,6CAAyC;AACzC,6CAA+C;AAM/C;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,EAC5B,KAAK,GAAG,UAAU,CAAC,KAAc,EACjC,eAAe,GAAG,GAAG,GAAG,IAAI,EAAE,QAAQ;AACtC,cAAc,GAAG,IAAI,EACrB,eAAe,GAAG,CAAC,cAAc,EACjC,SAAS,GAAG,KAAK,EACjB,SAAS,GAAG,CAAC,cAAc,EAC3B,WAAW,GAAG,IAAI,EAClB,eAAe,GAAG,CAAC,cAAc,EACjC,OAAO,GAAG,IAAI,EACd,oBAAoB,GAAG,sCAAkD;AACzE;;;;;GAKG;AACH,qBAAqB,GAAG,KAAK,MAC3B,EAAE;IACJ,OAAO,IAAA,WAAI;IACT;;OAEG;IACH,qBAAqB,CAAC,CAAC,CAAC,iBAAS,CAAC,CAAC,CAAC,IAAA,6CAAqC,GAAE;IAE3E;;OAEG;IACH,WAAW,CAAC,CAAC,CAAC,iBAAS,CAAC,CAAC,CAAC,IAAA,kCAA0B,GAAE;IAEtD;;OAEG;IACH,IAAA,qCAA6B,EAAC;QAC5B,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,SAAS;QAClB,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,SAAS,IAAI,EAAE,eAAe,EAAE;QACzC,QAAQ,EAAE,EAAE,eAAe,EAAE;KAC9B,CAAC;IAEF;;;;;OAKG;IACH,IAAA,2CAAmC,EAAC,oBAAoB,CAAC;IAEzD;;;OAGG;IACH,IAAA,kBAAU,EACR,OAAO;IAEP;;;;OAIG;IACH,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAA,6BAAgB,EAAC,EAAE,KAAK,EAAE,CAAC,CACtD;IAED;;;OAGG;IACH,IAAA,6BAAqB,EAAC,eAAe,CAAC,CACd,CAAA;AAC5B,CAAC"}
package/dist/unicast.d.ts CHANGED
@@ -9,4 +9,10 @@ export type SsrfFetchWrapOptions<C = FetchContext> = {
9
9
  */
10
10
  export declare function unicastFetchWrap<C = FetchContext>({ fetch, }: SsrfFetchWrapOptions<C>): Fetch<C>;
11
11
  export declare function unicastLookup(hostname: string, options: dns.LookupOptions, callback: Parameters<LookupFunction>[2]): void;
12
+ /**
13
+ * @param hostname - a syntactically valid hostname
14
+ * @returns whether the hostname is a name typically used for on locale area networks.
15
+ * @note **DO NOT** use for security reasons. Only as heuristic.
16
+ */
17
+ export declare function isLocalHostname(hostname: string): boolean;
12
18
  //# sourceMappingURL=unicast.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"unicast.d.ts","sourceRoot":"","sources":["../src/unicast.ts"],"names":[],"mappings":"AAAA,OAAO,GAAsB,MAAM,UAAU,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAIzC,OAAO,EACL,KAAK,EACL,YAAY,EAIb,MAAM,qBAAqB,CAAA;AAK5B,MAAM,MAAM,oBAAoB,CAAC,CAAC,GAAG,YAAY,IAAI;IACnD,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;CACjB,CAAA;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,YAAY,EAAE,EACjD,KAAwB,GACzB,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CA+HpC;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,GAAG,CAAC,aAAa,EAC1B,QAAQ,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,QA0BxC"}
1
+ {"version":3,"file":"unicast.d.ts","sourceRoot":"","sources":["../src/unicast.ts"],"names":[],"mappings":"AAAA,OAAO,GAAsB,MAAM,UAAU,CAAA;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAGzC,OAAO,EACL,KAAK,EACL,YAAY,EAIb,MAAM,qBAAqB,CAAA;AAK5B,MAAM,MAAM,oBAAoB,CAAC,CAAC,GAAG,YAAY,IAAI;IACnD,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;CACjB,CAAA;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,YAAY,EAAE,EACjD,KAAwB,GACzB,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CA0IpC;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,GAAG,CAAC,aAAa,EAC1B,QAAQ,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,QA0BxC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAYzD"}
package/dist/unicast.js CHANGED
@@ -5,9 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.unicastFetchWrap = unicastFetchWrap;
7
7
  exports.unicastLookup = unicastLookup;
8
+ exports.isLocalHostname = isLocalHostname;
8
9
  const node_dns_1 = __importDefault(require("node:dns"));
9
10
  const ipaddr_js_1 = __importDefault(require("ipaddr.js"));
10
- const psl_1 = require("psl");
11
11
  const undici_1 = require("undici");
12
12
  const fetch_1 = require("@atproto-labs/fetch");
13
13
  const util_js_1 = require("./util.js");
@@ -77,15 +77,31 @@ function unicastFetchWrap({ fetch = globalThis.fetch, }) {
77
77
  },
78
78
  },
79
79
  });
80
- const headers = new Headers(init?.headers);
81
- headers.set('connection', 'close'); // Proactively close the connection
82
80
  try {
83
- return await fetch.call(this, input, {
81
+ const headers = new Headers(init?.headers);
82
+ headers.set('connection', 'close'); // Proactively close the connection
83
+ const response = await fetch.call(this, input, {
84
84
  ...init,
85
85
  headers,
86
86
  // @ts-expect-error non-standard option
87
87
  dispatcher,
88
88
  });
89
+ if (!didLookup) {
90
+ // We need to ensure that the body is discarded. We can either
91
+ // consume the whole body (for await loop) in order to keep the
92
+ // socket alive, or cancel the request. Since we sent "connection:
93
+ // close", there is no point in consuming the whole response
94
+ // (which would cause un-necessary bandwidth).
95
+ //
96
+ // https://undici.nodejs.org/#/?id=garbage-collection
97
+ await response.body?.cancel();
98
+ // If you encounter this error, either upgrade to Node.js >=21 or
99
+ // make sure that the dispatcher passed through the requestInit
100
+ // object ends up being used to make the request.
101
+ // eslint-disable-next-line no-unsafe-finally
102
+ throw new fetch_1.FetchRequestError((0, fetch_1.asRequest)(input, init), 500, 'Unable to enforce SSRF protection');
103
+ }
104
+ return response;
89
105
  }
90
106
  finally {
91
107
  // Free resources (we cannot await here since the response was not
@@ -94,13 +110,6 @@ function unicastFetchWrap({ fetch = globalThis.fetch, }) {
94
110
  // No biggie, but let's still log it
95
111
  console.warn('Failed to close dispatcher', err);
96
112
  });
97
- if (!didLookup) {
98
- // If you encounter this error, either upgrade to Node.js >=21 or
99
- // make sure that the dispatcher passed through the requestInit
100
- // object ends up being used to make the request.
101
- // eslint-disable-next-line no-unsafe-finally
102
- throw new fetch_1.FetchRequestError((0, fetch_1.asRequest)(input, init), 500, 'Unable to enforce SSRF protection');
103
- }
104
113
  }
105
114
  }
106
115
  }
@@ -108,8 +117,8 @@ function unicastFetchWrap({ fetch = globalThis.fetch, }) {
108
117
  }
109
118
  }
110
119
  function unicastLookup(hostname, options, callback) {
111
- if (!isValidDomain(hostname)) {
112
- callback(new Error('Hostname is not a public domain'), '');
120
+ if (isLocalHostname(hostname)) {
121
+ callback(new Error('Hostname is not a public domain'), []);
113
122
  return;
114
123
  }
115
124
  node_dns_1.default.lookup(hostname, options, (err, address, family) => {
@@ -129,12 +138,21 @@ function unicastLookup(hostname, options, callback) {
129
138
  }
130
139
  });
131
140
  }
132
- // see lupomontero/psl#258 for context on psl usage.
133
- // in short, this ensures a structurally valid domain
134
- // plus a "listed" tld.
135
- function isValidDomain(domain) {
136
- const parsed = (0, psl_1.parse)(domain);
137
- return !parsed.error && parsed.listed;
141
+ /**
142
+ * @param hostname - a syntactically valid hostname
143
+ * @returns whether the hostname is a name typically used for on locale area networks.
144
+ * @note **DO NOT** use for security reasons. Only as heuristic.
145
+ */
146
+ function isLocalHostname(hostname) {
147
+ const parts = hostname.split('.');
148
+ if (parts.length < 2)
149
+ return true;
150
+ const tld = parts.at(-1).toLowerCase();
151
+ return (tld === 'test' ||
152
+ tld === 'local' ||
153
+ tld === 'localhost' ||
154
+ tld === 'invalid' ||
155
+ tld === 'example');
138
156
  }
139
157
  function isNotUnicast(ip) {
140
158
  return ip.range() !== 'unicast';
@@ -1 +1 @@
1
- {"version":3,"file":"unicast.js","sourceRoot":"","sources":["../src/unicast.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"unicast.js","sourceRoot":"","sources":["../src/unicast.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto-labs/fetch-node",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "license": "MIT",
5
5
  "description": "SSRF protection for fetch() in Node.js",
6
6
  "keywords": [
@@ -28,13 +28,11 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "ipaddr.js": "^2.1.0",
31
- "psl": "^1.9.0",
32
31
  "undici": "^6.14.1",
33
- "@atproto-labs/fetch": "0.2.1",
34
- "@atproto-labs/pipe": "0.1.0"
32
+ "@atproto-labs/fetch": "0.2.3",
33
+ "@atproto-labs/pipe": "0.1.1"
35
34
  },
36
35
  "devDependencies": {
37
- "@types/psl": "1.1.3",
38
36
  "typescript": "^5.6.3"
39
37
  },
40
38
  "scripts": {
package/src/safe.ts CHANGED
@@ -2,13 +2,12 @@ import {
2
2
  DEFAULT_FORBIDDEN_DOMAIN_NAMES,
3
3
  Fetch,
4
4
  asRequest,
5
+ explicitRedirectCheckRequestTransform,
5
6
  fetchMaxSizeProcessor,
6
7
  forbiddenDomainNameRequestTransform,
7
8
  protocolCheckRequestTransform,
8
- redirectCheckRequestTransform,
9
9
  requireHostHeaderTransform,
10
10
  timedFetch,
11
- toRequestTransformer,
12
11
  } from '@atproto-labs/fetch'
13
12
  import { pipe } from '@atproto-labs/pipe'
14
13
  import { unicastFetchWrap } from './unicast.js'
@@ -34,58 +33,63 @@ export function safeFetchWrap({
34
33
  allowPrivateIps = !ssrfProtection,
35
34
  timeout = 10e3,
36
35
  forbiddenDomainNames = DEFAULT_FORBIDDEN_DOMAIN_NAMES as Iterable<string>,
37
- } = {}): Fetch<unknown> {
38
- return toRequestTransformer(
39
- pipe(
40
- /**
41
- * Disable HTTP redirects
42
- */
43
- redirectCheckRequestTransform(),
36
+ /**
37
+ * When `false`, a {@link RequestInit['redirect']} value must be explicitly
38
+ * provided or the request will fail.
39
+ *
40
+ * @default false
41
+ */
42
+ allowImplicitRedirect = false,
43
+ } = {}) {
44
+ return pipe(
45
+ /**
46
+ * Require explicit {@link RequestInit['redirect']} mode
47
+ */
48
+ allowImplicitRedirect ? asRequest : explicitRedirectCheckRequestTransform(),
44
49
 
45
- /**
46
- * Only requests that will be issued with a "Host" header are allowed.
47
- */
48
- allowIpHost ? asRequest : requireHostHeaderTransform(),
49
-
50
- /**
51
- * Prevent using http:, file: or data: protocols.
52
- */
53
- protocolCheckRequestTransform({
54
- 'about:': false,
55
- 'data:': allowData,
56
- 'file:': false,
57
- 'http:': allowHttp && { allowCustomPort },
58
- 'https:': { allowCustomPort },
59
- }),
50
+ /**
51
+ * Only requests that will be issued with a "Host" header are allowed.
52
+ */
53
+ allowIpHost ? asRequest : requireHostHeaderTransform(),
60
54
 
61
- /**
62
- * Disallow fetching from domains we know are not atproto/OIDC client
63
- * implementation. Note that other domains can be blocked by providing a
64
- * custom fetch function combined with another
65
- * forbiddenDomainNameRequestTransform.
66
- */
67
- forbiddenDomainNameRequestTransform(forbiddenDomainNames),
55
+ /**
56
+ * Prevent using http:, file: or data: protocols.
57
+ */
58
+ protocolCheckRequestTransform({
59
+ 'about:': false,
60
+ 'data:': allowData,
61
+ 'file:': false,
62
+ 'http:': allowHttp && { allowCustomPort },
63
+ 'https:': { allowCustomPort },
64
+ }),
68
65
 
69
- /**
70
- * Since we will be fetching from the network based on user provided
71
- * input, let's mitigate resource exhaustion attacks by setting a timeout.
72
- */
73
- timedFetch(
74
- timeout,
66
+ /**
67
+ * Disallow fetching from domains we know are not atproto/OIDC client
68
+ * implementation. Note that other domains can be blocked by providing a
69
+ * custom fetch function combined with another
70
+ * forbiddenDomainNameRequestTransform.
71
+ */
72
+ forbiddenDomainNameRequestTransform(forbiddenDomainNames),
75
73
 
76
- /**
77
- * Since we will be fetching from the network based on user provided
78
- * input, we need to make sure that the request is not vulnerable to SSRF
79
- * attacks.
80
- */
81
- allowPrivateIps ? fetch : unicastFetchWrap({ fetch }),
82
- ),
74
+ /**
75
+ * Since we will be fetching from the network based on user provided
76
+ * input, let's mitigate resource exhaustion attacks by setting a timeout.
77
+ */
78
+ timedFetch(
79
+ timeout,
83
80
 
84
81
  /**
85
- * Since we will be fetching user owned data, we need to make sure that an
86
- * attacker cannot force us to download a large amounts of data.
82
+ * Since we will be fetching from the network based on user provided
83
+ * input, we need to make sure that the request is not vulnerable to SSRF
84
+ * attacks.
87
85
  */
88
- fetchMaxSizeProcessor(responseMaxSize),
86
+ allowPrivateIps ? fetch : unicastFetchWrap({ fetch }),
89
87
  ),
90
- )
88
+
89
+ /**
90
+ * Since we will be fetching user owned data, we need to make sure that an
91
+ * attacker cannot force us to download a large amounts of data.
92
+ */
93
+ fetchMaxSizeProcessor(responseMaxSize),
94
+ ) satisfies Fetch<unknown>
91
95
  }
package/src/unicast.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import dns, { LookupAddress } from 'node:dns'
2
2
  import { LookupFunction } from 'node:net'
3
3
  import ipaddr from 'ipaddr.js'
4
- import { parse as pslParse } from 'psl'
5
4
  import { Agent, Client } from 'undici'
6
5
  import {
7
6
  Fetch,
@@ -115,25 +114,27 @@ export function unicastFetchWrap<C = FetchContext>({
115
114
  },
116
115
  })
117
116
 
118
- const headers = new Headers(init?.headers)
119
- headers.set('connection', 'close') // Proactively close the connection
120
-
121
117
  try {
122
- return await fetch.call(this, input, {
118
+ const headers = new Headers(init?.headers)
119
+ headers.set('connection', 'close') // Proactively close the connection
120
+
121
+ const response = await fetch.call(this, input, {
123
122
  ...init,
124
123
  headers,
125
124
  // @ts-expect-error non-standard option
126
125
  dispatcher,
127
126
  })
128
- } finally {
129
- // Free resources (we cannot await here since the response was not
130
- // consumed yet).
131
- void dispatcher.close().catch((err) => {
132
- // No biggie, but let's still log it
133
- console.warn('Failed to close dispatcher', err)
134
- })
135
127
 
136
128
  if (!didLookup) {
129
+ // We need to ensure that the body is discarded. We can either
130
+ // consume the whole body (for await loop) in order to keep the
131
+ // socket alive, or cancel the request. Since we sent "connection:
132
+ // close", there is no point in consuming the whole response
133
+ // (which would cause un-necessary bandwidth).
134
+ //
135
+ // https://undici.nodejs.org/#/?id=garbage-collection
136
+ await response.body?.cancel()
137
+
137
138
  // If you encounter this error, either upgrade to Node.js >=21 or
138
139
  // make sure that the dispatcher passed through the requestInit
139
140
  // object ends up being used to make the request.
@@ -145,6 +146,15 @@ export function unicastFetchWrap<C = FetchContext>({
145
146
  'Unable to enforce SSRF protection',
146
147
  )
147
148
  }
149
+
150
+ return response
151
+ } finally {
152
+ // Free resources (we cannot await here since the response was not
153
+ // consumed yet).
154
+ void dispatcher.close().catch((err) => {
155
+ // No biggie, but let's still log it
156
+ console.warn('Failed to close dispatcher', err)
157
+ })
148
158
  }
149
159
  }
150
160
  }
@@ -157,8 +167,8 @@ export function unicastLookup(
157
167
  options: dns.LookupOptions,
158
168
  callback: Parameters<LookupFunction>[2],
159
169
  ) {
160
- if (!isValidDomain(hostname)) {
161
- callback(new Error('Hostname is not a public domain'), '')
170
+ if (isLocalHostname(hostname)) {
171
+ callback(new Error('Hostname is not a public domain'), [])
162
172
  return
163
173
  }
164
174
 
@@ -183,12 +193,23 @@ export function unicastLookup(
183
193
  })
184
194
  }
185
195
 
186
- // see lupomontero/psl#258 for context on psl usage.
187
- // in short, this ensures a structurally valid domain
188
- // plus a "listed" tld.
189
- function isValidDomain(domain: string) {
190
- const parsed = pslParse(domain)
191
- return !parsed.error && parsed.listed
196
+ /**
197
+ * @param hostname - a syntactically valid hostname
198
+ * @returns whether the hostname is a name typically used for on locale area networks.
199
+ * @note **DO NOT** use for security reasons. Only as heuristic.
200
+ */
201
+ export function isLocalHostname(hostname: string): boolean {
202
+ const parts = hostname.split('.')
203
+ if (parts.length < 2) return true
204
+
205
+ const tld = parts.at(-1)!.toLowerCase()
206
+ return (
207
+ tld === 'test' ||
208
+ tld === 'local' ||
209
+ tld === 'localhost' ||
210
+ tld === 'invalid' ||
211
+ tld === 'example'
212
+ )
192
213
  }
193
214
 
194
215
  function isNotUnicast(ip: ipaddr.IPv4 | ipaddr.IPv6): boolean {
@@ -1 +1 @@
1
- {"root":["./src/index.ts","./src/safe.ts","./src/unicast.ts","./src/util.ts"],"version":"5.6.3"}
1
+ {"root":["./src/index.ts","./src/safe.ts","./src/unicast.ts","./src/util.ts"],"version":"5.8.2"}