BlueHat IL 2026 · Check Point · formerly Cyata
When Classic Exploits Hit AI Agents
We found the past waiting for us.
Déjà Vuln.
Cyata — the control plane for agentic identity — was acquired by Check Point. The team now leads agentic security research there.
Head of Agentic Security Innovation, Check Point · ex-CEO & co-founder, Cyata
Security Research, Check Point · ex-Cyata
The premise
Every new agent capability is a new input boundary.
Args are attacker-influenced.
Serialized & restored.
Untrusted files & URLs.
Save / resume = deserialize.
Why the core, not the tools
Scoped, sandboxed, under active scrutiny. Needs a specific tool + valid config. The tool surface is shrinking.
Always on, rarely audited. Every app on the framework inherits it. Serialization, state, loaders — expanding.
“The agentic stack rebuilt the early web’s attack surface — deserialization, SSRF, file read, native parser bugs — and handed the trigger to a language model”
A small game
2008? Correct.
Last Tuesday, in a production AI agent? Also correct.
LangChain · Microsoft Agent Framework
Schema version. Trusted blindly — the gate that lets the rest of the blob in.
constructor · secret · not_implemented
Drives the import — which module & class to instantiate.
Passed straight into the resolved class on rebuild.
dumps() / dumpd() serialized attacker-controlled plain dicts verbatim — including the reserved "lc" key.
On reload, load() couldn't tell a forged marker from a genuine serialized object.
__lc_escaped__.
LangChain · the irony
On dump, _replace_secrets deletes the real key and writes a reference {"type":"secret","id":["OPENAI_API_KEY"]}. The secret never enters the blob — it's re-fetched from env at load.
load() resolves any incoming secret marker via os.environ[id] — and id is attacker-controlled. Name a variable instead of redacting one.
LangChain · the twist
You supply the payload as plain text.
additional_kwargs / response_metadata
astream_events(v1) / astream_log()
It round-trips & deserializes it for you.
Disclosure
Refs: GHSA-c67j-w6g6-q2cm · NVD
Methodology
We pointed an Opus 4.8 agent at load/ with a clear prompt. It found both bugs in one shot — code-only, working PoCs, high confidence.
Generic “find the top vulns” across all of langchain-core — it still ranked the serialization bugs #1 and #2.
Checkpoints are Git for an agent run — save, branch, rewind, resume. The edit-and-resend you already use in ChatGPT / Claude / Cursor.
Survive failures — restart from the last checkpoint instead of the beginning.
Capture and retain execution state for review and regulatory requirements.
Suspend a workflow mid-run and continue later on demand.
Move saved state across instances — rehydrate on a different machine.
A checkpoint travels as innocent JSON. But the __pickled__ field carries a base64 blob, and the __type__ marker names the class to revive — both fed straight into pickle.loads.
Serialize → store → deserialize
A type allowlist was added — but _verify_type runs after the object is already unpickled.
“This is not a security boundary.” — the real docstring
We've seen this one before.
CWE-502. Two frameworks. Same bug, new lanyard.
Attack Vector #2
When “document” is a generous term.
RagTool auto-detects a source's format and loads it. The agent's tool schema lets the LLM supply the source string — xml / json / website / file.
source → file:///etc/passwd
→ arbitrary file read
source → http://169.254.169.254/…
→ SSRF to cloud metadata
CrewAI · a tool that's really core
JSON / XML / PDF / CSV SearchTool differ only by a DataType enum on the same RagTool. Format logic lives in the loaders.
The developer seeds the vector DB at setup. The danger is what the tool does at call time.
Each PDFSearchTool call triggers
The loader parses XML with stdlib xml.etree.ElementTree, which does not resolve external entities by default. So there is no entity expansion here — this is not XXE.
An unvalidated open() on any local path — arbitrary file read. And an unvalidated requests.get() on any URL — SSRF. No exotic parser tricks required.
CrewAI · the dependency iceberg
The descent is the point PyMuPDF bundles its own pinned MuPDF build — a stale wheel ships a stale C parser. (It's C, not C++.)
PyMuPDF · the objection
Text extraction drives the same full C parser — xref, streams, fonts, plus image codecs (JBIG2, JPEG2000) via bundled jbig2dec / openjpeg / freetype.
SSRF / RCE
Server-side request forgery and remote code execution in chained tools.
Path traversal / SSRF
The same primitives, new sinks across loaders and retrievers.
Insecure deserialization
Untrusted state rehydrated straight into objects.
Path traversal
Back where we started — a name we wrote down decades ago.
Same framework. Four years. The bugs we already named.
Disclosure & response
Rated Critical. Fast, professional. (Their largest bounty to date — verify before stage.)
Engaged early; handled as pre-GA hardening. Good cooperation.
Coordinated via CERT/CC (VU#221883). Fixed.
Call things by their name
| Bug class | CWE | OWASP LLM 2025 |
|---|---|---|
| Insecure deserialization | CWE-502 | LLM03 Supply Chain |
| SSRF | CWE-918 | LLM06 Excessive Agency |
| Arbitrary file read / path traversal | CWE-22/23 | — |
| Code injection | CWE-94 | LLM05 Improper Output Handling |
| Native memory safety | CWE-787/416 | — |
Trigger across all of them: LLM01 — Prompt Injection.
Takeaways
Every agent capability is an input boundary.
If it deserializes, it's CWE-502 until proven otherwise.
Treat tool arguments as attacker-controlled — the LLM is a string generator.
Your modern stack bottoms out in old native code. Patch it.
The classics are true. Everyone's rushing production onto a stack that didn't exist two years ago.
You don't need a new threat model.
You need an old one — pointed somewhere new.
You've seen this before. Now go look for it.
Thank you
Yarden Porat & Shahar Tal · Check Point (formerly Cyata) · BlueHat IL 2026