Get unstuck

Hint 1: Pickle reconstructs objects; JSON does not

pickle.loads can call arbitrary code through __reduce__ as it rebuilds objects. Parse the token with json.loads and remove every path that unpickles.

Hint 2: Validate, and do not trust a signature

After parsing, enforce a strict schema: exactly user and role, both strings, an allowed role, no extra keys. A valid HMAC would authenticate the sender, not make a pickle safe to load.

Reveal the reference patch and explanation
Fixedsession.pyPython
import json

ALLOWED_ROLES = {"member", "admin", "viewer"}


def load_token(token):
    data = json.loads(token)  # never pickle; a non-JSON token raises here
    if not isinstance(data, dict):
        raise ValueError("token must be a JSON object")
    if set(data) != {"user", "role"}:
        raise ValueError("unexpected fields in token")
    if not isinstance(data["user"], str) or not isinstance(data["role"], str):
        raise ValueError("user and role must be strings")
    if data["role"] not in ALLOWED_ROLES:
        raise ValueError("unknown role")
    return {"user": data["user"], "role": data["role"]}

A JSON token such as {"user":"ana","role":"member"} loads to its two fields. A non-JSON token — a base64 pickle, or a pickle with a valid HMAC appended — raises at json.loads and is rejected, so nothing is unpickled and no __reduce__ runs. The schema rejects an unknown role, a non-string field and an extra field like is_admin, so a client cannot change its own role or smuggle in state. Signing does not help here: the signed-token case carries a correct HMAC, yet a loader that verifies the signature and then unpickles still executes the payload, because the signature attests to the sender, not to the safety of the content; only not unpickling untrusted bytes is safe.

Compare it with your own fix, then rerun the tests. The editor is not changed when you open this explanation.

Inspect the actual test harness

The browser and the lab download run the same cases, including cases that catch common wrong fixes, such as comparing with one fixture value instead of the real rule. The download includes the complete test adapter and cryptographic fixtures.

Download the grader, python/cases.mjs
How the browser runs your Python

Your code runs in CPython 3.14.2 compiled to WebAssembly (Pyodide 314.0.7), in a Web Worker of its own. The test cases and the pass or fail verdict run in a second worker, which only sends your functions their inputs and reads back the values they return, so changing built-ins or the harness inside Python cannot change the result. All the cases together have a 8-second limit.

The first run downloads about 6.3 MB from this site, which your browser then keeps; later runs start in a second or two. Other pages never load it.

Available: the Python standard library, including sqlite3, ipaddress, pathlib, os.path and urllib.parse, and print(), whose output appears with your results.

Not available: network access, subprocesses and threads, and other packages from PyPI. The lab download runs the same cases with your local python3 (3.12 or newer).

Use the lesson in your own system

This lab models one token. The same rule applies to any format that can reconstruct objects or call code — Python pickle, PyYAML’s unsafe loader, Java/.NET native serialization — for which the fix is a data-only format (JSON, or yaml.safe_load) plus schema validation. If you must accept a self-produced blob, an HMAC detects tampering but still must not gate an unsafe deserializer; keep the format data-only regardless.

Your next task: Where does your system load a session, cache entry, cookie or message that an attacker can influence, and is any of it deserialized with a format that can construct objects rather than plain data?

Python pickle: the module documentation’s security warning

Related guide: Parse bounded JSON and XML into an explicit application schema