mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
* Security: enforce the automation read boundary on stored smart-filter params Smart filters store a command id and params inside the document, so a crafted .pcraft could resolve stored ambient paths (displace mapPath, color-lookup LUT files) at render time without the authorize gate: synchronous smart-object rendering has no Session argument, spawned job threads drop the requesting session's authority, and a trusted UI render could warm the process-wide stack cache with a stage the gate never saw. Thread the requesting session's gate through a thread-local in jobs.rs (entered around command execution, reinstalled on spawned workers and on job apply, restored on drop), re-check stored filter commands in render_in before source resolution and any cache lookup and in apply_filter_to_surface, and re-authorize layer.smartFilter.setParam with the complete merged params after merging stored document params — including edits whose request contains no path and actions.play steps. Denial is conservative: the document is unchanged and no worker starts. Regression tests in the automation crate cover rerender, cached-stage reuse, merged-param edits, action playback, background jobs, and filter transfer, for both the displace map and color-lookup LUT vectors. * Review: carry render authority through Session::edit and surface denials Session::edit is the engine's universal mutation entry (linked-file refresh, transforms, place); renders reached through it ran with no authorization scope installed, so stored smart-filter params read ambiently there even after the execute-path fix. Install the scope in Session::edit as well, keyed on the session's authorize gate. apply_filter_to_surface also conflated a denied filter with an unknown one: authorize_render_command's Result was discarded with .ok()?, so a denial silently rendered unfiltered pixels and the stage entered the process-wide stack cache. The signature becomes Result<Option<Surface>>: Err for denials (callers propagate), None for ids that aren't pixel filters (pass-through semantics unchanged), and an unreadable map keeps its deliberate pass-through. apply_smart_filter and apply_smart_filters propagate instead of cur.clone(). Two tests: a refresh driven outside command execution (Session::edit) is denied and leaves the document unchanged; a denied render caches no stage. Nine authorization tests pass, engine suite and strict Clippy clean, panic hunt green. * ui_set description: name the layerFilter field main added The Layers-panel filter dropdown (#5048) added layerFilter to the control method's UI_SET_FIELDS without extending the ui_set tool's description, so the drift guard (mcp.rs, ui_set_description_names_every_ accepted_field) fails on Windows CI for any branch built on main. Name the field with its patch shape: by (kind, name or mode), kinds (a list of layer kinds), name (matches part of the layer name), mode (a blend mode name). The 40 MCP tests pass; strict Clippy clean. * wasm size: compose and algo join cms at opt-level "z" to clear the 24 MiB gate Main's browser build is 171 KB over the 24 MiB wasm gate in CI (25,337,343 bytes), so every branch built on current main fails the web size check. Demote three pixel crates in the wasm-release profile: - cms: transforms are built once and cached (transform::cached), so "z" costs little runtime (measured -42 KB). - compose: the browser app composites per frame on the GPU (crates/gpu, WebGPU/WebGL2), so the CPU compositor is not the browser's hot path there (measured -122 KB). - algo: filters run per pixel on every interactive filter application. "z" there is the lever of last resort (measured -331 KB), taken because the gate is enforced in CI; if browser filters feel too slow, restore 3 here and reclaim the bytes from a feature-gated decoder. Final size 24,811,949 bytes locally, 354 KB under the gate; the native profiles are untouched. If a maintainer prefers keeping algo at 3, the alternative is raising the self-imposed 24 MiB early-warning limit (Cloudflare's hard cap is 25 MiB) - a policy call in packaging/web/ package.sh, not a code fix.