mirror of
https://github.com/penpot/penpot.git
synced 2026-09-25 13:26:16 +00:00
* 🐛 Use gradient type instead of export type in SVG renderer data->gradient-def was comparing the render `type` parameter (:svg, :png, :pdf) against "linear" to decide between linearGradient and radialGradient elements. Since the export type is never "linear", the comparison always fell through to radialGradient, causing all linear gradients to be exported as radial in SVG output. Read the gradient type from the data map instead: (get-in data ["gradient" "type"]) Closes #5972 * 🐛 Add SVG gradient export regression test Extract SVG gradient definition generation from the renderer so it can be tested directly. Add exporter test build wiring and cover both linear and radial gradient output. AI-assisted-by: gpt-5.6-luna * ✨ Standardize exporter testing workflow Align exporter scripts with the frontend testing pattern. Add a dedicated GitHub Actions workflow and document the canonical exporter commands in Serena memories. AI-assisted-by: gpt-5.6-luna * ✨ Add focused exporter test execution Mirror frontend test-runner behavior for focused namespaces and test vars. Support --focus, --log-level, and --help, and document the commands. AI-assisted-by: gpt-5.6-luna * 🐛 Replace shell exec with execFile in exporter Replace child_process.exec with execFile to eliminate shell interpretation. Add hex color validation in exporter and frontend to reject malformed input before command construction. This fixes GHSA-4f36-m4hj-cv86 (CVSS 9.9 Critical), an authenticated OS command injection vulnerability where malicious fill-color values could execute arbitrary commands in the exporter container. Defense in depth: - Layer 1: execFile passes arguments directly without shell parsing - Layer 2: Exporter validates colors with strict hex regex - Layer 3: Frontend filters invalid colors before DOM emission All three independent reporters' attack vectors are addressed: - Quote breakout (lyhtheori) - Command substitution (B1gN0Se) - Path traversal (KimiSecurityTeam) AI-assisted-by: qwen3.7-plus * 🐛 Use existing hex-color-string? and fix test path mismatch Address code review feedback: - Replace duplicated hex-color-rx and valid-hex-color? with existing hex-color-string? from app.common.types.color - Fix RCE test to use marker path in payload instead of hardcoded /tmp/pwned AI-assisted-by: qwen3.7-plus --------- Co-authored-by: Sumit Ridhal <sridhal@redhat.com>
3.1 KiB
3.1 KiB
Exporter Architecture and Workflow
exporter/: CLJS/Node headless export service. Depends on common/; uses Playwright plus export JS/CLJS deps for SVG/PDF/assets.
Layout and commands
- Source:
exporter/src/; config:deps.edn,shadow-cljs.edn,package.json; runtime helpers/assets:vendor/,scripts/. - From
exporter/: setup./scripts/setup; watchpnpm run watchorpnpm run watch:app; production buildpnpm run build; test bundlepnpm run build:test; testspnpm run testorpnpm run test:quiet; lintpnpm run lint:clj; format check/fixpnpm run check-fmt:clj/pnpm run fmt:clj. - Because exporter consumes
common/, shared file/shape/model changes may need exporter verification even when the immediate change is not underexporter/. - Cross-cutting testing principles and anti-patterns:
mem:testing. - Exporter test conventions and CI:
mem:exporter/testing.
HTTP and browser pool
- POST body limit is about 60 MB. Exporter supports
application/transit+json; request params merge query params and body params. - Map response bodies are Transit JSON and force HTTP 200; nil 200 bodies become 204.
- Auth token comes from cookie
auth-token, then uploads use Bearer auth plus the management shared key. - Each export job gets a fresh Playwright browser context. On success, the context closes and the browser returns to the pool; on error, the browser is destroyed instead of reused.
- Borrow validates browser connection. Pool acquire timeout is about 10s; font loading timeout logs a warning and continues after about 15s.
Export batching and async behavior
prepare-exportsgroups entries by[scale type]and partitions groups into chunks of 50. Each partition uses file/page/share/name from its first item, so be careful if entries might cross those boundaries.- Single-export response is used only when multiple export is not forced and there is exactly one prepared export containing exactly one object.
- Multi-object export can run async: when
waitis false it returns a resource immediately and publishes progress/end/error to Redis by profile topic; whenwaitis true it waits for upload and returns the uploaded resource. - Frame export returns a resource immediately and publishes Redis updates; it does not follow the same
waitoption path. - ZIP entry names are sanitized and duplicates receive numeric suffixes.
Render details
- Bitmap export differs for WASM vs non-WASM render paths: WASM forces Playwright
deviceScaleFactorto 1 and passes scale through the render URL; non-WASM usesdeviceScaleFactor = scale. - WebP is produced by taking a PNG screenshot and converting it with ImageMagick.
- SVG export rasterizes text foreignObjects to PNG, converts through PPM/color masks/potrace, and reassembles SVG paths. It also replaces non-breaking spaces for SVG compatibility and drops empty defs/paths.
- PDF export injects
@pagesizing through raw browserevaluateJavaScript; that code cannot rely on CLJS runtime helpers. - Temporary resources schedule local deletion, then uploads POST to
/api/management/methods/upload-tempfilewithX-Shared-Key: exporter <management-key>and Bearer auth.