@wooksjs/http-proxy 0.7.16 → 0.7.18

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/dist/index.cjs CHANGED
@@ -1,8 +1,42 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
- let node_url = require("node:url");
3
2
  let _wooksjs_event_http = require("@wooksjs/event-http");
4
3
 
5
4
  //#region packages/http-proxy/src/proxy-utils.ts
5
+ const ALLOWED_PROXY_PROTOCOLS = new Set(["http:", "https:"]);
6
+ /**
7
+ * Resolves the upstream URL for a proxy target.
8
+ *
9
+ * The authority (protocol, host, port) is taken solely from `new URL(target)`.
10
+ * The pathname is intentionally NOT re-parsed against the origin: re-parsing a
11
+ * pathname that begins with `//` or `/\` would promote it to a protocol-relative
12
+ * authority, letting request-derived path data hijack the upstream host (SSRF).
13
+ * Only `http:`/`https:` targets are permitted.
14
+ *
15
+ * @param target - The proxy target URL string.
16
+ * @param allowedHosts - Optional allowlist of upstream hostnames. When provided,
17
+ * the resolved hostname must match one entry (string = case-insensitive exact
18
+ * match, RegExp = tested against the hostname); otherwise the request is
19
+ * rejected. An empty array denies every host.
20
+ * @returns A `URL` whose authority is fixed to the parsed target.
21
+ * @throws HttpError(502) if the target is not a valid `http:`/`https:` URL or its
22
+ * host is not in `allowedHosts`.
23
+ */
24
+ function resolveProxyTarget(target, allowedHosts) {
25
+ let url;
26
+ try {
27
+ url = new URL(target);
28
+ } catch {
29
+ throw new _wooksjs_event_http.HttpError(502, "Invalid proxy target URL");
30
+ }
31
+ if (!ALLOWED_PROXY_PROTOCOLS.has(url.protocol)) throw new _wooksjs_event_http.HttpError(502, `Proxy target protocol "${url.protocol}" is not allowed`);
32
+ if (allowedHosts && !isHostAllowed(url.hostname, allowedHosts)) throw new _wooksjs_event_http.HttpError(502, `Proxy target host "${url.hostname}" is not allowed`);
33
+ url.hash = "";
34
+ return url;
35
+ }
36
+ function isHostAllowed(hostname, allowedHosts) {
37
+ const lower = hostname.toLowerCase();
38
+ return allowedHosts.some((entry) => typeof entry === "string" ? entry.toLowerCase() === lower : entry.test(hostname));
39
+ }
6
40
  var IterableRecords = class {
7
41
  constructor() {
8
42
  this.index = 0;
@@ -102,8 +136,8 @@ function useProxy() {
102
136
  const req = raw;
103
137
  const logger = (0, _wooksjs_event_http.useLogger)();
104
138
  return async function proxy(target, opts) {
105
- const targetUrl = new node_url.URL(target);
106
- const url = new node_url.URL(targetUrl.pathname || "/", targetUrl.origin).toString() + targetUrl.search;
139
+ const targetUrl = resolveProxyTarget(target, opts?.allowedHosts);
140
+ const url = targetUrl.toString();
107
141
  const modifiedHeaders = {
108
142
  ...req.headers,
109
143
  host: targetUrl.hostname
package/dist/index.d.ts CHANGED
@@ -11,6 +11,13 @@ interface TWooksProxyControls {
11
11
  interface TWooksProxyOptions {
12
12
  /** Override the HTTP method used for the proxied request. */
13
13
  method?: string;
14
+ /**
15
+ * Allowlist of upstream hostnames. When set, the resolved target host must match
16
+ * one entry (string = case-insensitive exact match, RegExp = tested against the
17
+ * hostname) or the request is rejected with `502`. Use this when the target is
18
+ * built from request input. An empty array denies every host.
19
+ */
20
+ allowedHosts?: Array<string | RegExp>;
14
21
  /** Controls for filtering/transforming outgoing request headers. */
15
22
  reqHeaders?: TWooksProxyControls;
16
23
  /** Controls for filtering/transforming outgoing request cookies. */
package/dist/index.mjs CHANGED
@@ -1,7 +1,41 @@
1
- import { URL } from "node:url";
2
- import { useLogger, useRequest, useResponse } from "@wooksjs/event-http";
1
+ import { HttpError, useLogger, useRequest, useResponse } from "@wooksjs/event-http";
3
2
 
4
3
  //#region packages/http-proxy/src/proxy-utils.ts
4
+ const ALLOWED_PROXY_PROTOCOLS = new Set(["http:", "https:"]);
5
+ /**
6
+ * Resolves the upstream URL for a proxy target.
7
+ *
8
+ * The authority (protocol, host, port) is taken solely from `new URL(target)`.
9
+ * The pathname is intentionally NOT re-parsed against the origin: re-parsing a
10
+ * pathname that begins with `//` or `/\` would promote it to a protocol-relative
11
+ * authority, letting request-derived path data hijack the upstream host (SSRF).
12
+ * Only `http:`/`https:` targets are permitted.
13
+ *
14
+ * @param target - The proxy target URL string.
15
+ * @param allowedHosts - Optional allowlist of upstream hostnames. When provided,
16
+ * the resolved hostname must match one entry (string = case-insensitive exact
17
+ * match, RegExp = tested against the hostname); otherwise the request is
18
+ * rejected. An empty array denies every host.
19
+ * @returns A `URL` whose authority is fixed to the parsed target.
20
+ * @throws HttpError(502) if the target is not a valid `http:`/`https:` URL or its
21
+ * host is not in `allowedHosts`.
22
+ */
23
+ function resolveProxyTarget(target, allowedHosts) {
24
+ let url;
25
+ try {
26
+ url = new URL(target);
27
+ } catch {
28
+ throw new HttpError(502, "Invalid proxy target URL");
29
+ }
30
+ if (!ALLOWED_PROXY_PROTOCOLS.has(url.protocol)) throw new HttpError(502, `Proxy target protocol "${url.protocol}" is not allowed`);
31
+ if (allowedHosts && !isHostAllowed(url.hostname, allowedHosts)) throw new HttpError(502, `Proxy target host "${url.hostname}" is not allowed`);
32
+ url.hash = "";
33
+ return url;
34
+ }
35
+ function isHostAllowed(hostname, allowedHosts) {
36
+ const lower = hostname.toLowerCase();
37
+ return allowedHosts.some((entry) => typeof entry === "string" ? entry.toLowerCase() === lower : entry.test(hostname));
38
+ }
5
39
  var IterableRecords = class {
6
40
  constructor() {
7
41
  this.index = 0;
@@ -101,8 +135,8 @@ function useProxy() {
101
135
  const req = raw;
102
136
  const logger = useLogger();
103
137
  return async function proxy(target, opts) {
104
- const targetUrl = new URL(target);
105
- const url = new URL(targetUrl.pathname || "/", targetUrl.origin).toString() + targetUrl.search;
138
+ const targetUrl = resolveProxyTarget(target, opts?.allowedHosts);
139
+ const url = targetUrl.toString();
106
140
  const modifiedHeaders = {
107
141
  ...req.headers,
108
142
  host: targetUrl.hostname
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wooksjs/http-proxy",
3
- "version": "0.7.16",
3
+ "version": "0.7.18",
4
4
  "description": "Proxy Wooks composable",
5
5
  "keywords": [
6
6
  "api",
@@ -42,10 +42,10 @@
42
42
  "devDependencies": {
43
43
  "typescript": "^5.9.3",
44
44
  "vitest": "^3.2.4",
45
- "@wooksjs/event-http": "^0.7.16"
45
+ "@wooksjs/event-http": "^0.7.18"
46
46
  },
47
47
  "peerDependencies": {
48
- "@wooksjs/event-http": "^0.7.16"
48
+ "@wooksjs/event-http": "^0.7.18"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "rolldown -c ../../rolldown.config.mjs"