* 🐛 Fix deep-harden of host plugin context on load ses.harden(context) in loadPlugin deep-freezes every host-owned object and function reachable through the context. The host keeps needing to modify those across page navigation (listener wrappers, proxies), so a later property augmentation (e.g. assigning toString) throws 'TypeError: Cannot assign to read only property toString' and kills the MCP session (penpot/penpot#11001). Pass the host context through untouched; sandbox isolation stays at the compartment boundary (hardened sandbox-owned globals + ses.safeReturn). Regression test: load-plugin-context.spec.ts (no ses mock). AI-assisted-by: muse-spark-1.3 Signed-off-by: Junsoo Choi <junsoo1172@gmail.com> * 🐛 Add real SES bootstrap to host-context regression test The previous load-plugin-context.spec.ts had no SES bootstrap, so it failed on the original code with 'ReferenceError: harden is not defined' instead of the intended freeze assertion, and passed on the fixed code merely by avoiding ses.harden. Now the spec bootstraps real SES (repairIntrinsics + hardenIntrinsics), adds a control test proving real ses.harden deep-freezes host-owned functions (Object.isFrozen === true, later toString assignment throws TypeError - the #11001 crash signature), and keeps the regression test asserting loadPlugin leaves host functions unfrozen and patchable. AI-assisted-by: muse-spark-1.3 Signed-off-by: Junsoo Choi <junsoo1172@gmail.com> * 🐛 Add production-order hardening contrast evidence Proves the initialization-ordering hazard behind #11001 (cf. #8636): in production, index.ts runs repairIntrinsics only at module load while hardenIntrinsics runs later in createSandbox. The original loadPlugin called ses.harden(context) between those steps, freezing the shared Function.prototype with plain data properties so later override taming is skipped and any subsequent fn.toString assignment throws TypeError. Kept in a separate spec file so the full SES bootstrap in load-plugin-context.spec.ts cannot mask the ordering effect. AI-assisted-by: muse-spark-1.3 Signed-off-by: Junsoo Choi <junsoo1172@gmail.com> * 🐛 Apply approved lint fix and CHANGELOG entry Restores the two approved deliverables missing from the previous push: the prefer-rest-params fix in load-plugin-harden-order.spec.ts (replacing the deprecated arguments usage) and the plugins-runtime CHANGELOG entry for the host-context harden fix (#11001). AI-assisted-by: muse-spark-1.3 Signed-off-by: Junsoo Choi <junsoo1172@gmail.com> * 🐛 Remove deep-hardening of host plugin context on load Signed-off-by: makesomethingshit <junsoo1172@gmail.com> Co-authored-by: multica-agent <github@multica.ai> Signed-off-by: Junsoo Choi <junsoo1172@gmail.com> * 🐛 Align CHANGELOG and context comment with reviewed evidence AI-assisted-by: multica-agent Signed-off-by: Junsoo Choi <junsoo1172@gmail.com> Co-authored-by: multica-agent <github@multica.ai> * 🔥 Remove SES semantic tests from plugin regression coverage Drop the tests that only verify SES library semantics rather than Penpot application behavior: - Delete load-plugin-harden-order.spec.ts (pure SES initialization-order evidence, never calls loadPlugin). - Remove the ses.harden control test and its SES bootstrap setup from load-plugin-context.spec.ts. - Remove the #8636 hardening-order contrast test and the now-unused ses import from load-plugin-real-path.spec.ts. Keep the application-level regression coverage: the real loadPlugin initialization path, permission enforcement, host-context isolation and safeReturn protection. No production code changes. Signed-off-by: Junsoo Choi <junsoo1172@gmail.com> AI-assisted-by: Omen Alpha --------- Signed-off-by: Junsoo Choi <junsoo1172@gmail.com> Signed-off-by: makesomethingshit <junsoo1172@gmail.com> Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: Andrey Antukh <niwi@niwi.nz>
Penpot Plugins
What can you find here?
We've been working in an MVP to allow users to develop their own plugins and use the existing ones.
There are 2 important folders to keep an eye on: apps and libs.
In the libs folder you'll find:
- plugins-runtime: here you'll find the code that initializes the plugin and sets a few listeners to know when the penpot page/file/selection changes. It has its own README.
- plugins-styles: basic css library with penpot styles in case you need help for styling your plugins.
In the apps folder you'll find some examples that use the libraries mentioned
above.
-
contrast-plugin: to run this example check Create a plugin from scratch
-
example-styles: to run this example you should run
pnpm run start:styles-example
Open in your browser: http://localhost:4202/
Run Penpot sample plugins
This guide will help you launch a Penpot plugin from the penpot-plugins repository. Before proceeding, ensure that you have Penpot running locally by following the setup instructions.
In the terminal, navigate to the penpot-plugins repository and run pnpm -r install to install the required dependencies. Then, run pnpm run start to
launch the plugins runtime.
After installing the dependencies, choose a plugin to launch. You can either run one of the provided examples or create your own (see "Creating a plugin from scratch" below). To launch a plugin, Open a new terminal tab and run the appropriate startup script for the chosen plugin.
For instance, to launch the Contrast plugin, use the following command:
// for the contrast plugin
pnpm run start:plugin:contrast
Finally, open in your browser the specific port. In this specific example would
be http://localhost:4302
A table listing the available plugins and their corresponding startup commands is provided below.
Sample plugins
| Plugin | Description | PORT | Start command | Manifest URL |
|---|---|---|---|---|
| poc-state-plugin | Sandbox plugin to test new plugins api functionality | 4202 | pnpm run start:plugin:poc-state | http://localhost:4202/assets/manifest.json |
| contrast-plugin | Sample plugin that gives you color contrast information | 4202 | pnpm run start:plugin:contrast | http://localhost:4202/assets/manifest.json |
| icons-plugin | Tool to add icons from Feather | 4202 | pnpm run start:plugin:icons | http://localhost:4202/assets/manifest.json |
| lorem-ipsum-plugin | Generate Lorem ipsum text | 4202 | pnpm run start:plugin:loremipsum | http://localhost:4202/assets/manifest.json |
| create-palette-plugin | Creates a board with all the palette colors | 4202 | pnpm run start:plugin:palette | http://localhost:4202/assets/manifest.json |
| table-plugin | Create or import table | 4202 | pnpm run start:table-plugin | http://localhost:4202/assets/manifest.json |
| rename-layers-plugin | Rename layers in bulk | 4202 | pnpm run start:plugin:renamelayers | http://localhost:4202/assets/manifest.json |
| colors-to-tokens-plugin | Generate tokens JSON file | 4202 | pnpm run start:plugin:colors-to-tokens | http://localhost:4202/assets/manifest.json |
| poc-tokens-plugin | Sandbox plugin to test tokens functionality | 4202 | pnpm run start:plugin:poc-tokens | http://localhost:4202/assets/manifest.json |
Web Apps
| App | Description | PORT | Start command | URL |
|---|---|---|---|---|
| plugins-runtime | Runtime for the plugins subsystem | 4200 | pnpm run start:app:runtime | |
| example-styles | Showcase of some of the Penpot styles that can be used in plugins | 4201 | pnpm run start:app:styles-example | http://localhost:4201/ |
Creating a plugin from scratch
If you want to create a new plugin, read the following README
License
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 SUBSIDIARY SL
Penpot is a Kaleidos’ open source project