Configs Are Code: Threat Model#

A Hydra config can choose which Python code runs: instantiating a config imports and calls its _target_. So a config is code, much like a pickle file is, and it should be treated with the same level of trust.

hydra-zen’s configs are generated from your Python code. builds(f) writes a config that calls f because you asked it to. Thus, by default, hydra-zen treats configs as trusted code.

When Should You Worry?#

In the everyday workflow (you write the code, you run it, and you supply the command-line overrides) there is no attacker. Anyone who can type model._target_=os.system at your command line could just as easily run Python directly.

Config values become a risk when they come from someone less trusted than the program that instantiates them, for example:

  • a YAML config, or saved .hydra/config.yaml, that someone else wrote or could have modified, which you then load and instantiate;

  • a service (e.g. a job-submission web UI) that forwards users’ overrides to a Hydra app running with the service’s privileges.

Do not instantiate configs from sources you don’t trust. Hydra’s own checks on instantiation are, in Hydra’s words, defense in depth, not a security boundary: Hydra has many other ways for configs and command-line overrides to choose what code runs (launchers, plugins, hydra settings, and more).

Remote launchers#

Launchers that run jobs elsewhere (e.g. on a cluster) send the job, including its config, through some channel (a shared filesystem, a queue, an object store). Hydra’s launchers serialize jobs with pickle, so whoever can write to that channel can run arbitrary code, regardless of any whitelist. Make sure that only you can write to that channel.

Hydra’s Execution Whitelist#

hydra-core 1.4 introduced an execution whitelist, which restricts which targets instantiation may use [1]. Without a whitelist, Hydra 1.4 warns on every instantiated target, and Hydra 1.5 will raise an error.

hydra-zen’s behavior:

  • By default, hydra-zen treats configs as trusted code. instantiate() (and therefore zen() and launch()) instantiates with Hydra’s execution checks disabled, unless a whitelist is active, e.g. via hydra.main(execution_whitelist=...), or with hydra.utils.execution_whitelist(...):.

  • You can opt into a whitelist via instantiate(..., _execution_whitelist_=...), launch(..., execution_whitelist=...), and zen(...).hydra_main(..., execution_whitelist=...).

  • “auto” expands to every target that hydra-zen generated in-code (via builds, just, zen_wrappers, etc.) during this process. It can be combined with other entries, e.g. ["auto", "torch.*"].

  • instantiate(..., _execution_whitelist_=...) extends an active execution_whitelist(...) context, whereas the whitelist of launch and hydra_main replaces it for the job’s duration (as with hydra.main).

from hydra_zen import builds, instantiate, zen

def task(x: int) -> None: ...

Conf = builds(dict, a=1)

instantiate(Conf, _execution_whitelist_="auto")  # OK: `dict` was built in-code
instantiate({"_target_": "os.getcwd"}, _execution_whitelist_="auto")  # rejected

zen(task).hydra_main(config_name="main", execution_whitelist=["auto", "torch.*"])

Enforcing a whitelist rejects any target that isn’t on it, including legitimate ones: a _target_ overridden at the command line, targets in YAML configs, or the result of a factory that returns a class or other callable (Hydra also authorizes callable results). Add these to the whitelist explicitly.

hydra-zen’s zen_meta, zen_wrappers, and just features resolve objects from import paths. Whatever policy is active (Hydra’s blocklist, or a whitelist) is applied to those paths too, just as Hydra applies it to its own resolvers such as hydra.utils.get_object.