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

needs review[Status] The PR/issue is awaiting review from the maintainerservices[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions