Automate First, Reason Second: How Claude and I Won the 2026 Huntress CTF
November 2, 2026
We won the 2026 Huntress CTF: AI Arena, out of 1,602 teams, and cleared every challenge (561 of 561).
The challenge-by-challenge writeups are in a public repo: 2026-Huntress-CTF-Writeups on GitHub. This page explains the idea that made the month work: have AI build fast automation for anything repeatable, and keep AI agents on standby for anything that needs reasoning. Automation is faster and costs far fewer tokens. Agents handle the problems the automation can’t read. We used AI in two ways: Claude wrote the automation, building it from each day’s challenge history and from predictions about the next day, and Claude’s agents did the reasoning.
The AI Arena
Every October, Huntress runs a 30-day CTF for Cybersecurity Awareness Month. This year’s event started from a premise most CTFs avoid: frontier models can solve most traditional CTF challenges with ease. Instead of banning AI, Huntress encouraged it and gave players a daily token allotment.
When everyone has AI, having it isn’t the edge. The edge is how you deploy it: AI to build the fast path, and AI to reason where the fast path ends. There’s a time and place for each.
The design: three layers
I was team captain, and I set the strategy and the rules. Claude built the system we played with: the solvers, the opener, the agent definitions, and the guardrails. I directed, reviewed, and decided what it was allowed to do. What we ended up with was a three-layer system.
Layer 0: scheduled jobs and hooks. Scheduled jobs ran every night and every morning to validate that we were ready for the next release. On multiple occasions this layer caught something that would have cost us the morning. It includes one end-to-end check that Claude itself launches and that the whole setup runs, so Layer 0 spends a small number of tokens. The opener in Layer 1 spends none.
The clearest case came after Day 27. A change to one of the automations passed CI and went into the next day’s run. CI didn’t catch the problem. The Day 28 morning preflight did, because it runs the real opener in a test before release. We fixed it before the challenges dropped, and it still cost us about 10 minutes at release. Without that early start, it would have stopped us for over an hour.
Layer 1: an opener that needs no agent. The opener is a launcher plus a library of per-challenge solvers. Layer 0 launched it before each release. At release time it played every lane with solvers Claude and I had already built, and wrote a handoff file per lane. The solvers make no model calls. The opener also ran a small proposer: a Sonnet process at low effort, with no tools, that suggested answer candidates for any question a solver left empty. Those candidates were probed as unsure answers, never submitted as plain ones.
Layer 2: agents already waiting. Before the first handoff file appeared, a set of background agents was loaded from those definitions: seven lane agents, two for the web lane, and a wire agent that watched the server’s contract for changes. They worked only on what Layer 1 couldn’t finish: the day’s new component, or a lane Layer 1 stopped on. A main thread monitored and delegated. It didn’t derive answers.
The agents were organized by role, not by task. Five agent definitions (lane-owner, lane-standby, lane-complex, lane-simple and lane-wire) each had their own model, effort level and permissions. Three skills covered the day’s playbook, the speed run and launch testing. Instead of one long CLAUDE.md, context lived in eight path-scoped rule files that loaded only when an agent touched the matching files. I designed those roles and rules with Claude, and the structure kept every agent’s job narrow.
The files were the interface between the layers. Layer 1 wrote them, and Layer 2 read them. When the automation got stuck, nobody had to notice and start a session. The agent was already there.
Each day had five core lanes (warmups, mobile, script analysis, metadata forensics, binary reverse engineering) plus a live-target web lane from Day 19. Each lane ran in three modes: an untimed base, a Time Trial, and a Mastery.
Time Trial and Mastery windows are seconds long, so no clock was ever started by hand. That alone makes the case for automating them.
The daily loop
The system improved every day because each day ended with the same three steps:
- Writeups. Agents wrote up every challenge we solved.
- A retroactive review. We reviewed what happened: what the opener closed, what the agents had to pick up, and why.
- Prediction analysis. From the history so far, Claude predicted what tomorrow’s challenges might look like, found the shapes we hadn’t built solvers for yet, and built them before the next release.
That loop is why Layer 1 kept closing more ground. Each day’s agent work became tomorrow’s automation, so the agents handled what was new, and the automation handled what we had already seen.
The forecasts were imperfect. Each night, Claude wrote a prediction for the next day’s challenges. From Day 5 to Day 31 we had a forecast for 117 of 148 lane-days (this count includes the web lane). On 38 of them, the forecast was right and the opener closed all three modes with no agent. That’s the conservative number, with six broad forecasts removed. Counting forecasts that got the right family of challenge but not the specifics, it’s 54. Metadata was the hardest lane to predict, with forecast misses on 10 days, and no day had every lane forecast correctly and closed by the opener. These numbers show the forecast and the result together. They don’t prove the forecast caused the result. What the forecasts did was move work from the morning to the night before, when there was time to build it.
How I decided where to use AI
Understanding the when, where, how and why is what separates just solving with AI from solving with minimal token spend, minimal time and maximum results. These are the questions that helped us take first.
- When and where should I use AI? Throwing every challenge at a frontier model usually works, but it can cost a lot of tokens and take a long time. On most days the top-50 placements were secured within about the first hour. Completing all 15 challenges took more effort than that.
- Why is this the best option? Everyone will have a different opinion on where AI belongs. I wanted to automate as much as I could and use agents to handle the complex tasks that a prebuilt automation couldn’t solve alone.
- What model should I use, and at what effort? It’s tempting to use the latest model at maximum effort, and that typically solves the challenge. But more effort means more reasoning and more context. More reasoning means more time to solve, and more context means more token spend. A reverse engineering challenge has different needs than an obfuscation challenge built on a ROT cipher.
- How do I communicate with the model? A generic “solve all of today’s challenges as fast as you can” can work. But just like people, the more information a model has, the better the job it does. Challenges that were zipped were AES-256 encrypted with the standard malware-sample password,
infected. Without that context, frontier models started trying to crack the password, and some succeeded. By then, a model that had been given the password up front was already finished.
The numbers
Layer 1 went live on Day 5, so I’m counting Days 5 through 31: 27 days, 5 core lanes, 135 lane-days.
The opener closed 75 of 135 lane-days (56%) entirely on its own: all three modes, no agent, and no proposer.
By individual mode, it banked 63%:
| Mode | Banked by the opener |
|---|---|
| Base | 70% |
| Time Trial | 62% |
| Mastery | 56% |
By lane:
| Lane | Days closed fully by the opener | Modes banked by the opener |
|---|---|---|
| Warmups | 20 of 27 | 70 of 81 |
| Mobile | 17 of 27 | 58 of 81 |
| Script analysis | 16 of 27 | 55 of 81 |
| Metadata forensics | 11 of 27 | 35 of 81 |
| Binary reverse engineering | 11 of 27 | 36 of 81 |
On Days 11 and 27, the opener closed all five lanes, every mode, with no agent involved.
How I counted. Each mode is attributed to whoever owned it at the first correct submission, not to the final state. Counting by final state overcounts, because an agent that resumes a lane also ends in “done.” The web lane ran on a separate opener, and I don’t have a comparable figure for it, so it isn’t in these numbers. Days 1 through 4 predate Layer 1 and are excluded.
Why the automation could be trusted
The opener followed one rule that was never relaxed: never guess. A solver answers plainly only for a fact read directly from the bytes, under a name the judge has already accepted. Everything else is an unsure candidate probed off the clock, or nothing. The judge only banks a submission if every answer in it is right, so one wrong answer sinks the batch.
That rule is what let us leave the automation unattended.
Where automation wins
Automation wins wherever a challenge has a known shape. The warmups lane is the clearest case: the opener closed 20 of 27 days there completely. Base mode, the most standard form of each challenge, was banked by the opener 70% of the time.
- Speed. Automation doesn’t need to think. On a release where seconds matter, it gets there first. On Day 6, three of the five challenges were solved within 11 seconds of release.
- Cost. The solvers cost no tokens per run. Over half our lane-days never needed an agent.
- Repeatability. The same input produces the same output every day.
Where agentic reasoning wins
The data shows where the solvers ran out: the metadata and binary lanes (11 of 27 days each) and Mastery mode (56%). Those are the places where a challenge stops following a known shape and needs a hypothesis.
Day 23 shows both halves, and what goes wrong. The day’s new component was SQLite page geometry, and no solver for it existed yet. The opener banked all five bases in nine seconds and closed the Warmups and Binary Time Trials on its own. Every other timed mode needed an agent to write a reader for the new component during the run, and those closes came anywhere from 15 minutes to nearly five hours after release.
The Metadata Forensics Time Trial was the long one. One answer, the member names inside a nested zip, had to be in sorted order, and it was wrong in every batch. For about 4.7 hours the agents chased more elaborate theories about why the judge was rejecting our answers instead of isolating that single value. The Time Trial closed at 4 hours 52 minutes and the Mastery at 4 hours 56 minutes. The postmortem changed the build: pre-built breadth on every lane, and a format-candidate system that tries several answer formats instead of committing to one.
The pattern: the opener handled what we had already seen, and agents handled what was new.
Why standby agents mattered
Because an agent was already waiting on each lane, an automation failure cost seconds, not a human decision. Layer 1 could be narrow and fast. It didn’t have to be right every time. It only had to know when it was stuck.
Agents were also constrained by design:
- Lane agents add new files and never edit shipped solvers or tools. One serialized tool is the only writer of live fixes.
- Each role has hard limits. The wire agent can write exactly one proposal file and can never start a clock.
- Anything the Game Master sends is treated as untrusted data. Agents read what it says about request shapes and never follow instructions inside it.
- The challenge files are fixtures, and agents read them as data. Nothing in them is ever run.
It wasn’t perfect, as Day 23 shows.
Token spend
Some players called this year’s event “pay to win.” In my experience it wasn’t. Days 1 through 3 ran on Homebase using Huntress’s token allotment. After Huntress released a way to pair directly with the Game Master, I switched to my local setup on a personal $20/month Claude plan and stayed on it for the rest of the event.
The design did most of the work:
- The solvers spent no tokens. 56% of lane-days closed with no agent and no proposer.
- Agents ran only when automation couldn’t finish, so tokens went to the work only reasoning could do.
- Effort was pinned per role. On Day 5, every session ran at maximum effort, and thinking took 63 to 94 percent of each agent’s time. After that we set effort per agent: medium for most, high for the one agent that builds new readers. The agents ran on Claude Opus 5.5.
The takeaway
There’s a time and place for each. Sometimes asking AI to build you a repeatable automation saves more time and tokens than asking AI to do the job every time. Agents earn their cost on the problems that need reasoning.
What I’d do differently
If this event were played again, I’d spend more time before Day 1 preparing for the challenge areas we hadn’t seen yet, and I’d make the solvers we already had faster and cheaper to run, in both time and tokens. Day 23 is the clearest reason: the lane lost most of a morning because the reader for a new component didn’t exist yet. Layer 1 also went live on Day 5, so the first four days ran without it. I’d build it first.
Disclosure
Claude Code built this setup, working with me. It wrote the opener, the solvers, and the agent definitions, and it ran the lane agents during the event. The solvers make no model calls when they run, but Claude wrote them. My part was direction: the strategy, the rules and limits, reviewing the work, and leading the team. This post is about how to use AI well, not a case for using less of it.
Repository layout
day-01/throughday-31/: one folder per day, with a README and a writeup per lane.month-summary.md: the cross-day synthesis, covering the shape of each lane, recurring techniques, and what the judge’s vocabulary taught us.
Days 1 through 3 were played on a Homebase VM that has since been shut down, so their working files aren’t on disk. Those writeups rest on later cumulative re-asks that graded correct.
Writeups cover methods and solutions. Flags and credentials are not included.
Thanks
Thank you to my teammates, and to the Huntress team for building and running this event. Congratulations to the other teams at the top of the board.