88 Commits

Author SHA1 Message Date
David Barragán Merino
a2243f0c56 🐳 Cover the inline scripts of the served pages with CSP hashes
The frontend build now emits the sha256 hashes of the inline scripts of every page it writes into resources/public, the image moves them out of the document root, and the entrypoint splices them into the default script-src. This removes one of the two reasons why enforcing mode was not usable.

The hashes are computed on the rendered output rather than on the mustache templates, since the digest covers the exact bytes served between the script tags. All four served pages contribute, not just index.html: challenge.html handles the redirect and render.html and rasterizer.html are loaded by the exporter, so leaving them out would have broken export under enforcing mode. The storybook previews are excluded because that container does not serve them.

A bundle predating this change yields no hashes and the policy stays as it was, so older bundles keep building.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
2026-09-02 09:48:32 +02:00
David Barragán Merino
a8ccb5e0cd 🐳 Add configurable CSP and HSTS headers to the frontend image
Ship both headers from the image so that every deployment starts from a sensible default instead of each installation deriving its own policy. Report-only mode never blocks a request, so this changes no behaviour for existing deployments, and HSTS stays absent unless PENPOT_PUBLIC_URI declares an https scheme.

The policy can be narrow because the frontend already reverse proxies its own external dependencies, so 'self' covers them. What it must permit beyond that comes from the code: 'wasm-unsafe-eval' for the render engine, 'unsafe-inline' styles for the inline style attributes of the UI, and blob:/data: for thumbnails, exports and fonts.

