@redocly/realm 0.132.0 → 0.132.2

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,27 @@
1
1
  # @redocly/realm
2
2
 
3
+ ## 0.132.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 8d30048cf1: Hardened Realm CORS proxy validation to block private network targets before proxying requests.
8
+
9
+ ## 0.132.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 2c6eb90f48: Fixed security vulnerability `GHSA-39q2-94rc-95cp` by upgrading `dompurify` to version `3.4.0`.
14
+ - adc17017f1: Fixed security vulnerability `CVE-2026-41650` by upgrading `fast-xml-parser` to version `5.7.1`.
15
+ - 3246b1da21: Fixed security vulnerability `CVE-2026-39356` by upgrading `drizzle-orm` to version `0.45.2`.
16
+ - 387d761304: Fixed security vulnerability `CVE-2026-41673` by upgrading `@xmldom/xmldom` to version `0.9.10`.
17
+ - 2ac1683944: Fixed multiple moderate security vulnerabilities in `hono` dependency by upgrading it to version `4.12.14`.
18
+ - Updated dependencies [2c6eb90f48]
19
+ - Updated dependencies [adc17017f1]
20
+ - @redocly/openapi-docs@3.20.1
21
+ - @redocly/asyncapi-docs@1.9.1
22
+ - @redocly/graphql-docs@1.9.1
23
+ - @redocly/portal-plugin-mock-server@0.17.1
24
+
3
25
  ## 0.132.0
4
26
 
5
27
  ### Minor Changes
@@ -1,7 +1,7 @@
1
1
  import type { Context } from 'hono';
2
2
  import type { Store } from '../../store.js';
3
3
  export declare function corsProxyHandler(store?: Store, proxyBasePath?: string): (ctx: Context) => Promise<Response>;
4
- export declare function isPrivateIp(ip: string): boolean;
4
+ export declare function isPrivateIp(rawIp: string): boolean;
5
5
  export declare function resolveCorsProxyTarget(requestUrl: string, proxyBasePath: string): URL | null;
6
6
  export declare const CORS_PROXY_STREAM_HEADER = "x-redocly-proxy-streaming";
7
7
  //# sourceMappingURL=cors-proxy.d.ts.map
