From 33fd672c21a7dbbab0998de5124e720cb91b39e2 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 9 Feb 2026 18:00:43 +0100 Subject: [PATCH 1/4] :rewind: Backport MCP related changes from develop (#8306) --- .github/workflows/build-docker.yml | 17 ++++++++ manage.sh | 49 +++++++++++++++++++--- mcp/packages/plugin/public/manifest.json | 1 + mcp/packages/server/src/PenpotMcpServer.ts | 4 +- 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 2d03826b65..c1901c9114 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -59,6 +59,7 @@ jobs: mv penpot/frontend bundle-frontend mv penpot/exporter bundle-exporter mv penpot/storybook bundle-storybook + mv penpot/mcp bundle-mcp popd - name: Set up Docker Buildx @@ -89,6 +90,7 @@ jobs: backend exporter storybook + mcp labels: | bundle_version=${{ steps.bundles.outputs.bundle_version }} @@ -152,6 +154,21 @@ jobs: cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max + - name: Build and push MCP Docker image + uses: docker/build-push-action@v6 + env: + DOCKER_IMAGE: 'mcp' + BUNDLE_PATH: './bundle-mcp' + with: + context: ./docker/images/ + file: ./docker/images/Dockerfile.mcp + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.gh_ref }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache + cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max + - name: Notify Mattermost if: failure() uses: mattermost/action-mattermost-notify@master diff --git a/manage.sh b/manage.sh index d16e08b613..eb87d41b14 100755 --- a/manage.sh +++ b/manage.sh @@ -124,7 +124,7 @@ function run-devenv-shell { docker exec -ti \ -e JAVA_OPTS="$JAVA_OPTS" \ -e EXTERNAL_UID=$CURRENT_USER_ID \ - penpot-devenv-main sudo -EH -u penpot bash; + penpot-devenv-main sudo -EH -u penpot $@ } function run-devenv-isolated-shell { @@ -138,7 +138,7 @@ function run-devenv-isolated-shell { -e SHADOWCLJS_EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS \ -e JAVA_OPTS="$JAVA_OPTS" \ -w /home/penpot/penpot/$1 \ - $DEVENV_IMGNAME:latest sudo -EH -u penpot bash + $DEVENV_IMGNAME:latest sudo -EH -u penpot $@ } function build-imagemagick-docker-image { @@ -215,6 +215,23 @@ function build-frontend-bundle { echo ">> bundle frontend end"; } +function build-mcp-bundle { + echo ">> bundle mcp start"; + + mkdir -p ./bundles + local version=$(print-current-version); + local bundle_dir="./bundles/mcp"; + + build "mcp"; + + rm -rf $bundle_dir; + mv ./mcp/dist $bundle_dir; + echo $version > $bundle_dir/version.txt; + put-license-file $bundle_dir; + echo ">> bundle mcp end"; +} + + function build-backend-bundle { echo ">> bundle backend start"; @@ -309,6 +326,16 @@ function build-exporter-docker-image { popd; } +function build-mcp-docker-image { + rsync -avr --delete ./bundles/mcp/ ./docker/images/bundle-mcp/; + pushd ./docker/images; + docker build \ + -t penpotapp/mcp:$CURRENT_BRANCH -t penpotapp/mcp:latest \ + --build-arg BUNDLE_PATH="./bundle-mcp/" \ + -f Dockerfile.mcp .; + popd; +} + function build-storybook-docker-image { rsync -avr --delete ./bundles/storybook/ ./docker/images/bundle-storybook/; pushd ./docker/images; @@ -335,17 +362,19 @@ function usage { echo "- isolated-shell Starts a bash shell in a new devenv container." echo "- log-devenv Show logs of the running devenv docker compose service." echo "" - echo "- build-bundle Build all bundles (frontend, backend and exporter)." + echo "- build-bundle Build all bundles (frontend, backend, exporter, storybook and mcp)." echo "- build-frontend-bundle Build frontend bundle" echo "- build-backend-bundle Build backend bundle." echo "- build-exporter-bundle Build exporter bundle." echo "- build-storybook-bundle Build storybook bundle." + echo "- build-mcp-bundle Build mcp bundle." echo "- build-docs-bundle Build docs bundle." echo "" echo "- build-docker-images Build all docker images (frontend, backend and exporter)." echo "- build-frontend-docker-image Build frontend docker images." echo "- build-backend-docker-image Build backend docker images." echo "- build-exporter-docker-image Build exporter docker images." + echo "- build-mcp-docker-image Build exporter docker images." echo "- build-storybook-docker-image Build storybook docker images." echo "" echo "- version Show penpot's version." @@ -397,6 +426,7 @@ case $1 in ## production builds build-bundle) build-frontend-bundle; + build-mcp-bundle; build-backend-bundle; build-exporter-bundle; build-storybook-bundle; @@ -406,6 +436,10 @@ case $1 in build-frontend-bundle; ;; + build-mcp-bundle) + build-mcp-bundle; + ;; + build-backend-bundle) build-backend-bundle; ;; @@ -413,7 +447,7 @@ case $1 in build-exporter-bundle) build-exporter-bundle; ;; - + build-storybook-bundle) build-storybook-bundle; ;; @@ -431,6 +465,7 @@ case $1 in build-frontend-docker-image build-backend-docker-image build-exporter-docker-image + build-mcp-docker-image build-storybook-docker-image ;; @@ -445,7 +480,11 @@ case $1 in build-exporter-docker-image) build-exporter-docker-image ;; - + + build-mcp-docker-image) + build-mcp-docker-image + ;; + build-storybook-docker-image) build-storybook-docker-image ;; diff --git a/mcp/packages/plugin/public/manifest.json b/mcp/packages/plugin/public/manifest.json index 506021c29e..e2a769c7f8 100644 --- a/mcp/packages/plugin/public/manifest.json +++ b/mcp/packages/plugin/public/manifest.json @@ -1,6 +1,7 @@ { "name": "Penpot MCP Plugin", "code": "plugin.js", + "version": 2, "description": "This plugin enables interaction with the Penpot MCP server", "permissions": ["content:read", "content:write", "library:read", "library:write", "comment:read", "comment:write"] } diff --git a/mcp/packages/server/src/PenpotMcpServer.ts b/mcp/packages/server/src/PenpotMcpServer.ts index 1797fdec23..abb7677b11 100644 --- a/mcp/packages/server/src/PenpotMcpServer.ts +++ b/mcp/packages/server/src/PenpotMcpServer.ts @@ -55,8 +55,8 @@ export class PenpotMcpServer { this.port = parseInt(process.env.PENPOT_MCP_SERVER_PORT ?? "4401", 10); this.webSocketPort = parseInt(process.env.PENPOT_MCP_WEBSOCKET_PORT ?? "4402", 10); this.replPort = parseInt(process.env.PENPOT_MCP_REPL_PORT ?? "4403", 10); - this.listenAddress = process.env.PENPOT_MCP_SERVER_LISTEN_ADDRESS ?? "localhost"; - this.serverAddress = process.env.PENPOT_MCP_SERVER_ADDRESS ?? "localhost"; + this.listenAddress = process.env.PENPOT_MCP_SERVER_LISTEN_ADDRESS ?? "0.0.0.0"; + this.serverAddress = process.env.PENPOT_MCP_SERVER_ADDRESS ?? "0.0.0.0"; this.configLoader = new ConfigurationLoader(process.cwd()); this.apiDocs = new ApiDocs(); From d30387eb7757c59900337e976e45d25c87e96cff Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 9 Feb 2026 19:21:30 +0100 Subject: [PATCH 2/4] :rewind: Backport docker images changes from develop --- docker/images/Dockerfile.mcp | 58 +++++++++++++++++++++++++ docker/images/files/nginx.conf.template | 5 ++- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 docker/images/Dockerfile.mcp diff --git a/docker/images/Dockerfile.mcp b/docker/images/Dockerfile.mcp new file mode 100644 index 0000000000..f4d5544c89 --- /dev/null +++ b/docker/images/Dockerfile.mcp @@ -0,0 +1,58 @@ +FROM ubuntu:24.04 +LABEL maintainer="Penpot " + +ENV LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + NODE_VERSION=v22.21.1 \ + DEBIAN_FRONTEND=noninteractive \ + PATH=/opt/node/bin:$PATH + +RUN set -ex; \ + useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \ + mkdir -p /etc/resolvconf/resolv.conf.d; \ + echo "nameserver 127.0.0.11" > /etc/resolvconf/resolv.conf.d/tail; \ + apt-get -qq update; \ + apt-get -qqy --no-install-recommends install \ + curl \ + tzdata \ + locales \ + ca-certificates \ + ; \ + rm -rf /var/lib/apt/lists/*; \ + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \ + locale-gen; \ + find /usr/share/i18n/locales/ -type f ! -name "en_US" ! -name "POSIX" ! -name "C" -delete; + +RUN set -eux; \ + ARCH="$(dpkg --print-architecture)"; \ + case "${ARCH}" in \ + aarch64|arm64) \ + BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-arm64.tar.gz"; \ + ;; \ + amd64|x86_64) \ + BINARY_URL="https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz"; \ + ;; \ + *) \ + echo "Unsupported arch: ${ARCH}"; \ + exit 1; \ + ;; \ + esac; \ + curl -LfsSo /tmp/nodejs.tar.gz ${BINARY_URL}; \ + mkdir -p /opt/node; \ + cd /opt/node; \ + tar -xf /tmp/nodejs.tar.gz --strip-components=1; \ + chown -R root /opt/node; \ + rm -rf /tmp/nodejs.tar.gz; \ + corepack enable; \ + mkdir -p /opt/penpot; \ + chown -R penpot:penpot /opt/penpot; + +ARG BUNDLE_PATH="./bundle-mcp/" +COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/mcp/ + +WORKDIR /opt/penpot/mcp +USER penpot:penpot + +RUN ./setup + +CMD ["node", "index.js", "--multi-user"] diff --git a/docker/images/files/nginx.conf.template b/docker/images/files/nginx.conf.template index dca7262f38..95f88749fb 100644 --- a/docker/images/files/nginx.conf.template +++ b/docker/images/files/nginx.conf.template @@ -130,6 +130,7 @@ http { } location /readyz { + access_log off; proxy_pass $PENPOT_BACKEND_URI$request_uri; } @@ -144,7 +145,7 @@ http { location / { include /etc/nginx/overrides/location.d/*.conf; - location ~* \.(js|css|jpg|png|svg|gif|ttf|woff|woff2|wasm)$ { + location ~* \.(js|css|jpg|png|svg|gif|ttf|woff|woff2|wasm|map)$ { add_header Cache-Control "public, max-age=604800" always; # 7 days } @@ -152,8 +153,10 @@ http { return 301 " /404"; } + add_header X-Frame-Options SAMEORIGIN always; add_header Cache-Control "no-store, no-cache, max-age=0" always; try_files $uri /index.html$is_args$args /index.html =404; + } } } From 06e5825c8a0209889966a4eb5152efd6ff108626 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 10 Feb 2026 10:36:57 +0100 Subject: [PATCH 3/4] :bug: Add proper input checking to font related RCP method --- backend/src/app/rpc/commands/fonts.clj | 3 ++- backend/test/backend_tests/rpc_font_test.clj | 27 ++++++++++++++++++++ common/src/app/common/schema.cljc | 9 +++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/backend/src/app/rpc/commands/fonts.clj b/backend/src/app/rpc/commands/fonts.clj index 8ca20eac49..03c66a968f 100644 --- a/backend/src/app/rpc/commands/fonts.clj +++ b/backend/src/app/rpc/commands/fonts.clj @@ -89,7 +89,8 @@ (def ^:private schema:create-font-variant [:map {:title "create-font-variant"} [:team-id ::sm/uuid] - [:data [:map-of ::sm/text ::sm/any]] + [:data [:map-of ::sm/text [:or ::sm/bytes + [::sm/vec ::sm/bytes]]]] [:font-id ::sm/uuid] [:font-family ::sm/text] [:font-weight [::sm/one-of {:format "number"} valid-weight]] diff --git a/backend/test/backend_tests/rpc_font_test.clj b/backend/test/backend_tests/rpc_font_test.clj index ef19218314..24697ef8d8 100644 --- a/backend/test/backend_tests/rpc_font_test.clj +++ b/backend/test/backend_tests/rpc_font_test.clj @@ -275,3 +275,30 @@ (let [res (th/run-task! :storage-gc-touched {})] (t/is (= 0 (:freeze res))) (t/is (= 3 (:delete res))))))) + +(t/deftest input-sanitization-1 + (with-mocks [mock {:target 'app.rpc.quotes/check! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + proj-id (:default-project-id prof) + font-id (uuid/custom 10 1) + + ttfdata (-> (io/resource "backend_tests/test_files/font-1.ttf") + (io/read*)) + + params {::th/type :create-font-variant + ::rpc/profile-id (:id prof) + :team-id team-id + :font-id font-id + :font-family "somefont" + :font-weight 400 + :font-style "normal" + :data {"font/ttf" "/etc/passwd"}} + out (th/command! params)] + + (t/is (= 0 (:call-count @mock))) + ;; (th/print-result! out) + + (let [error (:error out) + error-data (ex-data error)] + (t/is (th/ex-info? error)))))) diff --git a/common/src/app/common/schema.cljc b/common/src/app/common/schema.cljc index 6c4ecb6ef1..e04307bba8 100644 --- a/common/src/app/common/schema.cljc +++ b/common/src/app/common/schema.cljc @@ -1009,6 +1009,15 @@ {:title "agent" :description "instance of clojure agent"}})) +#?(:clj + (register! + {:type ::bytes + :pred bytes? + :type-properties + {:title "bytes" + :description "bytes array"}})) + + (register! ::any (mu/update-properties :any assoc :gen/gen sg/any)) ;; ---- PREDICATES From 59711a1cf8797327423f1df6e8e485922bcc9db2 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 10 Feb 2026 11:57:01 +0100 Subject: [PATCH 4/4] :paperclip: Update changelog --- CHANGES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 58e9c0a9a5..41e8413c55 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,13 @@ # CHANGELOG + +## 2.13.2 + +### :bug: Bugs fixed + +- Fix security issue (Path Traversal Vulnerability) on fonts related RPC method + + ## 2.13.1 ### :bug: Bugs fixed