The fallback name was never checked against the names already taken, so an
entry called exactly what the fallback produces took the same file: `media/1/a:b.png`,
`media/2/a_b.png` and `media/3/2-a_b.png` extracted to five files for six
entries, the third overwriting the second, and both items' footage pointing at
it. Deduplication now runs on the name actually written and counts up until it
is free.
The collision predates making the names safe (two entries named `a_b.png` and
`2-a_b.png` collide on their own), but sanitising gives more ways in, so it
belongs with it.
Found by CodeRabbit on the pull request.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dropping the points a later stroke of the other kind painted over could leave
a stroke with none, even though most of it was still painted: a small Alt tap
in the middle of a large foreground dot removed its only point, and the
selected model was skipped for the classical cut. Points are now kept by the
resolved ownership under them, and a stroke that loses every sampled point
but still owns pixels is prompted from the one deepest inside them. A stroke
painted over completely still prompts nothing.
Green, Alt (red) over part of the object, then green there again, as the
Layer panel sends them through roto.stroke: the render follows the last
stroke, and Undo / Redo step between the two results.
Segmentations and rendered frames are keyed by their strokes, which now
segment differently where strokes overlap; bumping both versions keeps old
entries from showing the old result. Frozen mattes are stored in the project
and are untouched.
A trained model is no longer prompted with both labels at one spot: points
along a stroke that a later stroke of the other kind painted over are
dropped, and the propagated matte's points, box and mask take this frame's
strokes over it.
A Refine Edge stroke over an earlier Alt (erase) stroke now restores the band
there; the band carried from the previous frame stays under this frame's
strokes.
A green stroke over an earlier Alt (red) stroke now restores the area: where
strokes overlap, the one painted last sets the hard constraint, for the cut,
the colour samples and the cleanup seeds alike.
`Prefs::set` makes a JSON pointer of the key (`key.replace('.', "/")`), so
`scripting/allowScriptsWriteFiles` resolves as well as the dotted spelling, and
the page on its own (`{key: "scripting", value: {…}}`) replaces every setting on
it — `coerce` takes an object for an object, and the page deserializes with
`serde(default)`. A prefix check on `scripting.` let both through. The page is
now read from the key's first segment, split on either separator.
Found by CodeRabbit on the pull request; reproduced before fixing, with
`prefs.set {key: "scripting/allowScriptsWriteFiles", value: true}` answering
`{"scripting/allowScriptsWriteFiles": true}` under confinement.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
EffectCraft writes its own templates as `media/<item id>/<file>` entries, and
extraction trusted both halves. `base_name` splits on `/` and `\`, but the item
id was taken with `split('/')` alone, and it is pasted in front of a base name
another item already used. So a second `media/..\..\..\Startup\x/clip.mp4`
entry became `<dir>/..\..\..\Startup\x-clip.mp4`, which on Windows is three
folders up, with the attacker choosing how the name ends.
The id is now used only when it is one, and the name goes through
`safe_file_name`: separators, drive colons and NUL become `_`, and `.` / `..`
become `footage`. Reached from `templates.create` with `path`, from File ▸ New
from Template, and from a template dropped in the user Templates folder.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Layer is now a callable function with its own prototype, which costs
more to make than the plain object it was. __layer(c, l) makes each
layer once per evaluation (kept in __S, which __begin resets), so an
expression naming the same layer many times builds it once. That covers
thisComp.layer(...) in a loop, .parent, a Layer Control's value and
thisLayer.
The checks for a layer (the result, thisComp.layer(layer, offset), the
property proxy) read __isLayer only on functions. Numbers and other
primitives are no longer boxed to look the property up.
Timing (release, Windows, medians of 2000 evaluations, PR vs main):
- one evaluation building one layer: about 22 vs 16 us;
- thisLayer.transform.opacity: about 200 vs 170 us;
- 20000 thisComp.layer(1) lookups in one evaluation: 95 vs 138 ms.
In After Effects a layer is a property group: thisLayer("transform"),
thisComp.layer("Other")("effects")("Blur"), thisLayer(1). In
EffectCraft's expression runtime a Layer was a plain object, so those
failed with "TypeError: not a callable function", including the
references the pick whip writes with Compact English off
(thisComp.layer("Other")("effects")("ec.noise.fractal")(...)).
A Layer is now callable like a property group (built as __mkGroup
builds groups), looking up its own properties by match name, name or
index, and keeps its methods, name and index. The places that took a
layer for a plain object (the result reading as the layer's index,
thisComp.layer(layer, offset), a Layer Control's value handed through
the property proxy) accept it.
Test: layers_are_callable_property_groups (fails before).
The CLI installed no logger, so GPU warnings (a declined frame, out of
memory, fallback to the CPU) were dropped and an export silently rendered
on the CPU. Install the engine logger with a `warn` default (RUST_LOG
still overrides). Split out of #1415.
`script.run` and the Scripts menu are refused while file access is confined,
but a ScriptUI panel's script was not: `window.scriptPanel` runs it to build
the panel, `window.restoreScriptPanels` runs every remembered one,
`scriptui.click` / `set` / `mouse` / `close` dispatch events to its handlers,
and `script.runTasks` runs its `app.scheduleTask` tasks. A script reads and
writes files and reaches the network, so none of that may start while confined.
`prefs.set` is refused for the `scripting.*` settings for the same reason: the
engine already stops a script from granting itself the access the user withheld
(`Scripting::same_permissions`), and a confined agent writing it to the settings
file would hand it to every script run afterwards, long after the session.
`prefs.get` and the read-only `scriptui.list` / `scriptui.get` are untouched.
This is the "Running code" part of the F-11 audit; the other parts of #2131
are separate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Upstream split lib.rs files into modules and moved engine commands/ to cmd/; this branch's change merges without textual conflicts.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Upstream split lib.rs files into modules and moved engine commands/ to cmd/; this branch's change merges without textual conflicts.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>