* 🐛 Fix plugin postMessage channel allowing cross-plugin message injection
The global postMessage listener was broadcasting incoming messages to all
loaded plugins without validating the origin or routing to the correct
sender. This allowed any plugin (or any iframe from any origin) to inject
messages into other plugins.
- Added origin validation — messages from origins other than
window.location.origin are rejected.
- Added sender-based routing — a message is only delivered to the plugin
whose iframe contentWindow matches event.source.
- Exposed iframeWindow getters in PluginManager, PluginModalElement, and
createPlugin so the runtime can compare event.source against the correct
iframe reference.
- Updated documentation examples to include origin validation and
recommend window.location.origin over '*' for postMessage targetOrigin.
AI-assisted-by: qwen3.7-plus
* 🐛 Fix plugin origin check breaking cross-origin plugin messaging
The origin check added in the previous commit compared event.origin
against window.location.origin (Penpot own origin). Since plugins
are cross-origin by design (hosted on the plugin author domain),
this check rejected every legitimate message from every real plugin.
The event.source-based sender routing (matching iframeWindow identity)
is the correct and sufficient security mechanism - it cannot be forged
cross-origin, so the redundant origin check was removed.
- Removed event.origin check from load-plugin.ts message listener
- Updated tests to use realistic plugin origins (localhost:4202/4203)
and to verify rejection based on source identity, not origin
- Fixed documentation examples: use event.source for receiving
validation and '*' for postMessage targetOrigin
AI-assisted-by: mimo-v2.5-pro
* ⬆️ Update pnpm and its deps
* ⬆️ Update JVM dependencies in backend and common
Update several JVM dependencies across backend and common:
- passay 1.6.6 -> 2.0.0 (package reorg, ctor-based rules)
- siphash 2.0.0 -> 3.0.0 (SipHasher* renamed to SipHash*)
- lettuce-core, guava, sqlite-jdbc, jsoup, lz4-java, markdown-clj,
awssdk s3/sts, selmer, jackson-core/databind, shadow-cljs
Adapt passay validation to the new API (moved packages, constructor
configuration) and siphash to the renamed classes. Add tests for
password validation and UUID advisory-lock hashing.
AI-assisted-by: deepseek-v4-flash
* ⬆️ Update node on docker images
* 📎 Minor fixes related to pnpm12 compatibility
* 🐛 Fix plugin modal drag and close interactions
Switch plugin modal dragging to pointer-capture semantics from the header so drag state remains stable when crossing iframe boundaries. Prevent drag start from close-button pointerdown and add regression tests for both non-draggable close-button interaction and close-event dispatch.
Signed-off-by: Marek Hrabe <marekhrabe@me.com>
* 📚 Update changelog for plugin modal drag fix
Document plugin modal drag and close-button interaction fixes in the unreleased changelog.
Signed-off-by: Marek Hrabe <marekhrabe@me.com>
* 🐛 Simplify plugin modal drag CSS selection rules
Keep user-select disabled at the modal wrapper level and keep touch-action scoped to the header drag handle to remove redundant declarations while preserving drag behavior.
Signed-off-by: Marek Hrabe <marekhrabe@me.com>
---------
Signed-off-by: Marek Hrabe <marekhrabe@me.com>
* 🐛 Handle plugin errors gracefully without crashing the UI
Plugin errors (like 'Set is not a constructor') were propagating to the
global error handler and showing the exception page. This fix:
- Uses a WeakMap to track plugin errors (works in SES hardened environment)
- Wraps setTimeout/setInterval handlers to mark errors and re-throw them
- Frontend global handler checks isPluginError and logs to console
Plugin errors are now logged to console with 'Plugin Error' prefix but
don't crash the main application or show the exception page.
Signed-off-by: AI Agent <agent@penpot.app>
* ✨ Improved handling of plugin errors on initialization
* ✨ Fix test and linter
---------
Signed-off-by: AI Agent <agent@penpot.app>
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
- Change the default for the newWindow param from true to false, so
openPage() navigates in the same tab instead of opening a new one
- Accept a UUID string as the page argument in addition to a Page object,
avoiding the need to call penpot.getPage(uuid) first
- Add validation error when an invalid page argument is passed
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
- Change the default for the newWindow param from true to false, so
openPage() navigates in the same tab instead of opening a new one
- Accept a UUID string as the page argument in addition to a Page object,
avoiding the need to call penpot.getPage(uuid) first
- Add validation error when an invalid page argument is passed
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Fixes a crash where plugins sending messages via 'penpot.ui.sendMessage()'
could fail if their message payload contained non-serializable values like
functions or closures.
The fix adds validation using 'structuredClone()' to catch these messages
early with a helpful error message, and adds a defensive try/catch in the
modal's message handler as a safety net.
Fixes the error: 'Failed to execute postMessage on Window: ... could not
be cloned.'
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
* ✨ Add core changes for mcp server
* ✨ Changes to plugins-runtime to add mcp extensions
* ✨ Changes to MCP plugin
* ✨ Changes post-review and ci fixes