Skip to content

Security: Unsafe pickle.loads() in v0 schema runtime path and migration tool #5634

@daridor9

Description

@daridor9

Summary

Two code paths in ADK Python call pickle.loads() on data read from the session database with no type restriction, integrity check, or opt-in gate. A single poisoned row in events.actions (BLOB column) achieves arbitrary code execution in any ADK process that reads the session — including the official migration tool.

Affected Code

Sink 1 — Runtime read path
src/google/adk/sessions/schemas/v0.py:97DynamicPickleType.process_result_value()

if dialect.name in ("spanner+spanner", "mysql"):
    return pickle.loads(value)   # no guards

SQLite falls through to PickleType.result_processor() which calls self.pickler.loads(value) — same effect. All three supported dialects (SQLite, MySQL, Cloud Spanner) are affected.

Sink 2 — Migration path
src/google/adk/sessions/migration/migrate_from_sqlalchemy_pickle.py:62

if isinstance(actions_val, bytes):
    actions = pickle.loads(actions_val)   # no guards

This runs on every existing row during the recommended v0→v1 migration (adk migrate session). The vendor remediation path is itself an exploit trigger on a poisoned database.

Affected Versions

All google-adk releases through current (v1.31.1).

  • < 1.22.0 — v0 (pickle) was the only schema
  • 1.22.0+ — v1 (JSON) is the default for new databases; existing v0 databases continue using the pickle path (docs confirm)

The pickle code path is not deprecated, not warning-gated, and not behind a feature flag.

Impact

  • RCE in any ADK process reading events from a v0-schema database
  • Blast radius: one poisoned row compromises every reader until the row is manually deleted
  • Persistence: payload survives process restarts, container rebuilds, and ADK version upgrades
  • Cross-authority: write-authority (e.g., dev environment, adjacent service with DB access) and execution-authority (production ADK process) can be distinct principals — the database is treated as a trust boundary that pickle collapses
  • Migration trap: running adk migrate session on a poisoned DB executes every planted payload under the production service account

Suggested Remediation

  1. Fail-closed v0 reads (preferred): refuse to load v0-schema databases unless explicitly opted in via ADK_ALLOW_UNSAFE_V0_PICKLE=1. Forces operators to acknowledge risk rather than inheriting it silently.
  2. RestrictedUnpickler: if v0 must remain loadable, allowlist only EventActions and known field types. Apply to both Sink 1 and Sink 2.
  3. HMAC on serialized data before storage as defense-in-depth.
  4. Long-term: deprecate v0, remove v0.py.

CVSS 3.1

9.9 Critical — AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

(S:U fallback: 8.8 High)

Disclosure

  • Reported to Google VRP: 2026-04-24
  • GHSA filed
  • 90-day coordinated disclosure deadline: 2026-07-23
  • This issue is filed per Google VRP guidance to work with maintainers via public issues

Discovered by SPR{K}3 Security Research (Dan Aridor — @daridor9)

Metadata

Metadata

Labels

request clarification[Status] The maintainer need clarification or more information from the authorservices[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions