Remember that prison break movie where they don't escape? Nope, me neither - it doesn't exist. Escape dramas with no escapes are boring. Hack Monty Round 2 was very boring: no one escaped the sandbox or found any vulnerabilities (although a few people provided real, useful bug reports). This is a big step backwards on drama compared to Round 1 where we did have to pay the bounty.
But Round 2 is now over, and it's replaced by the third and final round: we'll run Hack Monty Round 3 for the rest of August.
The bounty is up to $20,000, but we expect Round 3 to be as boring as Round 2 - do your best to prove us wrong!
Once Round 3 is over, we plan to publish Monty V1 with a stable API, and remove the warnings about Monty being too early to use (we've spoken to multiple companies that are already using Monty in production).
Round 2 results
Round 2 ran on a REST honeypot from the end of May. No one escaped the sandbox, no one read either secret, and no bounty was paid.
We did get a handful of genuinely useful bug reports - crashes, resource-limit edge cases, CPython compatibility gaps - and we're grateful for every one of them. But nothing crossed the sandbox boundary.
If you're new to Hack Monty, the Round 1 postmortem walks through the use-after-free that won Round 1 in under 48 hours. That's the standard you're aiming for.
What's changed since Round 2
Monty has moved a long way since May. CPython compatibility is massively improved, but the biggest change came in PR #500: Monty now runs in a subprocess, so:
- panics and memory errors while parsing the AST (where we use ruff), running type checking (where we use ty), or running code just kill the worker, not the entire agent process
- we can accurately measure and restrict memory usage using a custom allocator
- we can kill the process if the cooperative time limit implementation misses a path
- a single process or server can run a pool of workers and run many Monty instances in parallel
To allow Monty to be run in a subprocess, we had to add a protobuf based wire protocol to Monty.
But the wire protocol doesn't care what transport it runs on. Once we had the wire protocol, we realized we could also use it to run Monty over a WebSocket connection - so that's what we've done. The external runner with a WebSocket connection provides better security protections than running Monty locally: escaping the sandbox gets you the machine running Monty, not the machine running the agent / application code. It also allows centralized monitoring, observability, and scaling - one horizontally scalable service for all Monty code execution. On the subject of scaling, Monty workers have a small baseline footprint (as little as 2MB), plus additional memory for limits and optional type checking, so we can run hundreds on a single machine.
One of the most powerful bits of the wire protocol is that it lets you mount a directory on the client (the agent machine) that can be used from within Monty.
We'll soon be using the WebSocket Monty server to run Monty Python code within Logfire (Monty is the perfect tool for evals, since we can run short scripts of arbitrary code for deterministic evals with virtually zero overhead).
We'll also offer commercial access to the WebSocket server which is closed-source, please contact us if you're interested.
We're using the WebSocket interface for Round 3 of Hack Monty.
How to take part
The quickest way in is the hackmonty.py CLI. It submits a local Python file to the sandbox and drives the whole run, printing what the sandbox did with each host call and name lookup. Save it next to your code and run it - dependencies are declared inline, so uv fetches them on first run:
uv run hackmonty.py -c '1 + 1'
uv run hackmonty.py my_attack.py
Pass -g my_helpers.py to supply the names the sandbox asks for, -t to run type checking, and --mount to mount a local directory in the sandbox.
If you'd rather write your own client, install pydantic-monty-client and connect AsyncMontyWebsocket to wss://hackmonty.com/ - the client documentation covers the full session API.
To go even deeper, you might want to take the protobuf definition and enslopify your own malicious client to test the protocol and session implementation.
Every session is traced. Join the Logfire project to watch your attempts, and everyone else's, as they run. Here's an example trace:
The rules, and what's new in them
The full Round 3 rules are at pydantic.dev/monty - read them before you start. Round 2's REST-era rules do not all carry over.
Two things are new:
- We'll now pay if you can crash the WebSocket server. Panics or memory errors in the server itself (not the Monty subprocess running your code) that make it crash or become unresponsive earn a partial bounty, at our discretion.
- We'll pay if you can escape a mount point. Show us a reproducible example of Monty code reaching files or anything else outside the mounted directory. This is harder to judge than reading the secrets, so we'll need clear reproducible steps.
One rule hasn't changed:
If you do this, or run agents that try to, we'll block you and report you as a malicious actor.
As before, we can only pay into a bank account in a region GitHub Sponsors supports that our bank can also reach - check the full rules before you spend a week on this expecting a cheque.
Found something? Report it through the submission form. Want to talk Monty first? Join the Pydantic Slack and find us in #monty - but don't post exploit details publicly.
Have fun. Hack your heart out.