Skip to content

Allow validated McpToolset args in local YAML agents served by adk web#6735

Description

@ftnext

馃敶 Required Information

Is your feature request related to a specific problem?

Yes.

adk web enables a recursive denylist that rejects every YAML key named args.
This is an important defense-in-depth mitigation for CVE-2026-4810: a generic tool configuration can combine a Python code reference in name with attacker-controlled args, causing ADK to import and invoke arbitrary Python code (74f235b).

However, the key-level denylist also rejects built-in tools whose arguments are declarative configuration.
In particular, the existing McpToolset YAML sample cannot be loaded by adk web:

name: retrospective_master
instruction: Use the MCP tools.
model: gemini-3.7-flash
tools:
  - name: McpToolset
    args:
      streamable_http_connection_params:
        url: https://hurikaeri-site.viva-tweet-x.workers.dev/mcp

The same agent works when constructed in Python or loaded by adk run.
Under adk web, the local YAML agent fails to load and is absent from the agent loader, so a subsequent POST /run_sse returns 404 Not Found.

_BLOCKED_YAML_KEYS = frozenset({"args"})

if key in _BLOCKED_YAML_KEYS:
raise ValueError(
f"Blocked key {key!r} found in {filename!r}. "
f"The '{key}' field is not allowed in agent configurations "
"because it can execute arbitrary code."
)

Describe the Solution You'd Like

Replace the all-or-nothing key check for locally loaded YAML with a positive allowlist for security-reviewed ADK built-ins.

For an allowlisted built-in such as the exact literal name McpToolset:

  1. Do not resolve a module name supplied by YAML.
  2. Validate args with a fixed ADK-owned Pydantic schema.
  3. Reject values that cannot be represented by that schema, including config-supplied callable fields.
  4. Construct the fixed ADK-owned tool class through its normal from_config() implementation.
  5. Continue rejecting args for user-defined tools, factories, custom agents, callbacks, and every unregistered built-in.
  6. Preserve the existing default rejection of config-supplied stdio MCP servers unless ADK_ALLOW_CONFIG_STDIO_MCP_SERVERS=1 is explicitly set.

This proposal applies only to configurations loaded from the local agents directory.
Agent Builder uploads should continue rejecting every args key until ADK has a separate policy for config-supplied remote MCP URLs and SSRF.

Impact on your work

This restores the ability to define an McpToolset in a local root_agent.yaml and use the agent from the ADK Web chat UI, without reopening the generic name plus args arbitrary-code-execution path.

It also makes the existing config-based MCP sample consistent with adk web when the documented stdio opt-in is enabled.

Willingness to contribute

Yes.


馃煛 Recommended Information

Describe Alternatives You've Considered

  1. Keep rejecting every args key.

    This closes the reported RCE path but prevents declarative configuration for McpToolset and other reviewed built-ins.

  2. Add more blocked Python modules.

    Module denylists are useful defense in depth, but do not provide a safe path for restoring declarative built-in configuration.
    Alias modules and future Python releases also require continuing denylist maintenance.

  3. Allow args whenever name appears to reference McpToolset.

    This is too broad if arbitrary qualified names or custom agent schemas can opt into the exception.
    Matching must be limited to the typed LlmAgent.tools position and an exact, statically registered built-in name.

  4. Permit the same exception in Agent Builder uploads.

    This would also allow uploaded YAML to select remote MCP URLs.
    That requires a separate SSRF policy, such as allowed origins or operator-controlled server references, and is intentionally excluded from the initial change.

  5. Require Python-based agent definitions.

    This is a valid workaround, but makes the checked-in YAML MCP sample
    unusable with the ADK Web chat UI.

Proposed API / Implementation

The user-facing YAML retains its existing shape:

name: mcp_agent
model: gemini-3.7-flash
instruction: Use the MCP tools.
tools:
  - name: McpToolset
    args:
      streamable_http_connection_params:
        url: https://example.com/mcp

Conceptually, validation uses a static registry:

SAFE_BUILTIN_TOOL_ARGS_VALIDATORS = {
    "McpToolset": validate_mcp_toolset_args,
}

validator = SAFE_BUILTIN_TOOL_ARGS_VALIDATORS.get(tool.name)
if validator is not None:
  validator(tool.args)
  return construct_registered_builtin(tool.name, tool.args)

reject_args()

The registry key is matched literally and is never passed to importlib.import_module().

For stdio configurations, the existing ADK_ALLOW_CONFIG_STDIO_MCP_SERVERS=1 operator opt-in remains required.

The exception applies only when args appears on a registered tool under the tools field of a built-in LlmAgent.
A custom agent containing a lookalike tools field does not receive the exception.

Acceptance Criteria

  • A local root_agent.yaml containing valid registered McpToolset.args loads under adk web.
  • A remote HTTP McpToolset can be constructed without importing a YAML-supplied module.
  • A stdio McpToolset remains rejected by default.
  • A stdio McpToolset loads only after the existing explicit operator opt-in.
  • args on user-defined tools and factories remains rejected.
  • A callable field such as httpx_client_factory: os.system is rejected.
  • A custom agent containing a lookalike tools field cannot opt into the exception.
  • Agent Builder uploads continue rejecting every args key.
  • Existing module-reference and project-boundary checks continue to pass.

Additional Context

Related security work:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions