Skip to content

fix: Port agent-config code-reference and config_path validation to v1 - #6790

Open
GWeale wants to merge 4 commits into
v1from
backport-v1-pr08
Open

fix: Port agent-config code-reference and config_path validation to v1#6790
GWeale wants to merge 4 commits into
v1from
backport-v1-pr08

Conversation

@GWeale

@GWeale GWeale commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

This PR ports three agent-config fixes to the v1 branch, plus a preparatory rename.

  1. Module denylist for code references (6a5be34b, faa17442, a16f6da3)

    • Code references in YAML agent configs must name a top-level module outside sys.stdlib_module_names | sys.builtin_module_names; anything else raises ValueError. Enforced at every import site, on by default.
    • Opt out process-wide with config_agent_utils._set_enforce_denylist(False), which an agent package whose own name matches a standard-library module (secrets, types) needs.
  2. config_path resolution (171ae9e7)

    • config_path in an AgentRefConfig must be relative and resolve under the directory of the config that names it; an absolute path raises.
    • No opt-out. Migrate by moving the target under that directory; a symlink out of it raises too.
  3. Builder upload validation (89950051)

    • POST /builder/save returns 400 unless every code reference in the uploaded YAML names code under the app being edited or an ADK built-in. Builder routes are mounted only when web=True.
    • Re-implemented for v1 rather than ported.

Preparatory rename: _ENFORCE_DENYLIST and _set_enforce_denylist become _ENFORCE_YAML_KEY_DENYLIST and _set_enforce_yaml_key_denylist, matching main. _set_enforce_denylist controls the module denylist; _set_enforce_yaml_key_denylist controls the args YAML-key check.

GWeale and others added 4 commits August 17, 2026 22:59
The module-level flag guarding the `args` YAML-key check in
`config_agent_utils` was called `_ENFORCE_DENYLIST`, with
`_set_enforce_denylist()` as its setter. It now reads
`_ENFORCE_YAML_KEY_DENYLIST` / `_set_enforce_yaml_key_denylist()`, matching
the name used on the main branch.

The name is being freed for a second, unrelated control that the next commit
adds: a denylist of modules an agent config may import from. Leaving both
checks on one flag would make enabling the key check silently enable the
module check as well.

Pure rename with no behaviour change. The only caller in product code is
`get_fast_api_app`, which enables the check when the web UI is on, and it is
updated here.
A YAML agent config names Python code by dotted path, and the loader
imported whatever it was given. `before_agent_callbacks: [{name: os.system}]`
resolved and became a callback; `tools: [{name: cProfile.run}]` resolved and
became a tool that runs a string it is handed. Now every name a config
supplies is checked before the import, and a name whose top-level module is
part of the standard library is rejected with a ValueError.

The check is `_validate_module_reference()`, called at the four places that
turn a config-supplied name into an object: `resolve_fully_qualified_name`,
`_resolve_agent_code_reference` and `resolve_code_reference` in
config_agent_utils, and the user-defined branch of `LlmAgent._resolve_tools`.
The blocked set is `sys.stdlib_module_names` and `sys.builtin_module_names`,
plus an explicit list for names that stay importable without being reported
as standard library any more: `distutils`, `telnetlib`, `pipes`, `crypt`,
CPython's own `test` and `_testcapi` packages, and the `posix`, `nt`,
`_posixsubprocess` and `_socket` aliases.

Blocking the whole standard library rather than a list of dangerous modules
is deliberate. A short list has to be right about which modules can run code,
and `cProfile.run`, `timeit.timeit` and `trace.Trace.run` all execute a string
you pass them, with more arriving in each Python release.

Behaviour change: an existing config that names a standard-library callable
stops loading. Nothing in this repository does. The case to watch is an agent
package whose own name matches a standard-library module, such as `test`,
`secrets` or `calendar`; `_set_enforce_denylist(False)` turns the check off.
Third-party packages stay resolvable by name, so integrations keep working
and this narrows the surface rather than closing it.

Ports the final state of three changes that supersede one another upstream,
so the intermediate module lists are not reproduced here.

Co-authored-by: Ashutosh Kumar Singh <161562995+Ashutosh0x@users.noreply.github.com>
Co-authored-by: HenD.YA <yusmer96@gmail.com>
Co-authored-by: Kathy Wu <wukathy@google.com>
…on (v1)

`resolve_agent_reference` took the `config_path` of a sub-agent or AgentTool
reference straight from the YAML. An absolute path was loaded as given, and a
relative one was joined to the referencing config's directory with no check
on where it landed, so `../../../../etc/passwd` read that file on the server
and the FileNotFoundError told the caller whether a path existed. It now
rejects an absolute config_path outright, and resolves a relative one through
os.path.realpath and requires the result to stay inside the directory holding
the config that named it.

Behaviour change, and the one in this area that can break a working setup. An
absolute config_path is legal on 1.x today and is the obvious way to point at
a shared agent library outside the app tree; it now raises. A relative path
that climbs above the referencing config's own directory also raises, and the
boundary is that directory rather than the agents root, so `../shared/x.yaml`
is rejected even though it stays under agents_dir. No config in this
repository does either.

Co-authored-by: Adil Burak Şen <56400880+adilburaksen@users.noreply.github.com>
The Agent Builder upload check tested one key, `args`, and let everything else
through. So `tools: [{name: os.system}]` was accepted, written under the
agents directory, and imported and called the next time that agent loaded. It
now also validates every field whose value names Python code the loader will
import, and requires each name to live under the app being edited or to be an
ADK built-in.

The fields checked are the twelve in `_CODE_REFERENCE_KEYS`: agent_class, the
six callback lists, code, input_schema, output_schema, model_code and tools. A
name with no dots is left alone, because the loader resolves it against
`google.adk.agents` or `google.adk.tools` rather than anything the upload
controls. A qualified ADK name is allowed one segment past those namespaces,
which is what an undotted name would have reached anyway; a deeper path walks
into a submodule and is refused.

An app whose own name matches an importable module, `os` for instance, gets a
distinct error rather than an allow, since a reference starting `os.` cannot
be told apart from one leaving the app.

Behaviour change, confined to the builder UI. A builder user whose YAML
references a helper in a sibling app, or a third-party package by dotted name,
now gets a 400 on save.

This is upload-time and stricter than the load-time module denylist added
earlier in this branch. Neither replaces the other: the loader must keep
accepting third-party packages, and the builder need not.

Upstream this lives in `dev_server.py`, which does not exist on this branch;
the equivalent code here is in `fast_api.py`, gated on `web` in the same way.
@GWeale GWeale changed the title fix: Port agent-config code-reference and path hardening to v1 fix: Port agent-config code-reference and config_path validation to v1 Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants