Core: apply auto-execution overrides when opening blend-files

Command line overrides now define the default "Trusted Source"
for drop and the file-selector.

This is done so the default matches the behavior of opening
files in contexts where the option to trust source isn't shown.

"Recover Last Session" run without setting "use_scripts"
(from Python for e.g.) now disables auto-execution
unless a command line override is passed in,
as the path being recovered isn't known until the file is read.

This includes a refactor & tests.

Details:

- `bpy.path.is_autoexec(..., skip_overrides=False)`:
  Can be used to check if the current environment would trust a file.
- Expose: `bpy.app.autoexec_override`,
  set when `--enable-autoexec` & `--disable-autoexec` are passed in.
- `BKE_autoexec_default_trust_source`:
  new function to more conveniently access the default trust for a file.
- Show when "Auto Run Python Scripts" preference is overridden.
- Add tests for the file selector, dropping & the command line.

Ref !163894
This commit is contained in:
Campbell Barton 2026-09-17 10:56:08 +10:00
parent 08fc1565cd
commit 93145bafae
12 changed files with 430 additions and 51 deletions

View file

@ -1871,6 +1871,44 @@ if(WITH_UI_TESTS)
endforeach()
unset(_ui_tests)
# The command line overriding the preference can't be set from within a test.
set(_ui_tests_enable_autoexec
test_blend_autoexec.pref_disabled_drop_blend_file_enable_autoexec
test_blend_autoexec.pref_enabled_drop_blend_file_untrusted_enable_autoexec
test_blend_autoexec.pref_disabled_drop_blend_file_trusted_source_enable_autoexec
test_blend_autoexec.pref_disabled_open_file_selector_enable_autoexec
test_blend_autoexec.pref_enabled_open_file_selector_untrusted_enable_autoexec
)
foreach(ui_test ${_ui_tests_enable_autoexec})
add_blender_test_ui(
"${ui_test}"
--enable-autoexec
--enable-event-simulate
--python "${CMAKE_CURRENT_LIST_DIR}/ui_simulate/run_blender_setup.py"
--
--tests "${ui_test}"
)
endforeach()
unset(_ui_tests_enable_autoexec)
set(_ui_tests_disable_autoexec
test_blend_autoexec.pref_enabled_drop_blend_file_disable_autoexec
test_blend_autoexec.pref_enabled_drop_blend_file_trusted_source_disable_autoexec
test_blend_autoexec.pref_enabled_drop_blend_file_untrusted_trusted_source_disable_autoexec
test_blend_autoexec.pref_enabled_open_file_selector_disable_autoexec
)
foreach(ui_test ${_ui_tests_disable_autoexec})
add_blender_test_ui(
"${ui_test}"
--disable-autoexec
--enable-event-simulate
--python "${CMAKE_CURRENT_LIST_DIR}/ui_simulate/run_blender_setup.py"
--
--tests "${ui_test}"
)
endforeach()
unset(_ui_tests_disable_autoexec)
# test_open_editor_types opens many areas, each needing its own GLContext.
# The number of available context is limit by driver and not queryable.
if(WITH_OPENGL_BACKEND)

View file

@ -180,9 +180,12 @@ def blender_state_print():
sys.stdout.write("{:s}{:s}\n".format(STATE_PREFIX, json.dumps({
"filepath": filepath,
"autoexec": bpy.app.autoexec,
"autoexec_override": bpy.app.autoexec_override,
"autoexec_fail": bpy.app.autoexec_fail,
"autoexec_fail_message": bpy.app.autoexec_fail_message,
"scene_name": bpy.data.scenes[0].name,
# The default for "Trusted Source" when opening this file interactively.
"trust_source": bpy.path.is_autoexec(filepath, skip_overrides=False, canonicalize=True, strip_filename=True),
})))
# Flush so the line isn't held in the buffer while Blender's own output is written.
sys.stdout.flush()
@ -322,6 +325,10 @@ class TestAutoExecCLI(unittest.TestCase):
)
self.assertEqual(len(states), 1, message)
self.assert_blend_file_state(states[0], filename, is_trusted=is_trusted, message=message)
# Opening the same file interactively must default to the same trust.
self.assertEqual(states[0]["trust_source"], is_trusted, message)
is_override = any(arg in {"--enable-autoexec", "--disable-autoexec"} for arg in args)
self.assertEqual(states[0]["autoexec_override"], is_override, message)
# -------------------------------------------------------------------------
# Tests with Auto-Execution Preference "Enabled"

