Skip to content

Sync foss-main with upstream Penpot 2.15.3#25

Merged
UsamaSadiq merged 179 commits into
foss-mainfrom
sync/upstream-2.15.3
May 20, 2026
Merged

Sync foss-main with upstream Penpot 2.15.3#25
UsamaSadiq merged 179 commits into
foss-mainfrom
sync/upstream-2.15.3

Conversation

@hunzlahmalik
Copy link
Copy Markdown

@hunzlahmalik hunzlahmalik commented May 19, 2026

Summary

Conflict resolution

Only one file had a real conflict: backend/test/backend_tests/rpc_profile_test.clj.

  • Upstream 2.15.3 added 4 new security tests around the prepare-register / register-profile / verify-email flows (GHSA-4937-35vc-hqjj exploit shape, JWE id leak, account takeover invariants, invitation-token propagation).
  • Pressingly had previously renamed the email-change-request test to email-change-request-is-disabled and rewrote its body to assert :email-managed-by-external-idp rejection (matches rpc/commands/profile.clj).
  • Resolution: kept all 4 upstream security tests verbatim, preserved Pressingly's email-change-request-is-disabled test + docstring + assertions.

Pressingly customizations verified post-merge

  • docker/images/files/nginx-entrypoint.shupdate_mpass_signout_url + MPASS_SIGNOUT_URL wiring intact.
  • backend/src/app/rpc/commands/profile.clj:email-managed-by-external-idp rejection intact.
  • backend/src/app/rpc/commands/verify_token.clj:email-managed-by-external-idp belt-and-suspenders intact.
  • backend/src/app/http/auth_request.clj — X-Auth-Request session re-keying, auto-register, askii.ai default-email-domain all intact.

Scope