Closes #11374
Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
AI-assisted-by: Claude
2026-09-01 16:13:04 +02:00
Filip Sajdak
9311737f66
🐛 Do not cache the environment generated config.js (#11146)
On self hosted installs /js/config.js is regenerated from PENPOT_FLAGS
on every container start, but nginx served it with the same
`public, max-age=604800` used for build assets, and index.html versions
it only by the build. A flags only change therefore leaves the URL
untouched, so a browser that had already loaded the app kept using its
cached copy for up to a week: enabling a flag such as
enable-login-with-google had no visible effect for returning users
until the cache expired or they cleared their site data.

Serve that one file with the same no-store headers already used for
index.html, which is the other file whose contents change without its
URL changing. Every other static asset keeps the long lived cache.

Fixes #10556.

Signed-off-by: Filip Sajdak <filip.sajdak@siili.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-08-17 22:35:10 +02:00
Yamila Moreno
9528400c6e
🐛 Forward internal Host in nginx proxy_pass to backend/exporter (#11233)
The global `proxy_set_header Host $http_host;` forwarded the client-facing
Host to internal proxy_pass calls (backend/exporter), breaking mTLS routing
in service-mesh setups (e.g. Istio STRICT mode), which match outbound
requests to a cluster based on Host/:authority.

Explicitly set `Host $proxy_host` on /api, /assets, /api/export, /readyz
and /ws/notifications so these calls always target the correct internal
service host, independent of the client's original Host header.

Fixes #10835

Signed-off-by: Sebastien MALOT <sebastien.malot@pm.gouv.fr>
Co-authored-by: Sebastien MALOT <sebastien.malot@pm.gouv.fr>
2026-08-13 12:03:27 +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
David Barragán Merino
c16b7919f9 🐳 Remove the configuration of the admin-console from Nginx if it is not enabled 2026-08-04 20:33:31 +02:00
Andrey Antukh
49119e0339
♻️ Rename nitrate config to admin-console (#10929)
* ♻️ Rename nitrate config to admin-console

Rename user-facing configuration from 'nitrate' to 'admin-console':
- Feature flags: :nitrate -> :admin-console, :nitrate-bulk-create-profiles -> :admin-console-bulk-create-profiles
- Config keys: :nitrate-shared-key -> :admin-console-shared-key, :nitrate-backend-uri -> :admin-console-uri
- Shared-keys map entry: :nitrate -> :admin-console (setup.clj + main.clj)
- Env vars: PENPOT_NITRATE_SHARED_KEY -> PENPOT_ADMIN_CONSOLE_SHARED_KEY, PENPOT_NITRATE_BACKEND_URI removed (consolidated into PENPOT_ADMIN_CONSOLE_URI)
- Docker/nginx: PENPOT_NITRATE_URI -> PENPOT_ADMIN_CONSOLE_URI

Code namespaces, file paths, CSS classes, and i18n keys stay as-is.

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

* ♻️ Rename initialize-user-in-nitrate-organization to initialize-user-in-organization

Part of the nitrate -> admin-console rename series. The function and all 9 references across 6 files have been renamed.

* ♻️ Rename :nitrate-bulk-create-profiles-not-allowed to :bulk-create-profiles-not-allowed

* ♻️ Inline nitrate-permissions into app.common.types.organization

- Delete app.common.types.nitrate-permissions and its test
- Move permission rules (allowed?, can-send-invitations?, etc.) into organization.cljc
- Harmonize all consumers to use alias cto for app.common.types.organization
- Update test runner and create organization_test.cljc
2026-08-03 17:23:55 +02:00
Andrey Antukh
80c84a3331
🐛 Strip Authorization header when proxying asset redirects to S3 (#10777)
When nginx follows a backend 307 redirect to a presigned S3 URL, it was
forwarding the client's Authorization header to S3. Production S3 rejects
this because it sees two auth mechanisms (presigned URL signature +
Authorization header). MinIO in devenv is more lenient and ignores the
extra header.

Fix: add proxy_set_header Authorization "" in the @handle_redirect block.

Fixes #10776

AI-assisted-by: mimo-v2.5-pro
2026-07-22 14:00:35 +02:00
Andrey Antukh
b4532486e3
Add configurable resource usage limits for imagemagick (#10240)
* 🐳 Add ImageMagick policy.xml resource limits to backend Docker image

Add a restrictive policy.xml to the backend Docker image that caps
ImageMagick resource usage: 256MiB memory, 512MiB map, 128MP area,
30s time limit, 16KP max dimensions. Blocks PS/EPS/PDF/XPS coders
to prevent Ghostscript attack surface.

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

*  Add timeout support to shell/exec!

Add optional :timeout parameter (in seconds) that uses
Process.waitFor(long, TimeUnit). On timeout, the process is
destroyed forcibly and an :internal/:process-timeout exception
is raised. Stdout/stderr readers handle IOException from closed
streams when the process is killed.

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* ♻️ Rename ::wrk/netty-executor to ::wrk/executor with cached pool

Replace DefaultEventExecutorGroup (fixed Netty thread pool) with a
cached thread pool (px/cached-executor) for general async task
offloading. The cached pool creates threads on demand and reuses
idle ones, which is more appropriate for blocking I/O workloads
(shell commands, message bus, rate limiting, etc.).

Changes:
- Rename ::wrk/netty-executor to ::wrk/executor in worker/executor.clj
- Switch implementation from DefaultEventExecutorGroup to px/cached-executor
- Update all ig/ref wiring in main.clj (msgbus, tmp cleaner, climit, rlimit, rpc)
- Remove ::wrk/netty-executor from redis.clj (let lettuce create its own
  eventExecutorGroup instead of sharing a Netty executor)
- Assert executor is present in shell/exec! to prevent silent nil usage
- Remove executor-threads config (no longer needed for cached pool)

The ::wrk/netty-io-executor (NioEventLoopGroup) remains unchanged as it
handles actual non-blocking network I/O for Redis and S3.

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 🔥 Remove im4java dependency and replace with direct ImageMagick CLI calls

- Replace im4java Java library with direct 'magick' CLI calls via shell/exec!
- Add PENPOT_IMAGEMAGICK_* config env vars for resource limits (thread, memory, map, area, disk, time, width, height)
- Use configurable ImageMagick environment with sensible defaults matching policy.xml
- Remove -Dim4java.useV7=true JVM flag from startup scripts
- Remove org.im4java/im4java from deps.edn
- All ImageMagick commands now use shell/exec! with 60s timeout and resource limits

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 💄 Rename imagemagick env functions and optimize config reads

- Rename imagemagick-defaults -> imagemagick-default-env
- Rename imagemagick-env -> get-imagemagick-env
- Optimize to avoid double cf/get calls per config key

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

*  Add tests for shell/exec! timeout and media processing

- Add shell_test.clj: tests for exec! timeout, env vars, stdin, stderr
- Add media_test.clj: tests for info, generic-thumbnail, profile-thumbnail
- Fix generic-process to prefer explicit format over input mtype
- Fix shell/exec! to use cached executor when system has no executor
- Fix reduce-kv accumulator in set-env (must return penv)

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* ♻️ Refactor media/process to take system as first argument

- Change (defmulti process :cmd) -> (defmulti process (fn [_system params] (:cmd params)))
- Change (run params) -> (run system params)
- All process methods now receive [system params]
- Update all callers: rpc/commands/media, profile, auth, fonts
- Revert shell/exec! to require system with executor (no fallback)
- Fix lint warnings and formatting

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 🔥 Remove unused app.svgo namespace

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 🔥 Remove Node.js from backend Docker image

- Delete unused svgo-cli.js script
- Remove Node.js installation from Dockerfile.backend
- Remove svgo-cli.js copy from backend build script

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 🔥 Remove unused process-error multimethod

- Remove process-error multimethod and its default handler
- Simplify media/run to directly call process
- Fix alignment in main.clj

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

* 📚 Add ImageMagick resource limits configuration to technical guide

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>

---------

Co-authored-by: mimo-v2.5-pro <mimo-v2.5-pro@penpot.app>
2026-06-18 17:52:01 +02:00
Andrey Antukh
9911ff7959 Merge remote-tracking branch 'origin/staging' into develop 2026-06-04 19:01:54 +02:00
David Barragán Merino
97c3a9facf
🐳 Add improvements related to Docker and Podman compatibility (#10012)
* 📎 Add tests for boolean parser coverage

* 🐳 Normalize boolean handling in nginx entrypoint

* 🐳 Quote boolean env vars in compose example (add Podman compatibility)

* 🔥 Remove deprecated and duplicated nginx.conf file for Storybook
2026-06-04 10:11:58 +02:00
Andrey Antukh
feca7cef41 Merge remote-tracking branch 'origin/staging' into develop 2026-06-02 17:50:45 +02:00
Andrey Antukh
3744186510 🔧 Update default nginx limit configuration 2026-06-02 14:05:21 +02:00
Andrey Antukh
4a8fb5af53 Merge remote-tracking branch 'origin/staging' into develop 2026-06-01 13:15:57 +02:00
Andrey Antukh
c5de4c27b0 Merge remote-tracking branch 'origin/main' into staging 2026-06-01 12:57:39 +02:00
Andrey Antukh
3858993a57 Merge remote-tracking branch 'origin/staging' into develop 2026-05-27 13:37:02 +02:00
David Barragán Merino
a0f869b917 🐳 Remove the configuration of the mcp from Nginx if it is not enabled 2026-05-27 12:36:26 +02:00
Andrey Antukh
f6c76711f4 Merge remote-tracking branch 'origin/main' into staging 2026-05-27 11:34:59 +02:00
Francis Santiago
2ad85db016 🐳 Fix frontend startup on hosts without IPv6 support
Signed-off-by: Francis Santiago <francis.santiago@kaleidos.net>
2026-05-20 20:53:30 +02:00
Marina López
d26412740a
♻️ Rename control center to admin console (#9705) 2026-05-18 14:33:24 +02:00
Andrey Antukh
d78074307f Merge remote-tracking branch 'origin/main' into staging 2026-05-14 11:07:42 +02:00
Yamila Moreno
4289cad9ab
🐳 Improve nginx configuration for MCP server (#9565) 2026-05-12 23:28:20 +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>
2026-05-08 14:11:09 +02:00
Alejandro Alonso
984d292ab2 Merge remote-tracking branch 'origin/staging' into develop 2026-04-24 09:29:24 +02:00
Andrey Antukh
75d99a0725 🔧 Add missing public uri handling on nginx entrypoint 2026-04-22 13:11:10 +02:00
Yamila Moreno
6de5370a0b 🐛 Fix nginx configuration for mcp 2026-04-22 10:29:52 +02:00
Yamila Moreno
6ea7a64e01 Add nginx configuration for mcp server 2026-04-22 09:33:58 +02:00
wdeveloper16
d772632b08
Allow customising the OIDC login button label (#9026)
*  Allow customising the OIDC login button label (#7027)

* 📚 Add CHANGES entry and docs for PENPOT_OIDC_NAME (#7027)

---------

Co-authored-by: wdeveloper16 <wdeveloer16@protonmail.com>
2026-04-17 16:56:29 +02:00
Yamila Moreno
dd856ecf50 ♻️ Deprecate PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE envvar 2026-02-23 13:48:01 +01:00
Francis Santiago
15fa6206e2
Merge pull request #8286 from penpot/fc-fix-ipv6-fronted
🎉 Enable IPv6 support for docker images
2026-02-10 16:01:06 +01:00
Francis Santiago
3281819283 🎉 Enable IPv6 support for docker images 2026-02-10 15:52:33 +01:00
Andrey Antukh
5ec345162a Add mcp plugin into the frontend bundle 2026-02-09 17:38:40 +01:00
Yamila Moreno
307dae9f61 💄 Remove access logs for /readyz 2026-02-04 10:54:07 +01:00
Pablo Alba
d5abc52dac
🎉 Add first integration with nitrate (#7803)
* 🐛 Display missing selected tokens set info (#8098)

* 🐛 Display missing selected tokens set info

*  Add integration tests to verify current active set

* 🎉 Integration with nitrate platform

* 🐛 Fix nitrate get-teams returns deleted teams

*  Add nitrate to tmux devenv

*  Add retry and validation to nitrate module

*  Add photoUrl to profile on nitrate authenticate

*  Move nitrate url to an env variable

* ♻️ Change Nitrate organization-id schema to text

* ♻️ Cleanup unused imports

* 🔧 Add control-center to nginx

*  Add create org link

* 🔧 Fix nginx entrypoint

* 🐛 Fix control-center proxy pass

* 🎉 Add nitrate licence check

* Revert " Add nitrate to tmux devenv"

This reverts commit dc6f6c458995dac55cab7be365ced0972760a058.

*  Add feature flag check

* 🐛 Rename licences for licenses

*  MR changes

*  MR changes 2

* 📎 Add the ability to have local config on start backend

* 📎 Add FIXME comment

---------

Co-authored-by: Xaviju <xavier.julian@kaleidos.net>
Co-authored-by: Juanfran <juanfran.ag@gmail.com>
Co-authored-by: Yamila Moreno <yamila.moreno@kaleidos.net>
Co-authored-by: Marina López <marina.lopez.yap@gmail.com>
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-01-27 10:04:53 +01:00
Andrey Antukh
0ecb2bc838 Merge remote-tracking branch 'origin/staging' into develop 2026-01-19 13:42:05 +01:00
Andrey Antukh
3ecf509f3b 🔧 Add missing gif files handling on defaut nginx config 2026-01-19 13:32:01 +01:00
Yamila Moreno
786736fadd
Improve default nginx config (#8104) 2026-01-18 10:07:44 +01:00
Yamila Moreno
1798461d21
🐳 Add override for assets (#7926) 2025-12-09 14:55:21 +01:00
Andrey Antukh
d04fdb5fbd
Make the dist bundle use consistent and cache-aware uris (#7911) 2025-12-09 08:05:28 +01:00
Andrey Antukh
496d37795b
Adapt docker images nginx config template to latest changes (#7891) 2025-12-03 13:45:18 +01:00
David Barragán Merino
034463e63a 🐛 Fix some paths and add missed nginx config file for the storybook docker image 2025-10-29 12:02:32 +01:00
David Barragán Merino
b10dcb2d63 🐳 Prevent error if config.js is a bind mounted file 2025-10-13 12:42:23 +02:00
Yamila Moreno
042bd03beb 🐳 Improve Docker nginx 2025-09-26 10:31:23 +02:00
Yamila Moreno
6fc63f14a0
Add configuration for air gapped installations (#6567) 2025-05-29 10:34:47 +02:00
Alejandro Alonso
2aaa2f3033
🐛 Fix template import (#6299) 2025-04-15 10:39:22 +02:00
Yamila Moreno
0ff9c44246
🐳 Improve nginx resolvers (#5967) 2025-02-28 09:02:40 +01:00
Andrey Antukh
f252ffb201 Add the ability to overwrite default http body size on docker images
And provide a compose file with good defaults
2025-01-29 12:22:05 +01:00
Andrey Antukh
d9ba107da2 🔧 Update default body size for docker images
Set it to 350MiB, the same as we have on our saas
2025-01-29 11:38:16 +01:00
BackRunner
068acb4303 🐛 Fix assets proxy ssl handshake error 2024-12-23 10:28:10 +01:00