Scripting: export commands a script runs directly respect Allow Scripts to Write Files (#2921)

* Scripting: export commands a script runs directly respect Allow Scripts to Write Files

CompItem.saveFrameToPng and exportAsMotionGraphicsTemplate checked the
file-write preference only in their JavaScript wrappers. A script could call
comp.saveFrameAs or essential.exportTemplate itself (app.run, __exec) and the
native handlers wrote the file with the preference off.

Session::refuse_export and write_output_with now refuse when the running
command was issued by a script and the preference is off, so every command
that writes a path it is given is gated the same way. The wrappers check the
gate with a new side-effect-free __file("checkWrite") instead of writing an
empty file at the destination.

* address review: clear stale export test dir before use

* test: allow script writes in script_imports_footage_and_saves
This commit is contained in:
Dylan M. Taylor
2026-11-11 08:24:28 -05:00
committed by GitHub
parent be5c98506f
commit 33a15e1d86
5 changed files with 72 additions and 3 deletions
+16
View File
@@ -509,12 +509,27 @@ impl Session {
/// is not for an export to replace. For callers that write the file themselves; call it
/// before the work, not after it.
pub fn refuse_export(&self, path: &str) -> crate::Result<()> {
self.refuse_script_write(path)?;
match self.blocking(path) {
Some(source) => Err(crate::EngineError::Other(source.refusal(path, Use::Export))),
None => Ok(()),
}
}
/// `Err` when a script issued the running command ([`Session::running_for_script`]) and
/// Settings ▸ Scripting & Expressions ▸ Allow Scripts to Write Files is off. The scripting
/// wrappers check this too, but a script can run any command directly (`app.run`, `__exec`),
/// so the commands that write a path they are given check here, through
/// [`Session::refuse_export`] and [`Session::write_output`].
pub fn refuse_script_write(&self, path: &str) -> crate::Result<()> {
if self.running_for_script() && !self.prefs.scripting.allow_scripts_write_files {
return Err(crate::EngineError::Other(format!(
"Unable to write {path}: scripts can't write files unless Settings ▸ Scripting & Expressions ▸ Allow Scripts to Write Files and Access Network is on"
)));
}
Ok(())
}
/// Write `bytes` to `path` through the host's services, unless the project reads the file
/// there ([`Session::refuse_export`]). Every command that writes to a path it is given
/// writes through this; Save and Save As (the project's own file) do not.
@@ -525,6 +540,7 @@ impl Session {
/// [`Session::write_output`] against files already found ([`Session::protected_files`]),
/// for a command that writes many.
pub(crate) fn write_output_with(&self, protected: &Protected, path: &str, bytes: &[u8]) -> crate::Result<()> {
self.refuse_script_write(path)?;
if let Some(source) = protected.find(Path::new(path)) {
return Err(crate::EngineError::Other(source.refusal(path, Use::Export)));
}