From bf0121ccbb1b0ee24859a9cd1e3f53bb104a8815 Mon Sep 17 00:00:00 2001 From: shellsort <> Date: Thu, 29 Oct 2026 18:47:02 +0100 Subject: [PATCH 1/5] Fix CVE-2026-111497: pin Node 22.23.4/26.12.1, nginx 1.30.6 --- deploy/docker/Dockerfile | 23 ++++++++++---------- deploy/docker/agora-token-service/Dockerfile | 2 +- deploy/docker/hocuspocus/Dockerfile | 2 +- server/proxy-service/Dockerfile | 2 +- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index f0ff6f607..4fbbd1abe 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -112,7 +112,7 @@ CMD [ "/bin/sh", "/lowcoder/node-service/entrypoint.sh" ] ## ## Standalone image is built from server/proxy-service/Dockerfile ## -FROM node:22.23-slim AS build-proxy-service +FROM node:22.23.4-slim AS build-proxy-service # Copy only the build inputs, so local build/ and node_modules folders are never picked up COPY server/proxy-service/package.json server/proxy-service/yarn.lock server/proxy-service/tsconfig.json /lowcoder/proxy-service/app/ @@ -137,7 +137,7 @@ RUN chmod +x /lowcoder/proxy-service/*.sh ## ## Standalone image is built from deploy/docker/agora-token-service/Dockerfile ## -FROM node:22.23-slim AS build-agora-token-service +FROM node:22.23.4-slim AS build-agora-token-service WORKDIR /lowcoder/agora-token-service/app COPY deploy/docker/agora-token-service/package.json ./ @@ -157,7 +157,7 @@ RUN chmod +x /lowcoder/agora-token-service/*.sh ## HOCUSPOCUS_SERVER_VERSION in sync with it. ## @hocuspocus/server 4.x requires node >= 22. ## -FROM node:22.23-slim AS build-hocuspocus +FROM node:22.23.4-slim AS build-hocuspocus ARG HOCUSPOCUS_SERVER_VERSION=4.3.0 @@ -176,7 +176,7 @@ RUN chmod +x /lowcoder/hocuspocus/*.sh ## ## Build lowcoder client application ## -FROM node:22.23-slim AS build-client +FROM node:22.23.4-slim AS build-client # curl is required for yarn build to succeed, because it calls it while building client RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates @@ -217,7 +217,7 @@ RUN yarn build ## To create a separate image out of it, build it with: ## DOCKER_BUILDKIT=1 docker build -f deploy/docker/Dockerfile -t lowcoderorg/lowcoder-ce-frontend --target lowcoder-ce-frontend . ## -FROM nginx:1.27.1 AS lowcoder-ce-frontend +FROM nginx:1.30.6 AS lowcoder-ce-frontend LABEL maintainer="lowcoder" # Change default nginx user into lowcoder user and remove default nginx config @@ -258,7 +258,7 @@ EXPOSE 3443 ## ## Build lowcoder client (Enterprise) application ## -FROM node:22.23-slim AS build-client-ee +FROM node:22.23.4-slim AS build-client-ee # curl is required for yarn build to succeed, because it calls it while building client RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates @@ -299,7 +299,7 @@ RUN yarn build ## To create a separate image out of it, build it with: ## DOCKER_BUILDKIT=1 docker build -f deploy/docker/Dockerfile -t lowcoderorg/lowcoder-ee-frontend --target lowcoder-ee-frontend . ## -FROM nginx:1.27.1 AS lowcoder-enterprise-frontend +FROM nginx:1.30.6 AS lowcoder-enterprise-frontend LABEL maintainer="lowcoder" # Change default nginx user into lowcoder user and remove default nginx config @@ -357,12 +357,12 @@ RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg - && curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-archive-keyring.gpg \ && echo "deb [signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] https://repo.mongodb.org/apt/ubuntu `lsb_release -cs`/mongodb-org/7.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-7.0.list \ && curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu `lsb_release -cs` nginx" | tee /etc/apt/sources.list.d/nginx.list + && echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" | tee /etc/apt/sources.list.d/nginx.list # Install required packages RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends -y \ - nginx=1.27.1-1~jammy \ + nginx=1.30.6-1~jammy \ mongodb-org \ redis \ supervisor \ @@ -376,8 +376,8 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-ins # Use configuration setup from official nginx image RUN rm -rf /etc/nginx/nginx.conf -COPY --from=nginx:1.27.1 /docker-entrypoint.d /docker-entrypoint.d -COPY --from=nginx:1.27.1 /docker-entrypoint.sh /docker-entrypoint.sh +COPY --from=nginx:1.30.6 /docker-entrypoint.d /docker-entrypoint.d +COPY --from=nginx:1.30.6 /docker-entrypoint.sh /docker-entrypoint.sh # Add lowcoder user RUN usermod --login lowcoder --uid 9001 nginx \ @@ -450,4 +450,3 @@ EXPOSE 8081 ENTRYPOINT [ "/bin/sh" , "/lowcoder/entrypoint.sh" ] CMD ["/usr/bin/supervisord", "-n" , "-c" , "/lowcoder/etc/supervisord.conf"] - diff --git a/deploy/docker/agora-token-service/Dockerfile b/deploy/docker/agora-token-service/Dockerfile index d148630de..cfb04030b 100644 --- a/deploy/docker/agora-token-service/Dockerfile +++ b/deploy/docker/agora-token-service/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine +FROM node:22.23.4-alpine WORKDIR /app diff --git a/deploy/docker/hocuspocus/Dockerfile b/deploy/docker/hocuspocus/Dockerfile index 2412a2cdb..a4f862b18 100644 --- a/deploy/docker/hocuspocus/Dockerfile +++ b/deploy/docker/hocuspocus/Dockerfile @@ -1,6 +1,6 @@ # Hocuspocus (Yjs WebSocket) server for lowcoder ChatBox. # Build context: client/packages/lowcoder (see Dockerfile.dockerignore next to this file) -FROM node:26-alpine +FROM node:26.12.1-alpine ARG HOCUSPOCUS_SERVER_VERSION=4.3.0 diff --git a/server/proxy-service/Dockerfile b/server/proxy-service/Dockerfile index e728bed6e..19594ab26 100644 --- a/server/proxy-service/Dockerfile +++ b/server/proxy-service/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine +FROM node:22.23.4-alpine WORKDIR /lowcoder/proxy-service From babbc794735e5e89c56f053df6659bca506eb627 Mon Sep 17 00:00:00 2001 From: shellsort <> Date: Wed, 4 Nov 2026 16:55:01 +0100 Subject: [PATCH 2/5] Attempted minimal fix, slight regression on items in list view behavior thou --- .../lowcoder/src/comps/controls/codeStateControl.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx b/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx index d70f911c8..45c8ae51e 100644 --- a/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx @@ -182,7 +182,8 @@ export function withLinkedDefaultValue< const valueChild = (ret.children as any)[valueKey]; const newValue = transformDefaultValue ? transformDefaultValue(defaultValue, ret) : defaultValue; if (!_.isEqual(newValue, valueChild.getView().value)) { - return (ret as any).setChild(valueKey, valueChild.reduce(changeChildAction("value", newValue, false))); + const message = changeChildAction("value", newValue, false); + setTimeout(valueChild.dispatch, 0, message); } } return ret; @@ -305,7 +306,7 @@ export const jsonObjectStateControl = (defaultValue?: JSONObject) => export const arrayObjectStateControl = (defaultValue?: JSONObject[]) => withTmpState(jsonArrayControl(defaultValue), {}); - + export const jsonValueStateControl = (defaultValue?: JSONValue) => withTmpState(jsonValueControl(defaultValue), ""); From 3f4c4e697d9a17eb6d035097de8d7a20d2432ae2 Mon Sep 17 00:00:00 2001 From: shellsort <> Date: Wed, 4 Nov 2026 17:52:12 +0100 Subject: [PATCH 3/5] Fixed regression mentioned in last commit --- .../lowcoder/src/comps/generators/withMultiContext.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/packages/lowcoder/src/comps/generators/withMultiContext.tsx b/client/packages/lowcoder/src/comps/generators/withMultiContext.tsx index 40c2d231a..a0ba7dba8 100644 --- a/client/packages/lowcoder/src/comps/generators/withMultiContext.tsx +++ b/client/packages/lowcoder/src/comps/generators/withMultiContext.tsx @@ -137,8 +137,8 @@ export function withMultiContext(VariantComp comp = this.getOriginalComp(); } else { comp = this.getOriginalComp() - .setParams(params) - .changeDispatch(wrapDispatch(wrapDispatch(this.dispatch, MAP_KEY), key)); + .changeDispatch(wrapDispatch(wrapDispatch(this.dispatch, MAP_KEY), key)) + .setParams(params); } } return comp; @@ -184,8 +184,8 @@ export function withMultiContext(VariantComp if (params) { const childComp = comp .getOriginalComp() - .setParams(params) - .changeDispatch(wrapDispatch(wrapDispatch(comp.dispatch, MAP_KEY), key)); + .changeDispatch(wrapDispatch(wrapDispatch(comp.dispatch, MAP_KEY), key)) + .setParams(params); const newChildComp = childComp.reduce(childAction); if (childComp !== newChildComp) { From 648166aff7e7e976acb5096c8fc627188175b6a5 Mon Sep 17 00:00:00 2001 From: shellsort <> Date: Wed, 4 Nov 2026 20:05:00 +0100 Subject: [PATCH 4/5] Fixed various reset functions not reseting to the components default --- .../src/comps/controls/codeStateControl.tsx | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx b/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx index 45c8ae51e..50779480f 100644 --- a/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx @@ -26,6 +26,7 @@ import { CompAction, CompActionTypes, ConstructorToView, + CustomAction, evalNodeOrMinor, updateNodesV2Action, } from "lowcoder-core"; @@ -42,8 +43,14 @@ import { toStringNumberArray, } from "util/convertUtils"; import { JSONObject, JSONValue } from "util/jsonTypes"; -import { getPromiseAfterDispatch } from "../../util/promiseUtils"; -import { EvalParamType, ParamConfig, ParamType } from "./actionSelector/executeCompTypes"; +import { getPromiseAfterDispatch, handlePromiseAfterResult } from "../../util/promiseUtils"; +import { + EvalParamType, + ExecuteAction, + MethodConfig, + ParamConfig, + ParamType, +} from "./actionSelector/executeCompTypes"; export const __TMP_STATE_FIELD_NAME = "__TMP_STATE_FIELD_NAME"; @@ -188,6 +195,43 @@ export function withLinkedDefaultValue< } return ret; } + + // Someone called a method on this component, e.g. switch1.resetValue(). + // "call" holds the method's name and its arguments. + // Returns true if this function took care of the call. + executeMethod(call: CustomAction): boolean { + const calledMethodName = call.value.methodName; + + // What the component shows right now (e.g. the switch is on or off). + const currentValueField = (this.children as any)[valueKey]; + + // What was entered as "Default value" in the property panel. + const defaultValueField = (this.children as any)[defaultValueKey]; + + // The current value field offers three methods: set…, clear… and reset… + // (e.g. setValue, clearValue, resetValue). Find the name of the reset one. + const currentValueMethods: MethodConfig[] = currentValueField.getMethodConfig(); + const resetMethod = currentValueMethods.find((method) => method.name.startsWith("reset")); + + if (calledMethodName !== resetMethod?.name) { + // Any other method (setValue, clearValue, focus, ...): handle it as before. + return super.executeMethod(call); + } + + // The current value field's own reset would read a default of its own, + // which is always empty. Take the one from the property panel instead. + let defaultValue = defaultValueField.getView().value; + if (transformDefaultValue) { + defaultValue = transformDefaultValue(defaultValue, this); + } + + // Same as calling setValue(defaultValue). + const doneWhenChanged = currentValueField.change(defaultValue); + + // Lets the caller wait for the change, e.g. "await switch1.resetValue()". + handlePromiseAfterResult(call, doneWhenChanged); + return true; + } } return LinkedDefaultValueComp as unknown as TCtor; From 7404277ee581f07a43531a9ab98062e21687a3c9 Mon Sep 17 00:00:00 2001 From: shellsort <> Date: Wed, 4 Nov 2026 21:28:45 +0100 Subject: [PATCH 5/5] setDefaultValue and clearDefaultValue now also have an effect on the current value --- .../src/comps/controls/codeStateControl.tsx | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx b/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx index 50779480f..85a85d0c0 100644 --- a/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/codeStateControl.tsx @@ -180,20 +180,39 @@ export function withLinkedDefaultValue< ): TCtor { class LinkedDefaultValueComp extends (Base as any) { reduce(action: CompAction): this { - const ret = super.reduce(action) as this; - if ( - action.type === CompActionTypes.UPDATE_NODES_V2 && - (ret.children as any)[defaultValueKey] !== (this.children as any)[defaultValueKey] - ) { - const defaultValue = (ret.children as any)[defaultValueKey].getView().value; - const valueChild = (ret.children as any)[valueKey]; - const newValue = transformDefaultValue ? transformDefaultValue(defaultValue, ret) : defaultValue; - if (!_.isEqual(newValue, valueChild.getView().value)) { - const message = changeChildAction("value", newValue, false); - setTimeout(valueChild.dispatch, 0, message); - } + const updated = super.reduce(action) as this; + + const defaultValueFieldBefore = (this.children as any)[defaultValueKey]; + const defaultValueFieldAfter = (updated.children as any)[defaultValueKey]; + if (defaultValueFieldAfter === defaultValueFieldBefore) { + return updated; + } + + const defaultBefore = defaultValueFieldBefore.getView().value; + const defaultAfter = defaultValueFieldAfter.getView().value; + if (_.isEqual(defaultBefore, defaultAfter)) { + return updated; + } + + const currentValueField = (updated.children as any)[valueKey]; + + let newValue = defaultAfter; + if (transformDefaultValue) { + newValue = transformDefaultValue(newValue, updated); + } + if (_.isEqual(newValue, currentValueField.getView().value)) { + return updated; } - return ret; + + const setValueMessage = changeChildAction("value", newValue, false); + + if (action.type === CompActionTypes.UPDATE_NODES_V2) { + setTimeout(currentValueField.dispatch, 0, setValueMessage); + return updated; + } + + const updatedCurrentValueField = currentValueField.reduce(setValueMessage); + return (updated as any).setChild(valueKey, updatedCurrentValueField); } // Someone called a method on this component, e.g. switch1.resetValue().