@@ -1,2 +1,2 @@
1
- import x from"node:dns";import{isIP as $}from"node:net";import{withPathPrefix as D}from"@redocly/theme/core/utils";import{ServerRoutes as L}from"../../../constants/common.js";import{envConfig as E}from"../../config/env-config.js";import{getRequestOrigin as k}from"../utils/get-request-origin.js";const A=new Set(["connection","keep-alive","proxy-authenticate","proxy-connection","proxy-authorization","te","trailer","transfer-encoding","upgrade","host"]),I=new Set(["cookie","cookie2","accept-encoding"]),M=new Set(["set-cookie","set-cookie2","content-encoding","content-length"]),y="x-redocly-proxy-streaming",H="x-http-method-override",S="x-redocly-cookie";function ee(o,e=D(L.CORS_PROXY)){return async t=>{const r=new URL(t.req.url).pathname;if(r===e||r===`${e}/`)return t.text(`Realm CORS proxy endpoint.
2
- Usage: ${e}/https://api.example.com/path`);const s=j(t.req.url,e);if(!s)return t.text("Invalid proxied URL",400);const a=o?.getConfig().corsProxy?.allowedTargets;if(a&&a.length>0&&!a.some(u=>X(s,u)))return t.text("Target URL is not in the allowed list for CORS proxy",403);const f=k(t),h=s.origin===f,m=s.pathname===e||s.pathname.startsWith(`${e}/`);if(h&&!m)return new Response("Please use a direct request",{status:308,headers:{Location:s.toString(),Vary:"origin","Cache-Control":"private"}});const l=await N(s.hostname);if((!E.isDevelopMode||E.isReunite)&&l&&Y(l))return t.text("Requests to private network addresses are not allowed",403);const i=new Headers,P=v(t.req.raw.headers);for(const[n,u]of t.req.raw.headers)P.has(n.toLowerCase())||I.has(n.toLowerCase())||i.append(n,u);const g=i.get(S);if(g){const n=t.req.raw.headers.get("cookie")||"";i.set("cookie",n?`${n}; ${g}`:g),i.delete(S)}const O=t.req.raw.headers.get("origin")||"";z(O)&&i.delete("origin");let p=t.req.method;const R=i.get(H);R&&(p=R.toUpperCase(),i.delete(H));const w={method:p,headers:i,redirect:"manual"};p!=="GET"&&p!=="HEAD"&&t.req.raw.body&&(w.body=t.req.raw.body,w.duplex="half");let c;try{c=await fetch(s,w)}catch(n){const u=n instanceof Error?n.message:"unknown error",_=n instanceof Error&&n.cause instanceof Error?`: ${n.cause.message}`:"";return t.text(`Failed to proxy request: ${u}${_}`,502)}const T=c.headers.get("content-type")||"";if(V(T)&&t.req.raw.headers.get("sec-fetch-mode")==="navigate")return t.text("Direct browser navigation to proxied HTML or JavaScript content is not allowed",403);const d=new Headers(c.headers),q=v(c.headers);for(const n of q)d.delete(n);for(const n of M)d.delete(n);return d.set(y,"1"),d.set("x-content-type-options","nosniff"),d.has("content-type")||d.set("content-type","text/plain"),new Response(c.body,{status:c.status,statusText:c.statusText,headers:d})}}function W(o){try{return decodeURIComponent(o)}catch{return o}}function b(o){return o.replace(/^(https?):\/(?!\/)/i,"$1://")}function U(o,e){return e?o.includes("?")?e==="?"?o:`${o.endsWith("?")||o.endsWith("&")?o:`${o}&`}${e.slice(1)}`:`${o}${e}`:o}function v(o){const e=new Set(A),t=o.get("connection");if(!t)return e;for(const r of t.split(",")){const s=r.trim().toLowerCase();s&&e.add(s)}return e}function z(o){const e=o.toLowerCase();return e.includes(".redocly.app")||e.includes("localhost")}async function N(o){if($(o))return o;try{const{address:e}=await x.promises.lookup(o);return e}catch{return null}}function Y(o){const e=o.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);return e?C(e[1]):o.includes(":")?F(o):C(o)}function C(o){const e=o.split(".").map(Number);if(e.length!==4||e.some(s=>isNaN(s)))return!0;const[t,r]=e;return t===0||t===10||t===127||t===172&&r>=16&&r<=31||t===192&&r===168||t===169&&r===254||t===100&&r>=64&&r<=127}function F(o){const e=o.toLowerCase();return e==="::1"||e==="::"||e.startsWith("fc")||e.startsWith("fd")||e.startsWith("fe80")}function X(o,e){let t;try{t=new URL(e)}catch{return!1}if(o.origin!==t.origin)return!1;const r=t.pathname.endsWith("/")?t.pathname:`${t.pathname}/`;return o.pathname.startsWith(r)||o.pathname===t.pathname}function V(o){const e=o.toLowerCase();return e.includes("text/html")||e.includes("javascript")||e.includes("application/xhtml")||e.includes("application/xml")||e.includes("image/svg")}function j(o,e){const t=new URL(o),r=t.pathname===e,s=t.pathname.startsWith(`${e}/`);if(!r&&!s)return null;const a=t.pathname.slice(e.length).replace(/^\/+/,"");if(!a)return null;const f=[a,W(a)];for(const h of f){const m=b(h),l=U(m,t.search);try{const i=new URL(l);if(i.protocol==="http:"||i.protocol==="https:")return i}catch{continue}}return null}const te=y;export{te as CORS_PROXY_STREAM_HEADER,ee as corsProxyHandler,Y as isPrivateIp,j as resolveCorsProxyTarget};
1
+ import $ from"node:dns";import{isIP as E}from"node:net";import{withPathPrefix as L}from"@redocly/theme/core/utils";import{ServerRoutes as A}from"../../../constants/common.js";import{envConfig as H}from"../../config/env-config.js";import{getRequestOrigin as D}from"../utils/get-request-origin.js";const M=new Set(["connection","keep-alive","proxy-authenticate","proxy-connection","proxy-authorization","te","trailer","transfer-encoding","upgrade","host"]),k=new Set(["cookie","cookie2","accept-encoding"]),b=new Set(["set-cookie","set-cookie2","content-encoding","content-length"]),y="x-redocly-proxy-streaming",x="x-http-method-override",S="x-redocly-cookie";function oe(n,e=L(A.CORS_PROXY)){return async t=>{const o=new URL(t.req.url).pathname;if(o===e||o===`${e}/`)return t.text(`Realm CORS proxy endpoint.
2
+ Usage: ${e}/https://api.example.com/path`);const r=J(t.req.url,e);if(!r)return t.text("Invalid proxied URL",400);const a=n?.getConfig().corsProxy?.allowedTargets;if(a&&a.length>0&&!a.some(p=>V(r,p)))return t.text("Target URL is not in the allowed list for CORS proxy",403);const f=D(t),h=r.origin===f,m=r.pathname===e||r.pathname.startsWith(`${e}/`);if(h&&!m)return new Response("Please use a direct request",{status:308,headers:{Location:r.toString(),Vary:"origin","Cache-Control":"private"}});const u=await Y(r.hostname);if((!H.isDevelopMode||H.isReunite)&&u&&F(u))return t.text("Requests to private network addresses are not allowed",403);const i=new Headers,I=v(t.req.raw.headers);for(const[s,p]of t.req.raw.headers)I.has(s.toLowerCase())||k.has(s.toLowerCase())||i.append(s,p);const g=i.get(S);if(g){const s=t.req.raw.headers.get("cookie")||"";i.set("cookie",s?`${s}; ${g}`:g),i.delete(S)}const O=t.req.raw.headers.get("origin")||"";U(O)&&i.delete("origin");let l=t.req.method;const R=i.get(x);R&&(l=R.toUpperCase(),i.delete(x));const w={method:l,headers:i,redirect:"manual"};l!=="GET"&&l!=="HEAD"&&t.req.raw.body&&(w.body=t.req.raw.body,w.duplex="half");let c;try{c=await fetch(r,w)}catch(s){const p=s instanceof Error?s.message:"unknown error",_=s instanceof Error&&s.cause instanceof Error?`: ${s.cause.message}`:"";return t.text(`Failed to proxy request: ${p}${_}`,502)}const T=c.headers.get("content-type")||"";if(G(T)&&t.req.raw.headers.get("sec-fetch-mode")==="navigate")return t.text("Direct browser navigation to proxied HTML or JavaScript content is not allowed",403);const d=new Headers(c.headers),q=v(c.headers);for(const s of q)d.delete(s);for(const s of b)d.delete(s);return d.set(y,"1"),d.set("x-content-type-options","nosniff"),d.has("content-type")||d.set("content-type","text/plain"),new Response(c.body,{status:c.status,statusText:c.statusText,headers:d})}}function N(n){try{return decodeURIComponent(n)}catch{return n}}function W(n){return n.replace(/^(https?):\/(?!\/)/i,"$1://")}function z(n,e){return e?n.includes("?")?e==="?"?n:`${n.endsWith("?")||n.endsWith("&")?n:`${n}&`}${e.slice(1)}`:`${n}${e}`:n}function v(n){const e=new Set(M),t=n.get("connection");if(!t)return e;for(const o of t.split(",")){const r=o.trim().toLowerCase();r&&e.add(r)}return e}function U(n){const e=n.toLowerCase();return e.includes(".redocly.app")||e.includes("localhost")}async function Y(n){const e=P(n);if(E(e))return e;try{const{address:t}=await $.promises.lookup(e);return t}catch{return null}}function F(n){const e=P(n).toLowerCase();if(!E(e))return!0;const t=X(e);return t?C(t):e.includes(":")?j(e):C(e)}function P(n){return n.startsWith("[")&&n.endsWith("]")?n.slice(1,-1):n}function X(n){const e=n.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);if(e){const o=parseInt(e[1],16),r=parseInt(e[2],16);return[o>>8&255,o&255,r>>8&255,r&255].join(".")}return n.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i)?.[1]??null}function C(n){const e=n.split(".").map(Number);if(e.length!==4||e.some(r=>isNaN(r)))return!0;const[t,o]=e;return t===0||t===10||t===127||t===172&&o>=16&&o<=31||t===192&&o===168||t===169&&o===254||t===100&&o>=64&&o<=127}function j(n){const e=B(n);return n==="::1"||n==="::"||e!==null&&(e&65024)===64512||e!==null&&(e&65472)===65152}function B(n){const[e]=n.split(":");if(!e)return null;const t=parseInt(e,16);return Number.isNaN(t)?null:t}function V(n,e){let t;try{t=new URL(e)}catch{return!1}if(n.origin!==t.origin)return!1;const o=t.pathname.endsWith("/")?t.pathname:`${t.pathname}/`;return n.pathname.startsWith(o)||n.pathname===t.pathname}function G(n){const e=n.toLowerCase();return e.includes("text/html")||e.includes("javascript")||e.includes("application/xhtml")||e.includes("application/xml")||e.includes("image/svg")}function J(n,e){const t=new URL(n),o=t.pathname===e,r=t.pathname.startsWith(`${e}/`);if(!o&&!r)return null;const a=t.pathname.slice(e.length).replace(/^\/+/,"");if(!a)return null;const f=[a,N(a)];for(const h of f){const m=W(h),u=z(m,t.search);try{const i=new URL(u);if(i.protocol==="http:"||i.protocol==="https:")return i}catch{continue}}return null}const re=y;export{re as CORS_PROXY_STREAM_HEADER,oe as corsProxyHandler,F as isPrivateIp,J as resolveCorsProxyTarget};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/realm",
3
- "version": "0.132.0",
3
+ "version": "0.132.2",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,7 +36,7 @@
36
36
  "@tanstack/react-virtual": "3.13.0",
