21867 Commits

Author SHA1 Message Date
Andrey Antukh
fd82744c62 Merge branch 'main' into staging 2026-05-11 17:52:52 +02:00
Andrey Antukh
843a4a5b58 🐛 Fix mattermost and database logger related to the audit event change 2.15.0-RC10 2026-05-11 17:08:38 +02:00
Andrey Antukh
c76e536cd8 Merge remote-tracking branch 'origin/main' into staging 2026-05-11 16:24:27 +02:00
Andrey Antukh
102c97040a 🐛 Fix unexpected exception on handling webhook events 2.15.0-RC9 2026-05-11 16:23:14 +02:00
Andrey Antukh
1de2718d43 Merge remote-tracking branch 'origin/main' into staging 2026-05-11 15:24:15 +02:00
Andrey Antukh
8f4f948104 🐛 Skip the ssrf check on internal audit-log archive task 2.15.0-RC8 2026-05-11 15:22:59 +02:00
Andrey Antukh
6ef231bf38 Merge remote-tracking branch 'origin/main' into staging 2026-05-11 14:04:27 +02:00
Dr. Dominik Jain
1f9f4126b7 Improve MCP server logging, adding Loki support (#9425)
*  Improve MCP server logging

Log only fingerprints of user tokens

*  Add Loki transport support to MCP server logger

Loki logging is enabled iff PENPOT_LOGGERS_LOKI_URI is non-empty.

File logging is now enabled iff PENPOT_MCP_LOG_DIR is set to a non-empty value
(previously defaulted to the "logs" directory when unset).

GitHub #9415
2.15.0-RC7
2026-05-11 14:01:05 +02:00
Dr. Dominik Jain
313777d1c3
Improve MCP server logging, adding Loki support (#9425)
*  Improve MCP server logging

Log only fingerprints of user tokens

*  Add Loki transport support to MCP server logger

Loki logging is enabled iff PENPOT_LOGGERS_LOKI_URI is non-empty.

File logging is now enabled iff PENPOT_MCP_LOG_DIR is set to a non-empty value
(previously defaulted to the "logs" directory when unset).

GitHub #9415
2026-05-11 14:00:23 +02:00
Andrey Antukh
feb49bc07a 🐛 Add missing migrations for audit-log tables 2026-05-11 13:28:53 +02:00
Belén Albeza
0639ca53de
Implement WebGL context restoring (#9317)
*  Implement asset re-uploading to wasm

*  Show toast instead of error screen when webgl context is lost

* 🎉 Recover context after webgl context restored event

* 🎉 Set Read-only mode when the context has been lost

*  Disable scroll & zoom when context loss

*  Fix stale reload payload

*  Use existing debounce util to take screenshots

*  Implement design / ux specs

*  Fix playwright test by looking for toast, not error page
2026-05-11 13:15:45 +02:00
Andrey Antukh
7d4be33d4f 🎉 Add telemetry anonymous event collection (#9483)
* 🎉 Add telemetry anonymous event collection

Rewrite the audit logging subsystem to support three operating modes and
add anonymous telemetry event collection:

Modes:
- A (audit-log only): events persisted with full context
- B (audit-log + telemetry): same as A, plus events are collected for
  telemetry shipping
- C (telemetry-only): events stored anonymously with PII stripped,
  telemetry flag active, audit-log flag inactive

Audit system refactoring (app.loggers.audit):
- Replace qualified map keys (::audit/name etc.) with plain keywords
- Rename submit! -> submit, insert! -> insert, prepare-event ->
  prepare-rpc-event
- Add submit* as a lower-level public API
- Add process-event dispatch function that handles all three modes and
  webhooks in a single tx-run!
- Add :id to event schema (auto-generated if omitted)
- Add filter-telemetry-props: anonymises event props per event type.
  Keeps UUID/boolean/number values; for login/identify events preserves
  lang, auth-backend, email-domain; for navigate events preserves route,
  file-id, team-id, page-id; instance-start trigger passes through.
- Add filter-telemetry-context: retains only safe context keys.
  Backend: version, initiator, client-version, client-user-agent.
  Frontend: browser, os, locale, screen metrics, event-origin.
- Timestamps truncated to day precision via ct/truncate for telemetry
  storage
- PII stripped: props emptied, ip-addr zeroed, session-linking and
  access-token fields removed from context

Config (app.config):
- Derive :enable-telemetry flag from telemetry-enabled config option

Email utilities (app.email):
- Add email/clean and email/get-domain helper functions for domain
  extraction from email addresses

Setup (app.setup):
- Emit instance-start trigger event at system startup
- Simplify handle-instance-id (remove read-only check)

RPC layer (app.rpc):
- wrap-audit now activates when :telemetry flag is set
- Add :request-id to RPC params context for event correlation

RPC commands (management, teams_invitations, verify_token, OIDC auth,
webhooks): migrate all audit call sites to use the new plain-key API

SREPL (app.srepl.main):
- Migrate all audit/insert! calls to audit/insert with plain keys

Telemetry task (app.tasks.telemetry):
- Restructure legacy report into make-legacy-request; distinguish
  payload type as :telemetry-legacy-report
- Add collect-and-send-audit-events: loop fetching up to 10,000 rows
  per iteration, encodes and sends each page, deletes on success,
  stops immediately on failure for retry
- Add send-event-batch: POSTs fressian+zstd batch (base64 via
  blob/encode-str) to the telemetry endpoint with instance-id per event
- Add gc-telemetry-events: enforces 100,000-row safety cap by dropping
  oldest rows first
- Add delete-sent-events: deletes successfully shipped rows by id

Blob utilities (app.util.blob):
- Add encode-str/decode-str: combine fressian+zstd encoding with URL-
  safe base64 for JSON-safe string transport

Database:
- Add migration 0145: index on audit_log (source, created_at ASC) for
  efficient telemetry batch collection queries

Frontend:
- Always initialize event system regardless of :audit-log flag
- Defer auth events (signin identify) to after profile is set
- Refactor event subsystem for telemetry support

Tests (21 test vars, 94 assertions in tasks-telemetry-test):
- Cover all code paths: disabled/enabled telemetry, no-events no-op,
  happy-path batch send and delete, failure retention, payload anonymity,
  context stripping, timestamp day precision, batch encoding round-trip,
  multi-page iteration, GC cap enforcement, partial failure handling
- blob encode-str/decode-str round-trip tests (14 test vars)
- RPC audit integration tests (5 test vars)

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

* 📎 Add pr feedback changes

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2.15.0-RC6
2026-05-11 12:42:01 +02:00
Andrey Antukh
cd882f9ebd 🔧 Add minor changes to devenv config 2026-05-11 12:39:03 +02:00
Andrey Antukh
cd4a4da0f2
🎉 Add telemetry anonymous event collection (#9483)
* 🎉 Add telemetry anonymous event collection

Rewrite the audit logging subsystem to support three operating modes and
add anonymous telemetry event collection:

Modes:
- A (audit-log only): events persisted with full context
- B (audit-log + telemetry): same as A, plus events are collected for
  telemetry shipping
- C (telemetry-only): events stored anonymously with PII stripped,
  telemetry flag active, audit-log flag inactive

Audit system refactoring (app.loggers.audit):
- Replace qualified map keys (::audit/name etc.) with plain keywords
- Rename submit! -> submit, insert! -> insert, prepare-event ->
  prepare-rpc-event
- Add submit* as a lower-level public API
- Add process-event dispatch function that handles all three modes and
  webhooks in a single tx-run!
- Add :id to event schema (auto-generated if omitted)
- Add filter-telemetry-props: anonymises event props per event type.
  Keeps UUID/boolean/number values; for login/identify events preserves
  lang, auth-backend, email-domain; for navigate events preserves route,
  file-id, team-id, page-id; instance-start trigger passes through.
- Add filter-telemetry-context: retains only safe context keys.
  Backend: version, initiator, client-version, client-user-agent.
  Frontend: browser, os, locale, screen metrics, event-origin.
- Timestamps truncated to day precision via ct/truncate for telemetry
  storage
- PII stripped: props emptied, ip-addr zeroed, session-linking and
  access-token fields removed from context

Config (app.config):
- Derive :enable-telemetry flag from telemetry-enabled config option

Email utilities (app.email):
- Add email/clean and email/get-domain helper functions for domain
  extraction from email addresses

Setup (app.setup):
- Emit instance-start trigger event at system startup
- Simplify handle-instance-id (remove read-only check)

RPC layer (app.rpc):
- wrap-audit now activates when :telemetry flag is set
- Add :request-id to RPC params context for event correlation

RPC commands (management, teams_invitations, verify_token, OIDC auth,
webhooks): migrate all audit call sites to use the new plain-key API

SREPL (app.srepl.main):
- Migrate all audit/insert! calls to audit/insert with plain keys

Telemetry task (app.tasks.telemetry):
- Restructure legacy report into make-legacy-request; distinguish
  payload type as :telemetry-legacy-report
- Add collect-and-send-audit-events: loop fetching up to 10,000 rows
  per iteration, encodes and sends each page, deletes on success,
  stops immediately on failure for retry
- Add send-event-batch: POSTs fressian+zstd batch (base64 via
  blob/encode-str) to the telemetry endpoint with instance-id per event
- Add gc-telemetry-events: enforces 100,000-row safety cap by dropping
  oldest rows first
- Add delete-sent-events: deletes successfully shipped rows by id

Blob utilities (app.util.blob):
- Add encode-str/decode-str: combine fressian+zstd encoding with URL-
  safe base64 for JSON-safe string transport

Database:
- Add migration 0145: index on audit_log (source, created_at ASC) for
  efficient telemetry batch collection queries

Frontend:
- Always initialize event system regardless of :audit-log flag
- Defer auth events (signin identify) to after profile is set
- Refactor event subsystem for telemetry support

Tests (21 test vars, 94 assertions in tasks-telemetry-test):
- Cover all code paths: disabled/enabled telemetry, no-events no-op,
  happy-path batch send and delete, failure retention, payload anonymity,
  context stripping, timestamp day precision, batch encoding round-trip,
  multi-page iteration, GC cap enforcement, partial failure handling
- blob encode-str/decode-str round-trip tests (14 test vars)
- RPC audit integration tests (5 test vars)

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

* 📎 Add pr feedback changes

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-05-11 12:19:59 +02:00
Andrey Antukh
b312e6b059 📚 Update changelog 2026-05-11 11:26:54 +02:00
Andrey Antukh
15379f37f5 🐛 Fix maximum call stack size exceeded in SSE read-stream (#9484)
The recursive `read-items` function in `app.util.sse/read-stream`
caused a synchronous stack overflow when reading buffered stream
data. Each `rx/mapcat` call chained another recursive invocation
on the same call stack without yielding to the event loop.

Replace the recursive pattern with an `rx/create`-based async pump
that uses Promise `.then()` chaining, keeping the call stack depth
constant regardless of stream size.

Also add progress reporting with names and IDs during binfile
export and import, and bump `eventsource-parser` dependency.

Closes #9470

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-05-11 11:06:15 +02:00
Andrey Antukh
ec0d692856
🐛 Fix maximum call stack size exceeded in SSE read-stream (#9484)
The recursive `read-items` function in `app.util.sse/read-stream`
caused a synchronous stack overflow when reading buffered stream
data. Each `rx/mapcat` call chained another recursive invocation
on the same call stack without yielding to the event loop.

Replace the recursive pattern with an `rx/create`-based async pump
that uses Promise `.then()` chaining, keeping the call stack depth
constant regardless of stream size.

Also add progress reporting with names and IDs during binfile
export and import, and bump `eventsource-parser` dependency.

Closes #9470

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-05-11 10:59:35 +02:00
Andrey Antukh
f2c631b8b7 Merge remote-tracking branch 'origin/main-staging' into staging 2026-05-11 09:30:10 +02:00
Andrey Antukh
1a212a2769 Merge remote-tracking branch 'origin/main-staging' 2026-05-11 08:46:25 +02:00
Alonso Torres
9f05ba2fdf
Add plugins and mcp event data (#9228)
*  Add plugins and mcp event data

* ♻️ Changed data-event ::ev/event to ev/event
2026-05-11 08:36:53 +02:00
Andrey Antukh
6eba2e6c42 📚 Update changelog 2026-05-10 20:23:10 +02:00
andrés gonzález
9c771ae6b9
🐛 Fix MCP integrations copy button to match displayed URL (#9239) 2026-05-10 19:23:03 +02:00
Andrey Antukh
79937027eb Merge remote-tracking branch 'origin/main' into staging 2026-05-10 10:50:29 +02:00
Andrey Antukh
9b336e9a3d Add nrepl-eval script and skill 2026-05-10 10:49:53 +02:00
Andrey Antukh
55406be084 Merge remote-tracking branch 'origin/main' into staging 2026-05-10 09:20:08 +02:00
Andrey Antukh
f414392f13 📎 Update changelog 2026-05-10 09:19:56 +02:00
Andrey Antukh
cf3455a487 📎 Add missing entry on CHANGES.md 2026-05-10 09:18:52 +02:00
Andrey Antukh
10a23a6869 Merge remote-tracking branch 'origin/main' into staging 2026-05-10 09:16:41 +02:00
Francis Santiago
e9588f3939
🐳 Reuse shared Nginx security headers (#9473)
Signed-off-by: Francis Santiago <francis.santiago@kaleidos.net>
2.15.0-RC5
2026-05-08 14:11:09 +02:00
Aitor Moreno
4e98dfb99f
♻️ Refactor GpuState and RenderState
* ♻️ Refactor GpuState

* ♻️ Refactor RenderState

* 🔧 Tweak some _build_env options
2026-05-08 11:10:14 +02:00
Eva Marco
cccd7bc6de
🐛 Fix pixel grid color row (#9360) 2026-05-08 11:06:56 +02:00
Belén Albeza
a52c4e099a 🐛 Fix round/square linecaps not being applied correctly in open paths 2026-05-08 10:31:18 +02:00
Andrey Antukh
a50785f105 📎 Update changelog 2026-05-08 09:29:28 +02:00
Andrey Antukh
279231240d
🐛 Harden outbound HTTP requests against SSRF and restrict assets handlers (#9390)
* ⬆️ Update root deps

* 🐛 Harden outbound HTTP requests against SSRF and restrict unauthenticated asset access

- Add app.util.ssrf URL/host validator that resolves hostnames and blocks
  loopback, link-local, site-local, cloud metadata, and operator-supplied CIDRs
- Add app.media.sanitize image EOF truncator that strips trailing data after
  PNG IEND, JPEG EOI, GIF trailer, and WebP RIFF markers
- Disable HTTP client auto-redirect; add req-with-redirects! helper that
  revalidates every redirect hop against the SSRF blocklist
- Wire SSRF validation and EOF sanitization into media/download-image
- Validate webhook URLs and OIDC profile picture URLs against SSRF
- Restrict /assets/by-id to require authentication for non-public buckets
  (profile) while keeping public access for file-media-object,
  file-object-thumbnail, team-font-variant, and file-data-fragment
- Add config knobs: ssrf-protection-enabled, ssrf-allowed-hosts,
  ssrf-extra-blocked-cidrs

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

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-05-08 09:18:22 +02:00
Andrey Antukh
3496435e69 📚 Update changelog 2026-05-08 00:32:21 +02:00
Andrey Antukh
d103feebfa 📚 Update changelog 2026-05-07 23:57:49 +02:00
Dr. Dominik Jain
362440fead 🚑 Use base64 envelope for Uint8Array task results to avoid JSON expansion (#9431)
Resolves #9420 (critical memory usage issue in PROD deployment)

When the plugin's ExecuteCodeTaskHandler returns a Uint8Array (e.g. from penpotUtils.exportImage),
JSON.stringify previously serialized it as an object with numeric string keys,
causing ~10x payload expansion and large peak heap usage on the server side.

The plugin now wraps a top-level Uint8Array result in a tagged envelope
{ __type: "base64", data: <base64> }, and ImageContent.byteData decodes this envelope
on the server. The legacy numeric-keyed-object path is retained as a fallback for
compatibility with older plugin builds.
2026-05-07 23:51:50 +02:00
Dr. Dominik Jain
c3743930c2
🚑 Use base64 envelope for Uint8Array task results to avoid JSON expansion (#9431)
Resolves #9420 (critical memory usage issue in PROD deployment)

When the plugin's ExecuteCodeTaskHandler returns a Uint8Array (e.g. from penpotUtils.exportImage),
JSON.stringify previously serialized it as an object with numeric string keys,
causing ~10x payload expansion and large peak heap usage on the server side.

The plugin now wraps a top-level Uint8Array result in a tagged envelope
{ __type: "base64", data: <base64> }, and ImageContent.byteData decodes this envelope
on the server. The legacy numeric-keyed-object path is retained as a fallback for
compatibility with older plugin builds.
2026-05-07 23:50:20 +02:00
Dr. Dominik Jain
6a44b19311
🐛 Fix keep-alive interval leak in PluginBridge (#9435)
The ping interval was stored in a single variable shared across all
WebSocket connections, so each new connection overwrote the previous
handle and leaked the prior interval.

Move the interval onto ClientConnection as a per-connection field,
and centralize teardown in a new removeConnection(ws) method used
by the close, error and duplicate token rejection paths.

Resolves #9430
2026-05-07 20:37:22 +02:00
Aitor Moreno
0817f13340
♻️ Change how rendering spiral is generated 2026-05-07 17:25:50 +02:00
Alejandro Alonso
fc7748fc84
🐛 Fix(render-wasm): stabilize interactive drag backbuffer crops 2026-05-07 17:12:00 +02:00
Aitor Moreno
bc0f081371
♻️ Refactor text editor state (#9379) 2026-05-07 16:16:44 +02:00
Eva Marco
c5f2ffab69
🐛 Fix internal error when applying not valid value to margin input (#9311) 2026-05-07 15:24:25 +02:00
Aitor Moreno
9fccee8689 ♻️ Refactor how viewport interest area works 2026-05-07 13:46:51 +02:00
Andrey Antukh
798ee46b4a 🐛 Bind MCP ReplServer to localhost to prevent unauthenticated RCE
The ReplServer Express app was calling `app.listen(port)` with no host
argument, causing Node/Express to default to binding on all interfaces
(0.0.0.0). Combined with the unauthenticated /execute endpoint, any
network peer could POST arbitrary JS and get it run inside the MCP
process.

Fix: add a `host` parameter (default "localhost") to the ReplServer
constructor and pass it to `app.listen`. The call site in
PenpotMcpServer now forwards `this.host` (sourced from
PENPOT_MCP_SERVER_HOST env var, default "localhost"), so environment-
variable overrides continue to work.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
2026-05-07 12:59:31 +02:00
Alejandro Alonso
03487f90e5 🐛 Fix double-clicking a text element selected via Ctrl+click in nested layouts jumps to parent instead of entering edit mode 2026-05-07 09:57:15 +02:00
Andrey Antukh
697a825d76 📚 Update opencode planner agent 2026-05-07 01:04:38 +02:00
Alejandro Alonso
db1e2a9cfc
Merge pull request #9391 from penpot/superalex-fix-drag-crop-cache-perf
🐛 Avoid opaque fill check in drag crop cache hot path
2026-05-06 19:27:58 +02:00
Alejandro Alonso
33396df2e2 🐛 Avoid opaque fill check in drag crop cache hot path 2026-05-06 19:15:00 +02:00
Dexterity
db77780227 🐛 Fix MCP "active in another tab" notification not clearing (#9321) 2026-05-06 17:42:34 +02:00