@nocobase/utils 2.1.0-beta.13 → 2.1.0-beta.15

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/lib/index.d.ts CHANGED
@@ -44,4 +44,5 @@ export * from './variable-usage';
44
44
  export * from './wrap-middleware';
45
45
  export * from './run-sql';
46
46
  export * from './liquidjs';
47
+ export * from './server-request';
47
48
  export { lodash };
package/lib/index.js CHANGED
@@ -80,6 +80,7 @@ __reExport(src_exports, require("./variable-usage"), module.exports);
80
80
  __reExport(src_exports, require("./wrap-middleware"), module.exports);
81
81
  __reExport(src_exports, require("./run-sql"), module.exports);
82
82
  __reExport(src_exports, require("./liquidjs"), module.exports);
83
+ __reExport(src_exports, require("./server-request"), module.exports);
83
84
  // Annotate the CommonJS export names for ESM import in node:
84
85
  0 && (module.exports = {
85
86
  Schema,
@@ -119,5 +120,6 @@ __reExport(src_exports, require("./liquidjs"), module.exports);
119
120
  ...require("./variable-usage"),
120
121
  ...require("./wrap-middleware"),
121
122
  ...require("./run-sql"),
122
- ...require("./liquidjs")
123
+ ...require("./liquidjs"),
124
+ ...require("./server-request")
123
125
  });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { AxiosRequestConfig, AxiosResponse } from 'axios';