View file

@ -45,17 +45,35 @@ class TestBpyPath(unittest.TestCase):
self.addCleanup(prefs.autoexec_paths.remove, path_cmp)
path_cmp.path = "/untrusted/"
self.assertFalse(is_autoexec("/untrusted/"))
self.assertFalse(is_autoexec(b"/untrusted/"))
self.assertTrue(is_autoexec("/trusted/"))
self.assertFalse(is_autoexec("/untrusted/", skip_overrides=True))
self.assertFalse(is_autoexec(b"/untrusted/", skip_overrides=True))
self.assertTrue(is_autoexec("/trusted/", skip_overrides=True))
self.assertFalse(is_autoexec("/untrusted/demo.blend", strip_filename=True))
self.assertTrue(is_autoexec("/trusted/demo.blend", strip_filename=True))
self.assertFalse(is_autoexec("/untrusted/demo.blend", strip_filename=True, skip_overrides=True))
self.assertTrue(is_autoexec("/trusted/demo.blend", strip_filename=True, skip_overrides=True))
path_cmp.use_glob = True
path_cmp.path = "*/download*"
self.assertFalse(is_autoexec("/home/user/downloads/"))
self.assertTrue(is_autoexec("/home/user/projects/"))
self.assertFalse(is_autoexec("/home/user/downloads/", skip_overrides=True))
self.assertTrue(is_autoexec("/home/user/projects/", skip_overrides=True))
# The default for "Trusted Source" also checks the preference.
path_cmp.use_glob = False
path_cmp.path = "/untrusted/"
prefs_filepaths = prefs.filepaths
self.addCleanup(
setattr, prefs_filepaths, "use_scripts_auto_execute", prefs_filepaths.use_scripts_auto_execute)
prefs_filepaths.use_scripts_auto_execute = True
self.assertTrue(is_autoexec("/trusted/", skip_overrides=False))
self.assertFalse(is_autoexec("/untrusted/", skip_overrides=False))
# Nothing is trusted by default, only the excluded paths are still checked.
prefs_filepaths.use_scripts_auto_execute = False
self.assertFalse(is_autoexec("/trusted/", skip_overrides=False))
self.assertFalse(is_autoexec("/untrusted/", skip_overrides=False))
self.assertTrue(is_autoexec("/trusted/", skip_overrides=True))
self.assertFalse(is_autoexec("/untrusted/", skip_overrides=True))
if __name__ == '__main__':

View file

