23289 Commits

Author SHA1 Message Date
María Valderrama
6d49fb2be0
🐛 Fix organization dropdown alignment (#11216) 2026-08-12 16:52:27 +02:00
Belén Albeza
fee416d275
🐛 Fix crash after changing typography options (v2 and v3) (#11221) 2026-08-12 15:10:23 +02:00
Eva Marco
986ee60cad
🐛 Fix invitation loop (#11223) 2026-08-12 13:20:21 +02:00
Eva Marco
e5c80edbf3
🐛 Fix libraries grid layout (#11226) 2026-08-12 13:20:07 +02:00
David Barragán Merino
732162e720 🔧 Report flaky e2e tests in integration workflow
Enable Playwright's JSON reporter alongside `list` and publish a
summary of flaky tests to the job step summary. The JSON report is
kept as an artifact for 30 days so flakiness rates can be aggregated
over time.

CI already runs with `retries: 2`, so unstable tests have been passing
silently on retry. This only surfaces what the suite already absorbs;
no test behaviour changes.

The reporter in `frontend/scripts/test-e2e` becomes overridable via
`PLAYWRIGHT_REPORTER` so the local developer default stays untouched.
2026-08-12 12:52:30 +02:00
María Valderrama
985d219810
🐛 Fix confusing copy for feams in organizations (#11213) 2026-08-11 14:36:07 +02:00
Eva Marco
53985dc630
🐛 Fix setting dark theme on onboarding (#11212)
* 🐛 Fix setting dark theme on onboarding

* 🎉 Add test
2026-08-11 13:49:48 +02:00
Marina López
02c31e7348
🐛 Cache Nitrate SSO checks during navigation (#11209) 2026-08-11 13:30:04 +02:00
Eva Marco
69ef7e86cd
🐛 Fix colorpicker z-index (#11207) 2026-08-11 13:06:30 +02:00
Eva Marco
d7daefafe2
🐛 Fix select shape after enter path edition (#11205) 2026-08-11 13:05:47 +02:00
Eva Marco
c4d1a1bc94
🐛 Fix node deleting (#11126)
* 🐛 Fix delete path node

* 🐛 Fix typography on shortcuts list
2026-08-11 13:03:29 +02:00
María Valderrama
1e6d438257
🐛 Fix SSO failure logging user out instead of showing error page (#11129)
* 🐛 Fix SSO failure logging user out instead of showing error page

* 📎 Code review
2026-08-11 09:14:29 +02:00
María Valderrama
d4294bbf1e
🐛 Fix missing membership check in create-team (#11166) 2026-08-11 09:13:13 +02:00
Filip Sajdak
83efa28b12
🐛 Keep comment bubbles from painting over the rulers (#11168)
The comments layer lives in the viewport overlays, which are absolutely
positioned above the canvas, and the container itself carries a high
z-index. A comment bubble panned into the ruler bars therefore painted
on top of them, covering the ticks and numbers.

Clip the comments container to the area outside the ruler bars while
the rulers are visible, the same thing the `clip-handlers` clip path
already does so the selection handlers stay off the rulers. Clipping
only the comments container leaves the text editing overlay, which
shares the viewport overlays, untouched.

Fixes #11163.

Signed-off-by: Filip Sajdak <filip.sajdak@siili.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 16:18:58 +02:00
Luis de Dios
16e52b0494
🐛 Fix error page logo not visible in dark mode (#11167) 2026-08-10 15:51:31 +02:00
Marina López
0fd2a9d26f
🐛 Secure organization invitation creation (#11164) 2026-08-10 13:45:15 +02:00
Marina López
5d2cb22966
Fetch team organization in a single batch (#11140) 2026-08-10 13:43:27 +02:00
Marina López
900a7ef498
♻️ Show subscription section to everybody (#11007) 2026-08-10 13:42:28 +02:00
Luis de Dios
86c563f11f
🐛 Fix font family typography asset persist across files in new created text layers (#11134) 2026-08-10 11:35:51 +02:00
Jules
d63d6370c0
🐛 Fix stale DNS caching in frontend nginx MCP proxy (#10947)
The generated /etc/nginx/overrides/server.d/mcp-locations.conf used a
plain proxy_pass target (e.g. `proxy_pass http://penpot-mcp:4402;`)
where $PENPOT_MCP_URI/$PENPOT_MCP_URI_WS are shell variables substituted
once by envsubst in nginx-entrypoint.sh at container startup, not nginx
variables. nginx resolves a literal proxy_pass hostname once when the
config loads and never re-checks it, so the existing
`resolver 127.0.0.11 valid=10s;` directive in
overrides/http.d/resolvers.conf has no effect on these three locations
- it only applies to nginx variables evaluated per-request.

In multi-container deployments where the penpot-mcp container restarts
or is recreated independently of penpot-frontend (image update, OOM,
orchestrator reschedule), it gets a new IP from Docker's/the
orchestrator's DNS, and the frontend's nginx keeps forwarding to the
old, now-dead address until penpot-frontend itself is restarted. This
surfaces to users as `wss://<host>/mcp/ws` failing to connect from the
browser after enabling the MCP plugin, with
`connect() failed (111: Connection refused)` in the frontend's nginx
logs.

Route each location through a `set $var ...; proxy_pass $var;` pair so
proxy_pass evaluates a real nginx variable, letting the pre-existing
resolver directive re-resolve penpot-mcp within its 10s TTL instead of
caching the address for the container's lifetime.

For /mcp/stream and /mcp/sse, the set value also appends
$is_args$args explicitly: when proxy_pass targets a variable AND that
variable's value includes a URI/path component, nginx does not
automatically forward the original request's query string the way it
does for a static proxy_pass target - it must be appended by hand, or
the userToken query parameter used for multi-user authentication is
silently dropped before reaching the MCP server. /mcp/ws has no path
component in its target so it isn't affected by this and needed no
such change.

Verified locally: force-recreated the penpot-mcp container onto a
different IP while leaving penpot-frontend untouched; the /mcp/ws
WebSocket upgrade kept returning 101 Switching Protocols throughout,
both immediately and after the resolver's TTL window. Separately
verified /mcp/stream: a POST with ?userToken=... now shows up
server-side as userTokenFp=<redacted first 8 chars> instead of <none>,
and an actual MCP client (Claude Code) using this proxy can now call
authenticated tools like execute_code successfully.

Signed-off-by: Jules LaPrairie <jules@lucidbox.ca>
2026-08-10 11:34:52 +02:00
Luis de Dios
b9c92496f1
🐛 Fix overrides lost after switch (#10619) 2026-08-10 10:56:04 +02:00
Andrey Antukh
e01b36b841
🐛 Add project-id guard to use-plugin-register layout effect (#10859)
Add `project-id` to the guard condition in `use-plugin-register`'s layout effect so the plugin "Try out" flow waits until projects have loaded.

Previously, only `plugin-url` was checked, which allowed the fetch to fire
before projects were available, sending a nil `project-id` and causing a 400
validation error from the backend.

AI-assisted-by: mimo-v2.5-pro
2026-08-07 19:21:32 +02:00
Andrey Antukh
2f04fcddbf
🐛 Invalidate all sessions on profile deletion (#11115)
When a profile is deleted, only the current session was being
invalidated. Other active sessions on different devices remained
functional until the background cleanup task completed.

Add session/invalidate-all helper that deletes all sessions for
a profile by profile_id, and call it from delete-profile before
the response transform. This ensures immediate access revocation
across all devices when an account is deleted.

Closes #11114

AI-assisted-by: qwen3.7-plus
2026-08-07 13:44:13 +02:00
Andrey Antukh
e2d429d283
🐛 Add timeout to plugin manifest fetch (#11120)
The fetch-manifest function previously had no timeout, causing the
plugin installation flow to hang indefinitely if the server accepted
the connection but never completed the response.

Added a 15-second timeout using rx/timeout to abort the request
automatically.

Closes #11119

AI-assisted-by: qwen3.7-plus
2026-08-07 13:37:00 +02:00
Eva Marco
bf9825fcfe
🐛 Fix close modal with esc (#11131) 2026-08-07 12:33:11 +02:00
Andrey Antukh
a131e40a6d Add proper rlimit config and propagate limit timestamp
Replace the placeholder rlimit.edn with a real per-endpoint
configuration covering auth, SSRF, search, email, media and project
operations. The previous file only had a commented-out example, so
all limits fell back to the 200k/h default window.

Also propagate the evaluated `now` timestamp into both bucket and
window result maps, so consumers (e.g. soft-mode reports) can know
exactly when the limit was checked.

AI-assisted-by: minimax-m3
2026-08-07 11:35:05 +02:00
Andrey Antukh
5571c53502
🐛 Use random UUIDs for share link IDs (#11117)
Share link IDs function as capability secrets — anyone possessing
the ID can read a file without authentication. The previous UUIDv8
scheme is predictable (56 bits fixed per process + 48-bit timestamp).

Changed to uuid/random (UUIDv4) for genuine unpredictability.

Closes #11116

AI-assisted-by: qwen3.7-plus
2026-08-07 11:27:12 +02:00
Andrey Antukh
6951876c13
🐛 Use constant-time comparison for shared key authentication (#11122)
Replace standard '=' operator with MessageDigest/isEqual to prevent
timing attacks on shared key authentication middleware.

Closes #11121

AI-assisted-by: qwen3.7-plus
2026-08-07 11:25:15 +02:00
Andrey Antukh
399b00b86d
🐛 Add permission checks to WebSocket subscription handlers (#11054)
* 🐛 Add permission checks to WebSocket subscription handlers

Check file and team read permissions before allowing WebSocket
subscriptions to prevent resource enumeration via presence
notifications.

AI-assisted-by: mimo-v2.5-pro

* 🐛 Fix random backend test failure
2026-08-07 11:24:24 +02:00
Álvaro Tejero Cantero
bc9319eac5 🐛 Port the foreign-font-id test to the uploads API
`create-font-variant-rejects-foreign-font-id` sends `:data`, which
`schema:create-font-variant` no longer accepts: the same commit that
added the test documents that param as removed in 2.18 in favour of
`:uploads`. Both of the test's requests are therefore rejected by params
validation before they reach `check-font-team-ownership!`, which is the
thing the test exists to check. It asserted nothing about ownership and
failed three assertions.

Upload the font through `upload-font-chunked!`, the helper the other
tests in this namespace already use, and pass the session id in
`:uploads`.

`backend-tests.rpc-font-test` is 16 tests, 172 assertions, 0 failures
with this applied.

AI-assisted-by: mixed models
2026-08-07 09:10:04 +02:00
Álvaro Tejero Cantero
5359ff04cf 📎 Drop an unused binding in create-font-variant
`create-font-variant` destructures `uploads` and never reads it: the
handler passes the whole `params` map to `prepare-font-data-from-uploads`.

`clj-kondo` reports it as an unused binding and exits 2, which fails the
Lint step of the Backend workflow, and the Lint step runs before the
tests, so no branch based on `develop` can run the backend suite at all.

AI-assisted-by: mixed models
2026-08-07 09:10:04 +02:00
Andrey Antukh
9875db2f82 🐛 Fix media-remote-test falling back to real config in REPL
The info-service-uri-not-configured test used config-get-mock with an
empty map, which falls back to cf/config for missing keys. In a REPL
with real config, media-processing-service-uri is set, causing the code
to attempt an HTTP call instead of raising the expected error.

Use (constantly nil) to ensure cf/get always returns nil, matching the
test intent of simulating an unconfigured service URI.

AI-assisted-by: mimo-v2.5
2026-08-06 20:53:58 +02:00
Andrey Antukh
1548748aed ♻️ Reuse organization schema in create-organization-invitation
Replace the inline organization map in schema:create-organization-invitation with cto/schema:organization-with-avatar, eliminating schema duplication and fixing mismatched validation rules for :logo and :sso-active fields.

AI-assisted-by: mimo-v2.5
2026-08-06 20:53:58 +02:00
Andrey Antukh
0702363b5c
🐛 Validate font-id team ownership in create-font-variant (#11014)
Prevent cross-team font injection by checking that when a font-id
already has variants, they belong to the same team. This closes a
BOLA gap where a user with team edit permissions could create a
font variant referencing a font-id from another team.

AI-assisted-by: mimo-v2.5-pro
2026-08-06 18:32:00 +02:00
Elena Torró
688c69b478
🐛 Fix import-binfile schema test (#11118) 2026-08-06 17:05:41 +02:00
Andrey Antukh
229d24e8f2 🐛 Fix regression on uploading binfile with incorrect schema 2026-08-06 13:30:59 +02:00
Eva Marco
fdf1684565
🐛 Fix plugin modal z index (#11109)
* 🐛 Fix z-index on plugin modals

* 🐛 Fix CI
2026-08-06 12:49:52 +02:00
Andrey Antukh
81e44afbe3
🐛 Add backend password validation with complexity rules and dictionary check (#11059)
* 🐛 Add backend password validation with complexity rules and dictionary check

Enforce minimum 8-character password length, require at least 1 lowercase
letter, 1 uppercase letter, 1 digit, and 1 special character, and reject
common passwords using Passay library with a 10k-entry wordlist from
SecLists during registration and password change flows.

AI-assisted-by: mimo-v2.5-pro

*  Improve user feedback

When the password is invalid, the user now gets extra indications to make it stronger, so it can be valid.

* 🐛 Fix remove unneeded common password check

The dictionary check is only relevant for passwords that meet all other requirements, but all 10,000 common passwords would fail the character requirements, so this check is not needed

---------

Co-authored-by: Luis de Dios <luis.dedios@kaleidos.net>
2026-08-06 10:17:14 +02:00
Andrey Antukh
495e9f059e 🐛 Add minor fix on token tests 2026-08-06 09:35:21 +02:00
Andrey Antukh
a60b648c6c 🐛 Fix issues with draft-js tests 2026-08-06 08:55:58 +02:00
Andrey Antukh
b6656ee8dd
🐛 Enable SSRF check for organization SSO provider (#11064) (#11065)
Remove :skip-ssrf-check? true from prepare-organization-sso-provider so
SSRF protection is active when validating organization SSO configs.

The endpoint is already protected by shared-key authentication
(admin-console), but enabling SSRF protection prevents potential misuse
of internal network resources if the shared key were ever compromised
(defense-in-depth).

Add test prepare-organization-sso-provider-does-not-skip-ssrf-check to
verify the SSRF check is not skipped.

AI-assisted-by: qwen3.7-plus
2026-08-05 21:54:33 +02:00
Andrey Antukh
86aaf642b6 🐛 Fix scripts/ci issue with backend lintig 2026-08-05 21:52:59 +02:00
Andrey Antukh
c4dd04353f 🐛 Sanitize SVG files on upload to prevent XSS
Add sanitize-svg function that removes dangerous elements and attributes:
- script tags
- foreignObject elements
- Event handler attributes (onload, onmouseover, etc.)
- javascript: URLs from href/xlink:href attributes

Apply sanitization in process-main-image before storing SVG files.

AI-assisted-by: mimo-v2.5-pro
2026-08-05 21:52:59 +02:00
Andrey Antukh
0ac711aa68
🐛 Normalize string inputs to prevent unfiltered echo (#11061)
Add normalize-string helper in app.common.data that trims whitespace
and returns empty string for nil input. Apply to profile, team, and
project string fields (fullname, lang, theme, name) before storage.

AI-assisted-by: qwen3.7-plus
2026-08-05 17:54:07 +02:00
Andrey Antukh
bf62e59f73
🐛 Add cooldown to prevent duplicate invitation emails (#11063) 2026-08-05 17:53:15 +02:00
Andrey Antukh
5906312dff
🐛 Normalize error response on duplicate file ID (#11050)
Capture unique constraint violation in insert-file! and return
generic :not-found error instead of propagating raw PostgreSQL
exception, preventing file existence oracle.

AI-assisted-by: mimo-v2.5-pro
2026-08-05 17:52:14 +02:00
Andrey Antukh
25066c2f46
🐛 Require file read permissions for asset endpoints (#11036)
Add authorization check to generic-handler in assets.clj so that
/assets/by-file-media-id/:id and its /thumbnail variant verify the
requesting profile has read access to the parent file. Return 404
(not 403) when access is denied to avoid confirming existence.

Also switch get-file-media-object from db/get to db/get* so that
non-existent media objects return nil instead of raising.

AI-assisted-by: mimo-v2.5-pro
2026-08-05 17:44:01 +02:00
Andrey Antukh
3d176d5390
🐛 Restrict webhook creation/edit/delete to team members only (#11029)
* 🐛 Restrict webhook edit/delete to team members only

Remove the creator-id fallback from get-webhooks-permissions.
Previously, the webhook creator could always edit/delete their
webhook even after being removed from the team. Now can-edit
comes from team role only — removed users get :not-found.

Webhooks are NOT deleted on member removal; the team owns them
and team admins/owners manage them.

AI-assisted-by: mimo-v2.5-pro

* 🐛 Restrict webhook creation to team editors

Use team role check (check-edition-permissions!) for create-webhook
instead of the custom check that allowed any team member to create
webhooks via creator-id self-match override.

AI-assisted-by: mimo-v2.5-pro
2026-08-05 17:42:49 +02:00
Andrey Antukh
0481408531
🐛 Add recursion depth limit to Fressian reader (#11020)
Bound read depth at 128 levels to prevent StackOverflowError from
crafted deeply-nested payloads. All recursive read handlers go
through read-object!, so a single depth check covers all paths.

AI-assisted-by: mimo-v2.5-pro
2026-08-05 17:41:59 +02:00
Andrey Antukh
689d3a1be2
🐛 Add max-object-size guard to read-obj! in v1 parser (#11018)
Prevent unbounded memory allocation when a crafted binfile specifies
an excessively large object size. Apply the same 100 MiB limit that
read-stream! already enforces.

AI-assisted-by: mimo-v2.5
2026-08-05 17:40:58 +02:00