442 files changed, +19076 / −6315 lines (upstream's diff between 2.14.2 and 2.15.3, plus the conflict resolution).

Test plan

  • Frontend image rebuilds via make dev.build.penpot.frontend (kicked off locally; will update if it fails)
  • https://design.foss.local.dev reachable through mPass SSO after rebuild
  • Version footer shows 2.15.3-22-g5633682a2
  • Email-change still blocked in user settings (mPass regression check)
  • Backend image rebuilds via make dev.build.penpot.backend (slower; do separately)
  • Exporter still produces PDFs through the dashboard

myfunnyandy and others added 30 commits April 8, 2026 17:48
In `preview-next-point`, `st/get-path` was called without extra keys,
which returns the full Shape record. That value was then passed directly
to `path/next-node` as its `content` argument.

`path/next-node` delegates to `impl/path-data`, which only accepts a
`PathData` instance, `nil`, or a sequential collection of segments. A
Shape record matches none of those cases, so `path-data` threw
"unexpected data" every time the user moved the mouse while drawing a
path.

The fix is to call `(st/get-path state :content)` so that only the
`:content` field (a `PathData` instance) is extracted and forwarded to
`path/next-node`.
…ssue

🐛 Fix path drawing preview passing shape instead of content to next-node
* 🐛 Add webp export format to plugin types

Align plugin API typings with runtime export support by including 'webp' in
'Export.type' and updating the exported formats documentation.

Signed-off-by: Marek Hrabe <marekhrabe@me.com>

* 📚 Add plugin-types changelog entry for missing webp export format

Signed-off-by: Marek Hrabe <marekhrabe@me.com>

---------

Signed-off-by: Marek Hrabe <marekhrabe@me.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
…enpot#8929)

The backtrace-tokens-tree function used a namespaced keyword :temp/id
which clj->js converted to the JS property "temp/id". The sd-token-uuid
function then tried to access .id on the sd-token top-level object,
which was undefined, causing "Cannot read properties of undefined
(reading uuid)".

Fix by using the existing token :id instead of generating a temporary
one, and read it from sd-token.original (matching sd-token-name pattern).
Add normalize-coord helper function that clamps coordinate values to
max-safe-int and min-safe-int bounds when reading segments from PathData
binary buffer. Applies normalization to read-segment, impl-walk,
impl-reduce, and impl-lookup functions to ensure coordinates remain
within safe bounds.

Add corresponding test to verify out-of-bounds coordinates are properly
clamped when reading PathData.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
The CLJS implementation of PathData's -nth protocol method had
swapped arguments in the 3-arity version (with default value).
The call (d/in-range? i size) should be (d/in-range? size i)
to match the CLJ implementation. With swapped args, valid indices
always returned the default value, and invalid indices attempted
out-of-bounds buffer reads.
The metadata key was misspelled as :cosnt instead of :const,
preventing the compiler from recognizing the Var as a compile-time
constant.
- Fix 'conten' typo to 'content' in path.cljc docstring
- Fix 'curvle' typo to 'curve' in shape_to_path.cljc docstring
- Replace confusing XOR-style filter with readable
  (contains? #{:line-to :curve-to} ...) in bool.cljc
- Align handler-indices and opposite-index docstrings with
  matching API in path.cljc
Fixes three concrete builder issues in common/files/builder:\n- Use bool type from shape when selecting style source for difference bools\n- Persist :strokes correctly (fix typo :stroks)\n- Validate add-file-media params after assigning default id\n\nAlso adds regression tests in common-tests.files-builder-test and registers them in runner.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: raguirref <ricardoaguirredelafuente@gmail.com>
Remove unrelated local pid file that was accidentally included in previous commit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: raguirref <ricardoaguirredelafuente@gmail.com>
…-media-validation

🐛 Fix builder bool styles and media validation
Refactor use-portal-container to allocate one persistent <div> per
logical category (:modal, :popup, :tooltip, :default) instead of
creating a new div for every component instance. This keeps the DOM
clean with at most four fixed portal containers and eliminates the
arbitrary growth of empty <div> elements on document.body while
preserving the removeChild race condition fix.
lambdaisland/uri's query-string->map uses :multikeys :duplicates by
default: a key that appears once yields a plain string, but the same
key repeated yields a vector. cljs.core/parse-long only accepts
strings and therefore threw "Expected string, got: object" whenever
a URL contained a duplicate 'index' parameter.

Add rt/get-query-param to app.main.router. The helper returns the
scalar value of a query param key, taking the last element when the
value is a sequential (i.e. the key was repeated). Use it at every
call site that feeds a query-param value into parse-long, in both
app.main.ui (page*) and app.main.data.viewer.
…selected

The 'Move to' menu in the dashboard file context menu only filtered
out the first selected file's project from the available target list.
When multiple files from different projects were selected, the other
files' projects still appeared as valid targets, causing a 400
'cant-move-to-same-project' backend error.

Now all selected files' project IDs are collected and excluded from
the available target projects.
The key :podition was used instead of :position when updating the
id-from cell in swap-shapes, silently discarding the position value
and leaving the cell's :position as nil after every swap.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
… stops

When no gradient stop satisfies (<= offset (:offset %)),
d/index-of-pred returns nil. The previous code called (dec nil) in
the start binding before the nil check, throwing a
NullPointerException/ClassCastException. Guard the start binding with
a cond that handles nil before attempting dec.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
get-children-rec passed the original children vector to each recursive
call instead of the updated one that already includes the current
shape. This caused descendant results to be accumulated from the wrong
starting point, losing intermediate shapes. Pass children' (which
includes the current shape) into every recursive call.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
font-weight-keys was listed twice in the set/union call for
typography-keys, a copy-paste error. The duplicate entry has no
functional effect (sets deduplicate), but it is misleading and
suggests a missing key such as font-style-keys in its place.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
\`(get "type" shadow)\` always returns nil because the map and key
arguments were swapped. The correct call is \`(get shadow "type")\`,
which allows the legacy innerShadow detection to work correctly.
Update the test expectation accordingly.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
`(mapcat collect-main-shapes children objects)` passes `objects` as a
second parallel collection instead of threading it as the second
argument to `collect-main-shapes` for each child. Fix by using an
anonymous fn: `(mapcat #(collect-main-shapes % objects) children)`.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
niwinz and others added 24 commits May 11, 2026 16:23
* ✨ Add additional logging and validation for image upload

* 🎉 Add chunked upload support for font variants

Extend the font variant upload flow across frontend, backend, and common
to support the standardized chunked upload protocol.

**Backend:**
- Add \`:font-max-file-size\` config default (30 MiB) and schema entry
- Add \`validate-font-size!\` in \`media.clj\` (mirrors
  \`validate-media-size!\`, raises \`:font-max-file-size-reached\`)
- Extend \`schema:create-font-variant\` to accept either \`:data\`
  (legacy bytes or chunk-vector) or \`:uploads\` (new chunked session
  map), with a validator requiring exactly one
- Add \`prepare-font-data-from-uploads\`: assembles each chunked
  session via \`cmedia/assemble-chunks\`, validates type+size
- Add \`prepare-font-data-from-legacy\`: normalises legacy byte/chunk
  entries, writing to a tempfile (joining via SequenceInputStream),
  validates type+size
- Add structured logging ("init"/"end") with \`:size\`, \`:mtypes\`,
  and \`:elapsed\` in \`create-font-variant\`

**Frontend:**
- \`upload-blob-chunked\` accepts a per-caller \`:chunk-size\` option
- Add \`font-upload-chunk-size\` (10 MiB) and \`upload-font-variant\`
  fn that uploads each mtype as a separate chunked session
- \`on-upload*\` in dashboard fonts now calls \`upload-font-variant\`
  instead of issuing \`create-font-variant\` RPC directly
- \`process-upload\` stores raw ArrayBuffer instead of chunking
  client-side

**Common:**
- Replace \`"font/opentype"\` with \`"font/woff2"\` in \`font-types\`

**Tests:**
- 25 tests / 224 assertions covering all three upload paths (direct
  bytes, legacy chunk-vector, new chunked sessions), size validation,
  and media type validation

Signed-off-by: Andrey Antukh <niwi@niwi.nz>

* 📎 Add a script for check the commit format locally

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* ⬆️ Update dependencies

* 📎 Fix playwright dep
* ✨ Remove usage of RELEASE placeholder on deps.edn

* 🔧 Add Maven cache to CI

---------

Co-authored-by: Yamila Moreno <yamila.moreno@kaleidos.net>
Add escape-html function that escapes HTML special characters and apply
it in the comment editor at four dom/set-html! call sites where
user-provided text is inserted as innerHTML, preventing stored XSS.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Add a shared `schema:font-family` whitelist validator in
app.common.types.font that only allows letters, digits, spaces,
hyphens, underscores, and dots in font family names. Apply the schema
to create-font-variant and update-font RPC endpoints on the
backend, and add client-side validation in the dashboard fonts UI.
Include unit tests for the schema and integration tests for the RPC
handlers.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
# Conflicts:
#	backend/test/backend_tests/rpc_profile_test.clj
@hunzlahmalik
Copy link
Copy Markdown
Author

hunzlahmalik commented May 19, 2026

Local build verified ✅

  • make dev.build.penpot.frontend rebuilt the image cleanly (exit 0).
  • bundles/frontend/version.txt now reads 2.15.3-22-g5633682a2.
  • penpot-frontend container recreated, responds HTTP 200 internally.
  • https://design.foss.local.dev/ returns HTTP 302 → https://auth.foss.local.dev/authorize?… (mPass SSO redirect — exactly what we want, confirming both nginx routing and the SSO middleware survived the merge).

Test plan progress:

  • Frontend image rebuilds
  • design.foss.local.dev reachable through mPass SSO redirect
  • Version footer should show 2.15.3-22-g5633682a2 (verify visually in browser)
  • Email-change still blocked in user settings (regression check — verify visually after logging in)
  • Backend image rebuilds (make dev.build.penpot.backend — ~10-20min, run separately)
  • Exporter still produces PDFs

hunzlahmalik and others added 2 commits May 20, 2026 12:06
On macOS Docker Desktop, Node's fs.cpSync writes files in the bind
mount with a com.docker.grpcfuse.ownership xattr declaring mode 200
even when the host inode is 644. The subsequent `cp -a` (and the
original `rsync -avr`) then chmod the destination to 200, which lands
on the real host inode and renders the bundle unreadable to anything
outside the build container (Linux containers via gRPC-FUSE included).

chmod-ing packages/server/dist before the copy rewrites the xattr to
644 while the host mode is still 644, so cp -a propagates the correct
mode and the bundle is readable downstream.

Behaviour on Linux hosts is unchanged: gRPC-FUSE only runs on macOS
Docker Desktop; chmod -R is a no-op when modes are already correct.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…attr

fix(mcp-build): normalize dist modes before cp -a (macOS gRPC-FUSE)
@UsamaSadiq UsamaSadiq merged commit de483b2 into foss-main May 20, 2026
1 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.