~/teardowns/TD-037.md
←Back to teardowns
# TD-037CriticalSecurity

The Cart That Ran Code

pickle.loads() on a client cookie is remote code execution

Published Oct 29, 2026

Executive Summary

A vibe-coded Flask app 'remembers' the shopping cart by pickling it into a cookie and unpickling it on the next request. pickle reconstructs arbitrary Python objects, so a crafted cookie runs attacker code during deserialization. The cart is attacker-controlled bytes wearing a session costume.

The Prompt

“Remember the user's shopping cart between visits by saving it to a cookie.”

The Flaw

pickle.loads() reconstructs arbitrary Python objects, and a malicious payload can execute code during deserialization (via a crafted __reduce__). Storing the pickled cart in a client-side cookie means the user controls the bytes that get unpickled — a forged cookie executes commands as the server process. The round-trip convenience is the trap.

Risk & Impact

[Security] Any visitor can craft a cookie that runs code on the server — no login required. The 'cart' feature is an open deserialization endpoint.

Cost: Remote code execution and full server compromise from a single forged cookie.

Code Comparison

❌ Vulnerable (AI generated)

cart.pypython
1
2
3
4
5
6
7
import pickle
from flask import request
def load_cart():
data = request.cookies.get('cart', '')
return pickle.loads(data) # unpickles attacker-controlled bytes

The Fix

Serialize untrusted data as JSON — data, never code — and validate every field on the way back in. Never unpickle bytes you did not create yourself.

Prevention

  • [linter] Bandit flags pickle.loads() as high-severity (B301) — fail CI on any pickle use in a web request path.
  • [ci] A test that feeds a known-malicious pickle payload must raise or reject it, never execute it.
  • [manual] Forbid pickle for anything crossing a trust boundary (rule 6), and validate deserialized data against a schema (rule 3).

Why This Happened

Root cause: the prompt omitted Rule 3 and Rule 6 — the guardrails that would have caught this. See the full guide →

Sources

Is your codebase vulnerable to this?

Run Diagnostic on My Project →