From bf94959d6608e2f11f5015caac9f58795fbb0051 Mon Sep 17 00:00:00 2001 From: Fiddle-Config Team Date: Mon, 9 Mar 2026 14:18:55 -0700 Subject: [PATCH] Add allow_imports option to DEFINE_fiddle_config etc., default True A later change will flip the default value of this flag to False. Setting this flag to False fixes an RCE security vulnerability that occurs if the Fiddle flags come from an untrusted or less-trusted source. It prevents Fiddle from implicitly loading modules and executing code when dotted names are passed, such as --config=config:foo.bar(). PiperOrigin-RevId: 881035149 --- fiddle/_src/absl_flags/flags.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/fiddle/_src/absl_flags/flags.py b/fiddle/_src/absl_flags/flags.py index d769f67a..0e956b27 100644 --- a/fiddle/_src/absl_flags/flags.py +++ b/fiddle/_src/absl_flags/flags.py @@ -135,6 +135,14 @@ def __init__( # This will save all arguments that have passed, when unparse() is called # this will also be reset to empty. self._all_arguments = [] + + if default_module is None and not allow_imports: + raise ValueError( + "A Fiddle config flag must either specify a default_module to load " + "configs from, or set allow_imports=True to allow them to be loaded " + "from any module." + ) + super().__init__(*args, **kwargs) def _apply_fiddler(self, cfg: config.Buildable, expression: str): @@ -239,7 +247,21 @@ def value(self): or command == "config_file" or command == "config_str" ): - self._parse_config(command, expression) + try: + self._parse_config(command, expression) + except ValueError as e: + if ( + "Could not resolve reference to named function" in str(e) + and not self.allow_imports + ): + raise ValueError( + "If this is a new error in previously-functional code, pass " + "allow_imports=True to DEFINE_fiddle_config(). This allows " + "anyone with control of the fiddle command-line flags to " + "execute arbitrary code; only do this if such imports are " + "trusted." + ) from e + raise elif command == "set": utils.set_value(self._value, expression) @@ -267,6 +289,7 @@ def DEFINE_fiddle_config( # pylint: disable=invalid-name pyref_policy: Optional[serialization.PyrefPolicy] = None, flag_values: flags.FlagValues = flags.FLAGS, required: bool = False, + allow_imports: bool = True, ) -> flags.FlagHolder[Any]: r"""Declare and define a fiddle command line flag object. @@ -325,6 +348,10 @@ def main(argv) -> None: registered. This should almost never need to be overridden. required: bool, is this a required flag. This must be used as a keyword argument. + allow_imports: If true, then fully qualified dotted names may be used to + specify configs or fiddlers that should be automatically imported. This + grants arbitrary code execution to anyone who can specify the fiddle + command-line flags. Returns: A handle to defined flag. @@ -338,6 +365,7 @@ def main(argv) -> None: parser=flags.ArgumentParser(), serializer=FiddleFlagSerializer(pyref_policy=pyref_policy), help_string=help_string, + allow_imports=allow_imports, ), flag_values=flag_values, required=required,