37
37
  "@redocly/mcp-typescript-sdk": "1.18.1",
38
38
  "@wojtekmaj/react-datetimerange-picker": "6.0.0",
39
- "@xmldom/xmldom": "0.9.9",
39
+ "@xmldom/xmldom": "0.9.10",
40
40
  "ajv-formats": "^3.0.1",
41
41
  "anser": "^2.3.2",
42
42
  "babel-plugin-styled-components": "2.1.4",
@@ -44,7 +44,7 @@
44
44
  "colorette": "2.0.20",
45
45
  "copy-to-clipboard": "3.3.3",
46
46
  "dotenv": "16.4.5",
47
- "drizzle-orm": "^0.44.2",
47
+ "drizzle-orm": "^0.45.0",
48
48
  "enquirer": "2.3.6",
49
49
  "esbuild": "0.27.0",
50
50
  "escape-carriage": "^1.3.1",
@@ -53,7 +53,7 @@
53
53
  "flexsearch": "0.7.43",
54
54
  "graphql": "16.12.0",
55
55
  "gray-matter": "4.0.3",
56
- "hono": "4.12.8",
56
+ "hono": "4.12.14",
57
57
  "htmlparser2": "8.0.2",
58
58
  "i18next": "22.4.15",
59
59
  "is-glob": "4.0.3",
@@ -90,14 +90,14 @@
90
90
  "xpath": "0.0.34",
91
91
  "yaml-ast-parser": "0.0.43",
92
92
  "zod": "^3.25.76",
93
+ "@redocly/asyncapi-docs": "1.9.1",
93
94
  "@redocly/config": "0.48.0",
94
- "@redocly/graphql-docs": "1.9.0",
95
- "@redocly/openapi-docs": "3.20.0",
96
- "@redocly/portal-legacy-ui": "0.15.0",
97
- "@redocly/portal-plugin-mock-server": "0.17.0",
95
+ "@redocly/graphql-docs": "1.9.1",
98
96
  "@redocly/realm-asyncapi-sdk": "0.10.0",
99
97
  "@redocly/theme": "0.64.0",
100
- "@redocly/asyncapi-docs": "1.9.0"
98
+ "@redocly/portal-plugin-mock-server": "0.17.1",
99
+ "@redocly/openapi-docs": "3.20.1",
100
+ "@redocly/portal-legacy-ui": "0.15.0"
101
101
  },
102
102
  "peerDependencies": {
103
103
  "react": "^19.2.4",