10
+ /**
11
+ * Match a hostname against a domain pattern.
12
+ * `*.example.com` matches exactly one subdomain level (e.g. `foo.example.com`)
13
+ * but not `example.com` itself or deeper levels like `a.b.example.com`.
14
+ */
15
+ export declare function matchesDomainPattern(hostname: string, pattern: string): boolean;
16
+ /**
17
+ * Validate a URL against the SERVER_REQUEST_WHITELIST environment variable.
18
+ *
19
+ * Throws an error if:
20
+ * - The URL scheme is not http or https.
21
+ * - SERVER_REQUEST_WHITELIST is set and the host does not match any entry.
22
+ *
23
+ * Silently returns for relative URLs (no scheme) so that internal API calls
24
+ * that use a relative path are not affected.
25
+ *
26
+ * Prefer using {@link serverRequest} over calling this directly.
27
+ */
28
+ export declare function checkUrlAgainstWhitelist(url?: string): void;
29
+ /**
30
+ * Drop-in replacement for `axios.request()` with built-in SSRF protection.
31
+ *
32
+ * Validates `config.url` against {@link checkUrlAgainstWhitelist} before
33
+ * forwarding to axios. Use this instead of calling axios directly for all
34
+ * server-initiated outbound HTTP requests.
35
+ */
36
+ export declare function serverRequest<T = any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>>;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __create = Object.create;
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
13
+ var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __getProtoOf = Object.getPrototypeOf;
15
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
16
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, { get: all[name], enumerable: true });
20
+ };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (let key of __getOwnPropNames(from))
24
+ if (!__hasOwnProp.call(to, key) && key !== except)
25
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
26
+ }
27
+ return to;
28
+ };
29
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
30
+ // If the importer is in node compatibility mode or this is not an ESM
31
+ // file that has been converted to a CommonJS file using a Babel-
32
+ // compatible transform (i.e. "__esModule" has not been set), then set
33
+ // "default" to the CommonJS "module.exports" for node compatibility.
34
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
35
+ mod
36
+ ));
37
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
38
+ var server_request_exports = {};
39
+ __export(server_request_exports, {
40
+ checkUrlAgainstWhitelist: () => checkUrlAgainstWhitelist,
41
+ matchesDomainPattern: () => matchesDomainPattern,
42
+ serverRequest: () => serverRequest
43
+ });
44
+ module.exports = __toCommonJS(server_request_exports);
45
+ var import_ipaddr = __toESM(require("ipaddr.js"));
46
+ var import_axios = __toESM(require("axios"));
47
+ const ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
48
+ function matchesIpEntry(hostname, entry) {
49
+ try {
50
+ let addr = import_ipaddr.default.parse(hostname);
51
+ if (addr.kind() === "ipv6" && addr.isIPv4MappedAddress()) {
52
+ addr = addr.toIPv4Address();
53
+ }
54
+ if (entry.includes("/")) {
55
+ const cidr = import_ipaddr.default.parseCIDR(entry);
56
+ if (addr.kind() !== cidr[0].kind()) return false;
57
+ if (addr.kind() === "ipv4") {
58
+ return addr.match(cidr);
59
+ }
60
+ return addr.match(cidr);
61
+ }
62
+ let entryAddr = import_ipaddr.default.parse(entry);
63
+ if (entryAddr.kind() === "ipv6" && entryAddr.isIPv4MappedAddress()) {
64
+ entryAddr = entryAddr.toIPv4Address();
65
+ }
66
+ return addr.toString() === entryAddr.toString();
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+ __name(matchesIpEntry, "matchesIpEntry");
72
+ function matchesDomainPattern(hostname, pattern) {
73
+ const h = hostname.toLowerCase();
74
+ const p = pattern.toLowerCase();
75
+ if (p.startsWith("*.")) {
76
+ const suffix = p.slice(1);
77
+ if (!h.endsWith(suffix) || h.length <= suffix.length) return false;
78
+ const prefix = h.slice(0, h.length - suffix.length);
79
+ return !prefix.includes(".");
80
+ }
81
+ return h === p;
82
+ }
83
+ __name(matchesDomainPattern, "matchesDomainPattern");
84
+ function matchesEntry(hostname, entry) {
85
+ const e = entry.trim();
86
+ if (!e) return false;
87
+ return import_ipaddr.default.isValid(hostname) ? matchesIpEntry(hostname, e) : matchesDomainPattern(hostname, e);
88
+ }
89
+ __name(matchesEntry, "matchesEntry");
90
+ function checkUrlAgainstWhitelist(url) {
91
+ if (!url) return;
92
+ if (!url.includes("://")) return;
93
+ let parsed;
94
+ try {
95
+ parsed = new URL(url);
96
+ } catch {
97
+ return;
98
+ }
99
+ if (!ALLOWED_PROTOCOLS.has(parsed.protocol)) {
100
+ throw new Error(
101
+ `URL scheme "${parsed.protocol.replace(":", "")}" is not allowed. Only http and https are permitted.`
102
+ );
103
+ }
104
+ const whitelist = process.env.SERVER_REQUEST_WHITELIST;
105
+ if (!whitelist || !whitelist.trim()) return;
106
+ const entries = whitelist.split(",").map((e) => e.trim()).filter(Boolean);
107
+ if (entries.length === 0) return;
108
+ const { hostname } = parsed;
109
+ const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
110
+ for (const entry of entries) {
111
+ if (matchesEntry(host, entry)) return;
112
+ }
113
+ throw new Error(
114
+ `Outbound request to "${host}" is blocked. Add it to SERVER_REQUEST_WHITELIST to allow this request.`
115
+ );
116
+ }
117
+ __name(checkUrlAgainstWhitelist, "checkUrlAgainstWhitelist");
118
+ async function serverRequest(config) {
119
+ checkUrlAgainstWhitelist(config.url);
120
+ return import_axios.default.request(config);
121
+ }
122
+ __name(serverRequest, "serverRequest");
123
+ // Annotate the CommonJS export names for ESM import in node:
124
+ 0 && (module.exports = {
125
+ checkUrlAgainstWhitelist,
126
+ matchesDomainPattern,
127
+ serverRequest
128
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/utils",
3
- "version": "2.1.0-beta.13",
3
+ "version": "2.1.0-beta.15",
4
4
  "main": "lib/index.js",
5
5
  "types": "./lib/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -8,16 +8,18 @@
8
8
  "@budibase/handlebars-helpers": "0.14.0",
9
9
  "@hapi/topo": "^6.0.0",
10
10
  "@rc-component/mini-decimal": "^1.1.0",
11
+ "axios": "^1.7.0",
11
12
  "dayjs": "^1.11.9",
12
13
  "deepmerge": "^4.2.2",
13
14
  "flat-to-nested": "^1.1.1",
14
15
  "fs-extra": "^11.1.1",
15
16
  "graphlib": "^2.1.8",
16
17
  "handlebars": "^4.7.8",
18
+ "ipaddr.js": "^1.9.1",
17
19
  "liquidjs": "^10.23.0",
18
20
  "multer": "^1.4.5-lts.2",
19
21
  "object-path": "^0.11.8",
20
22
  "ses": "^1.14.0"
21
23
  },
22
- "gitHead": "691716e5f4e5f8bd3859d65bc8a29b4e3c32209b"
24
+ "gitHead": "dc1aceea6357e6ab149976c2a236fc4b6bee1370"
23
25
  }