@ -9,6 +9,8 @@ Covered test cases, with automatic script execution enabled & disabled:
- "Dropping a blend-file" both trusted/untrusted cases.
- "Open" (the file selector) both trusted/untrusted cases.
- Enabling "Trusted Source" for an excluded path when dropping.
- Both of the above with the command line overriding the preference,
see the tests ending in ``_enable_autoexec`` & ``_disable_autoexec``.
"""
import modules.ui_test_utils as ui
@ -122,9 +124,12 @@ def _blend_file_open(e, filepath):
yield getattr(e, action)()
def _setup(dirpath_temp, *, use_autoexec):
def _setup(dirpath_temp, *, use_autoexec, autoexec_override=None):
"""
Save a blend-file in a trusted & an excluded directory of ``dirpath_temp``.
:arg autoexec_override: The value of "--enable-autoexec" or "--disable-autoexec"
Blender was started with, None when neither was passed.
"""
import bpy
import os
@ -170,7 +175,11 @@ def _setup(dirpath_temp, *, use_autoexec):
e = _events_after_load()
t.assertEqual(bpy.data.filepath, "")
t.assertEqual(bpy.app.autoexec, use_autoexec, "Auto-execution follows the preference")
t.assertEqual(bpy.app.autoexec_override, autoexec_override is not None)
if autoexec_override is None:
t.assertEqual(bpy.app.autoexec, use_autoexec, "Auto-execution follows the preference")
else:
t.assertEqual(bpy.app.autoexec, autoexec_override, "Auto-execution follows the command line")
return e, t, filepaths
@ -217,19 +226,19 @@ def _blend_file_drop(e, *, filepath):
yield getattr(e, DROP_KEY_WORKAROUND.lower())()
def _assert_blend_file_dropped(t, filepath, *, is_path_trusted, message=None):
def _assert_blend_file_dropped(t, filepath, *, is_trusted, message=None):
"""
Check whether the path is excluded, an excluded path shows the option
disabled as "Trusted Source [Untrusted Path]".
Check the default for "Trusted Source" the popup was shown with.
The popups own "Trusted Source" state isn't checked, it's stored on the
operator running the popup which isn't reachable from here.
operator running the popup which isn't reachable from here,
so check the value it's initialized from.
Opening the file checks it, see ``_assert_blend_file_opened``.
"""
import bpy
t.assertEqual(
bpy.path.is_autoexec(filepath, canonicalize=True, strip_filename=True),
is_path_trusted, message)
bpy.path.is_autoexec(filepath, skip_overrides=False, canonicalize=True, strip_filename=True),
is_trusted, message)
def _drop_ui_trusted_source_toggle(e):
@ -248,10 +257,23 @@ def _drop_ui_open(e):
yield e.o()
def _blend_file_drop_test(filename, *, use_autoexec, is_trusted, message):
is_path_trusted = filename == BLEND_TRUSTED
def _blend_file_drop_test(
filename, *,
use_autoexec,
autoexec_override=None,
toggle_trusted_source=False,
is_trusted,
message,
):
"""
Drop ``filename`` & open it from the popup, checking it opened with ``is_trusted``.
:arg toggle_trusted_source: Toggle "Trusted Source" before opening,
so the popup must default to the opposite of ``is_trusted``.
"""
with _temp_dir_context() as dirpath_temp:
e, t, filepaths = yield from _setup(dirpath_temp, use_autoexec=use_autoexec)
e, t, filepaths = yield from _setup(
dirpath_temp, use_autoexec=use_autoexec, autoexec_override=autoexec_override)
filepath = filepaths[filename]
yield from _blend_file_drop(e, filepath=filepath)
@ -259,10 +281,13 @@ def _blend_file_drop_test(filename, *, use_autoexec, is_trusted, message):
_assert_blend_file_dropped(
t,
filepath,
is_path_trusted=is_path_trusted,
is_trusted=is_trusted != toggle_trusted_source,
message=message,
)
if toggle_trusted_source:
yield from _drop_ui_trusted_source_toggle(e)
yield from _drop_ui_open(e)
_assert_blend_file_opened(
@ -405,7 +430,7 @@ def pref_disabled_drop_blend_file_trusted_source():
_assert_blend_file_dropped(
t,
filepath,
is_path_trusted=False,
is_trusted=False,
message="An excluded path must not be trusted in the drop UI",
)
@ -419,3 +444,175 @@ def pref_disabled_drop_blend_file_trusted_source():
is_trusted=True,
message="Enabling \"Trusted Source\" from the drop UI must run scripts",
)
# -----------------------------------------------------------------------------
# Tests with `--enable-autoexec` Overriding the Preference
#
# Blender is started with the argument, see the CMake test definitions.
def pref_disabled_drop_blend_file_enable_autoexec():
"""
Drop a trusted file & open it from the popup with the preference disabled
but "--enable-autoexec" passed, its scripts must run.
Proves the command line overrides the preference in the drop UI.
"""
yield from _blend_file_drop_test(
BLEND_TRUSTED,
use_autoexec=False,
autoexec_override=True,
is_trusted=True,
message="\"--enable-autoexec\" must override the preference in the drop UI",
)
def pref_enabled_drop_blend_file_untrusted_enable_autoexec():
"""
Drop an excluded path & open it from the popup with "--enable-autoexec" passed,
its scripts must run.
Proves the command line overrides the excluded paths in the drop UI.
"""
yield from _blend_file_drop_test(
BLEND_UNTRUSTED,
use_autoexec=True,
autoexec_override=True,
is_trusted=True,
message="\"--enable-autoexec\" must override the excluded paths in the drop UI",
)
def pref_disabled_drop_blend_file_trusted_source_enable_autoexec():
"""
Drop a trusted file with "--enable-autoexec" passed & disable "Trusted Source"
before opening it, its scripts must not run.
Proves the command line only sets the default, the user may still opt-out.
"""
yield from _blend_file_drop_test(
BLEND_TRUSTED,
use_autoexec=False,
autoexec_override=True,
toggle_trusted_source=True,
is_trusted=False,
message="Disabling \"Trusted Source\" from the drop UI must not run scripts, even with \"--enable-autoexec\"",
)
def pref_disabled_open_file_selector_enable_autoexec():
"""
Open a trusted file from the file selector with the preference disabled
but "--enable-autoexec" passed, its scripts must run.
Proves the command line overrides the preference in the file selector.
"""
with _temp_dir_context() as dirpath_temp:
e, t, filepaths = yield from _setup(dirpath_temp, use_autoexec=False, autoexec_override=True)
yield from _blend_file_open(e, filepaths[BLEND_TRUSTED])
_assert_blend_file_opened(
t,
BLEND_TRUSTED,
is_trusted=True,
message="\"--enable-autoexec\" must override the preference in the file selector",
)
def pref_enabled_open_file_selector_untrusted_enable_autoexec():
"""
Open an excluded path from the file selector with "--enable-autoexec" passed,
its scripts must run.
Proves the command line overrides the excluded paths in the file selector.
"""
with _temp_dir_context() as dirpath_temp:
e, t, filepaths = yield from _setup(dirpath_temp, use_autoexec=True, autoexec_override=True)
yield from _blend_file_open(e, filepaths[BLEND_UNTRUSTED])
_assert_blend_file_opened(
t,
BLEND_UNTRUSTED,
is_trusted=True,
message="\"--enable-autoexec\" must override the excluded paths in the file selector",
)
# -----------------------------------------------------------------------------
# Tests with `--disable-autoexec` Overriding the Preference
#
# Blender is started with the argument, see the CMake test definitions.
def pref_enabled_drop_blend_file_disable_autoexec():
"""
Drop a trusted file & open it from the popup with the preference enabled
but "--disable-autoexec" passed, its scripts must not run.
Proves the command line overrides the preference in the drop UI.
"""
yield from _blend_file_drop_test(
BLEND_TRUSTED,
use_autoexec=True,
autoexec_override=False,
is_trusted=False,
message="\"--disable-autoexec\" must override the preference in the drop UI",
)
def pref_enabled_drop_blend_file_trusted_source_disable_autoexec():
"""
Drop a trusted file with "--disable-autoexec" passed & enable "Trusted Source"
before opening it, its scripts must run.
Proves the command line only sets the default, the user may still opt-in.
"""
yield from _blend_file_drop_test(
BLEND_TRUSTED,
use_autoexec=True,
autoexec_override=False,
toggle_trusted_source=True,
is_trusted=True,
message="Enabling \"Trusted Source\" from the drop UI must run scripts, even with \"--disable-autoexec\"",
)
def pref_enabled_drop_blend_file_untrusted_trusted_source_disable_autoexec():
"""
Drop an excluded path with "--disable-autoexec" passed & enable "Trusted Source"
before opening it, its scripts must run.
Proves the excluded paths don't apply when the command line overrides the preference,
matching the file selector.
"""
yield from _blend_file_drop_test(
BLEND_UNTRUSTED,
use_autoexec=True,
autoexec_override=False,
toggle_trusted_source=True,
is_trusted=True,
message="Enabling \"Trusted Source\" for an excluded path must run scripts with \"--disable-autoexec\"",
)
def pref_enabled_open_file_selector_disable_autoexec():
"""
Open a trusted file from the file selector with the preference enabled
but "--disable-autoexec" passed, its scripts must not run.
Proves the command line overrides the preference in the file selector.
"""
with _temp_dir_context() as dirpath_temp:
e, t, filepaths = yield from _setup(dirpath_temp, use_autoexec=True, autoexec_override=False)
yield from _blend_file_open(e, filepaths[BLEND_TRUSTED])
_assert_blend_file_opened(
t,
BLEND_TRUSTED,
is_trusted=False,
message="\"--disable-autoexec\" must override the preference in the file selector",
)