Compare commits

...

6 Commits

Author SHA1 Message Date
Eva Marco
750b1c6409 🐛 Fix negative border radius token 2026-02-12 11:25:26 +01:00
Andrey Antukh
59711a1cf8 📎 Update changelog 2026-02-10 11:57:01 +01:00
Andrey Antukh
06e5825c8a 🐛 Add proper input checking to font related RCP method 2026-02-10 10:36:57 +01:00
Andrey Antukh
d30387eb77 Backport docker images changes from develop 2026-02-09 19:21:30 +01:00
Andrey Antukh
33fd672c21 Backport MCP related changes from develop (#8306) 2026-02-09 18:00:43 +01:00
Andrey Antukh
a7b2e98b8e ⬆️ Use latest imagemagick version on docker images 2026-02-09 13:19:26 +01:00
17 changed files with 245 additions and 15 deletions

View File

@@ -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

View File

@@ -1,5 +1,14 @@
# CHANGELOG
## 2.13.2
### :bug: Bugs fixed
- Fix security issue (Path Traversal Vulnerability) on fonts related RPC method
- Fix allow creating negative border radius tokens [Taiga #13317](https://tree.taiga.io/project/penpot/issue/13317)
## 2.13.1
### :bug: Bugs fixed

View File

@@ -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]]

View File

@@ -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))))))

View File

@@ -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

View File

@@ -125,7 +125,7 @@ RUN set -ex; \
COPY --from=build /opt/jre /opt/jre
COPY --from=build /opt/node /opt/node
COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick
COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick
ARG BUNDLE_PATH="./bundle-backend/"
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/backend/

View File

@@ -107,7 +107,7 @@ RUN set -eux; \
ARG BUNDLE_PATH="./bundle-exporter/"
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/exporter/
COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick
COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick
WORKDIR /opt/penpot/exporter
USER penpot:penpot

View File

@@ -0,0 +1,58 @@
FROM ubuntu:24.04
LABEL maintainer="Penpot <docker@penpot.app>"
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"]

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,48 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.workspace.tokens.management.forms.border-radius
(:require
[app.common.data :as d]
[app.common.files.tokens :as cft]
[app.common.schema :as sm]
[app.common.types.token :as cto]
[app.main.ui.workspace.tokens.management.forms.generic-form :as generic]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(defn- make-schema
[tokens-tree]
(sm/schema
[:and
[:map
[:name
[:and
[:string {:min 1 :max 255
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
(sm/update-properties cto/token-name-ref assoc
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error")))
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
#(not (cft/token-name-path-exists? % tokens-tree))]]]
[:value
[:and
[::sm/text]
[:fn {:error/fn #(tr "workspace.tokens.border-radius-token-value-error")}
(fn [value]
(let [n (d/parse-double value)]
(or (nil? n) (not (< n 0)))))]]]
[:description {:optional true}
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]]]]))
(mf/defc form*
[{:keys [token token-type] :rest props}]
(let [props (mf/spread-props props {:token token
:make-schema make-schema
:token-type token-type})]
[:> generic/form* props]))

View File

@@ -160,7 +160,8 @@
(sd/resolve-tokens-interactive)
(rx/mapcat
(fn [resolved-tokens]
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))]
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens (:name token))
_ (prn "token" token)]
(if resolved-value
(rx/of {:value resolved-value})
(rx/of {:error (first errors)}))))))))
@@ -230,9 +231,16 @@
(do
(swap! form assoc-in [:extra-errors input-name] {:message error})
(reset! hint* {:message error :type "error"}))
(let [message (tr "workspace.tokens.resolved-value" value)]
(swap! form update :extra-errors dissoc input-name)
(reset! hint* {:message message :type "hint"}))))))))]
;; This is needed because, SD allows to create br token with negative values
(if (and
(< value 0)
(= :border-radius (:type token)))
(do
(swap! form assoc-in [:extra-errors input-name] {:message (tr "workspace.tokens.border-radius-token-value-error")})
(reset! hint* {:message (tr "workspace.tokens.border-radius-token-value-error") :type "error"}))
(let [message (tr "workspace.tokens.resolved-value" value)]
(swap! form update :extra-errors dissoc input-name)
(reset! hint* {:message message :type "hint"})))))))))]
(fn []
(rx/dispose! subs))))

View File

@@ -10,6 +10,7 @@
[app.common.files.tokens :as cft]
[app.common.types.tokens-lib :as ctob]
[app.main.refs :as refs]
[app.main.ui.workspace.tokens.management.forms.border-radius :as border-radius]
[app.main.ui.workspace.tokens.management.forms.color :as color]
[app.main.ui.workspace.tokens.management.forms.font-family :as font-family]
[app.main.ui.workspace.tokens.management.forms.generic-form :as generic]
@@ -50,4 +51,5 @@
:text-case [:> generic/form* text-case-props]
:text-decoration [:> generic/form* text-decoration-props]
:font-weight [:> generic/form* font-weight-props]
:border-radius [:> border-radius/form* props]
[:> generic/form* props])))

View File

@@ -8080,6 +8080,10 @@ msgstr "Blur value cannot be negative"
msgid "workspace.tokens.shadow-token-spread-value-error"
msgstr "Spread value cannot be negative"
#: src/app/main/ui/workspace/tokens/management/forms/border_radius.cljs
msgid "workspace.tokens.border-radius-token-value-error"
msgstr "Border-radius value cannot be negative"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:42, src/app/main/ui/workspace/tokens/management/create/form.cljs:68
msgid "workspace.tokens.token-name-length-validation-error"
msgstr "Name should be at least 1 character"

View File

@@ -7976,6 +7976,10 @@ msgstr "El valor de blur no puede ser negativo"
msgid "workspace.tokens.shadow-token-spread-value-error"
msgstr "El valor de spread no puede ser negativo"
#: src/app/main/ui/workspace/tokens/management/forms/border_radius.cljs
msgid "workspace.tokens.border-radius-token-value-error"
msgstr "El valor de Border radius no puede ser negativo"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:42, src/app/main/ui/workspace/tokens/management/create/form.cljs:68
msgid "workspace.tokens.token-name-length-validation-error"
msgstr "El nombre debería ser de al menos 1 caracter"

View File

@@ -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
;;

View File

@@ -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"]
}

View File

@@ -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();