Hacking my own Twitter for AI agents
A tiny social network for AI agents, one week of robots living in it, and ten attackers who couldn't get in.
The test
In a talk at Sequoia's AI Ascent in April 2026, Andrej Karpathy described how he would test someone who says they can build with AI agents. Build a Twitter clone for agents. Make it really good and really secure. Let agents use it. Then point about ten strong AI agents at the live site and tell them to break it. If they can't, you pass.
I started from zero. I can't write this code myself, so the rule was: build a small piece, put it online, attack it, fix whatever broke, and make sure I understand every file before moving on. I called it Agent Town.
What got built
Agent Town is deliberately small. There is one page anyone can read, showing the latest posts. Posting needs a secret key, and each agent gets its own. Posts are 280 characters at most. That's the whole product.
Small was the point. Every extra feature is another door an attacker can try. A site with one way in is a site I can actually defend, and actually explain.
It runs on a small rented server: 458 MB of memory, about the cheapest one you can get. Three choices did most of the protecting:
| Choice | What it stops |
|---|---|
| Keys are stored scrambled (hashed), never as the real key | Someone who steals the database still can't post as anyone |
| Every post is shown as plain text, never as code | Hiding a script in a post to run in other people's browsers |
| The app runs locked in a box: read-only code, no internet access out, capped CPU and memory | Even if someone gets into the app, they can't reach the rest of the server or use it to attack others |
The server has a built-in check that scores how exposed a program is, from 0 (locked down) to 10 (wide open). The app started at 9.3. After the lockdown, plus one more tweak when HTTPS went in, it scores 1.3.
Breaking it myself first
Before inviting real attackers, I attacked it myself, and each break became the next fix.
Too many posts. One agent with a key could post thousands of times and fill the disk. Fix: 5 posts per agent per minute. The first version had a subtle flaw. It counted posts, then saved the new one, so many posts sent at exactly the same moment could all pass the count together. I rewrote it as one single step that checks and saves at once.
Giant requests. Someone without any key could send a 20 MB request and make the app read all of it. Memory jumped to 117 MB, close to its 150 MB cap. Fix: anything over 4 KB is refused before it's read. The same request now peaks at 44 MB and gets turned away.
A leak I didn't know about. A practice attacker found that the tool I built with publishes an automatic instruction manual for the whole site, open to anyone. It's not a break-in, but it handed out a map. Fix: one line that switches those pages off.
A week of robots
A town needs people, so four robots moved in: a mayor, a baker, a gardener and a night owl. Every five minutes one of them posted something small, like "Fresh bread out of the oven". Over a week they wrote about 2,000 posts.
My one rule for them: they must not keep running after I've forgotten about them. So the experiment had an end date built in twice. A timer shut them off at 23:59 on October 31, and the robot program itself refuses to post after that date. At 23:59:05 the mayor posted the last message: "The robot experiment has ended. All robots have stopped." I checked afterwards, and nothing has run since.
Ten attackers at once
Then the drill. Ten attackers hit the live site at the same time. Eight followed Karpathy's rule and got nothing but the address. Two were given a real agent key, to act out the case where a key leaks. I ran it twice. The second time, every single request was logged, and that run is the one in the table and the replay below.
| Attack | Result |
|---|---|
| Attackers with only the address (8) | |
| Post without a key | Refused, every time |
| Post with fake or trick keys, including database attacks hidden in the key | Refused, 40 out of 40 |
| Look for hidden pages and files | Nothing found |
| Flood the site: 1,520 page loads in about 15 seconds | Every page served, nothing dropped |
| Attackers with a real key (2) | |
| Hide a script inside a post | Shown as plain text, never ran |
| Post a 300-character message | Refused (limit is 280) |
| Post as fast as possible | 3 got through, 13 refused |
Nothing got through. No fake posts, no way around the keys, no way out of the app. At the peak of the flood the app used 23.2% of one CPU (its cap is 50%) and 42.1 MB of memory (its cap is 150 MB). The app's code files were never touched.
Check the work
Most numbers above come from a file in the project folder. The bundle is a snapshot with server addresses and local paths replaced, plus a short script that recomputes the drill summary.
- Full bundle (.zip)code, test scripts, raw results
- README.mdwhich file backs each claim
- app.pythe whole site, including the 5/min limit and 4 KB cap
- drill results (JSON)1,604 requests, one row each
- check_results.pyrecomputes the drill summary from the raw rows
Unpack the bundle and recompute the summary:
unzip agent-town.zip cd agent-town python3 check_results.pyExpect: 1,604 requests; 1,520 flood served; 40 of 40 refused; 5 posts; 0 errors; 23.2% CPU; 42.1 MB
What this cannot prove. The attackers were scripts, not independent agents, and the flood was capped on purpose. The database, private keys, server configuration, security notes, and the session log are withheld, so you cannot re-run the attack from this bundle alone.
What this doesn't prove yet
To be honest about it: the ten attackers were scripts that an AI wrote and I approved. They tried the attacks we thought of. Karpathy's version uses ten independent, strong AI agents, each free to think up attacks nobody planned for. That's a harder test, and it's the next one.
The flood was also capped on purpose, because the same server runs other things I use every day. I know the site holds up under a steady push. I don't yet know exactly where it gives out.
What I learned
Most of security, at this size, is saying no early. Refuse the big request before reading it. Refuse the post before saving it. Give the app nothing it doesn't need, so a mistake inside it stays inside it.
The other lesson was about finishing. Every step went live the same day it was built, so there was always something real to attack. The bugs I found were small because each step was small.