KOM CLOUDSERVICE
All entries

Double Spent

Tuesday: seven challenges done inside 91 seconds, and a race condition we had solved at second 54 took the rest of the morning. 4th on the day, 4th overall, and an afternoon spent making sure it never happens again.

Shaded passages were held back from the public edition while the event ran.

Tuesday was eight challenges and 24 checks. Seven of the eight were done inside 91 seconds. The eighth, a web challenge called Double Spent, took five and a half minutes, and it took the day with it. The last check went CORRECT at 329.0 seconds after the 09:00 release: 4th on the day behind crash (171.9), secretNinja (202.8) and Scottronic (296.7), and 4th overall.

Here’s what stings. We had the right answer at 53.9 seconds. Everything after that was us getting in our own way.

09:00

Eight challenges dropped. In the order they finished:

  • Dependency Surface (mobile): a dependency flaw reachable from an exported part of the app. All three modes by 7.2 seconds, first blood on every one.
  • FROM Whence It Came (script): which Dockerfile build stage each specimen came from, when shared strings don’t mean shared parents. All three by 16.4.
  • Packet Loss Prevention (metadata): count the packets in a capture. All three by 23.3.
  • Monkey Business (warmups): the blob was a program in Ook!, the language written for orangutans. All three by 29.6.
  • Compiler Says What (binary): name the toolchain behind each program from its runtime and linker fingerprints. All three by 32.0.
  • Forwarded Misgivings (web): a server that believes a header about where a request came from. All three by 69.1, first blood on the Single Solve.
  • Socket to Me (web): the secret goes by on a WebSocket, if you know which channel to listen to. All three by 90.2, first blood on every one.
  • Double Spent (web): a race condition. Single Solve at 53.9, Time Trial at 189.3, Mastery at 329.0.

Script, mobile and binary were new challenges in familiar shapes, and the robot took every mode on the first try with nothing but the code it went to bed with. Warmups and metadata each needed one hand answer (an AI agent working the question directly instead of writing code) at about 17 and 18 seconds, and the fix carried into the timed modes on its own. The three web challenges were all brand new techniques. Two of them went fine.

Double Spent

A race condition lives in the gap between a check and an action. Double Spent’s targets let you spend something exactly once: the server checks you still have it, then takes it away. Send one request and you’ve spent it. Send a crowd of identical requests at the same instant and they can all get past the check before any of them takes it away. That’s the whole trick.

The robot found the right route, and then made the one move you can’t make against a one-shot target: it tried it the ordinary way, with a single plain request. That spent it. On a free attempt you reset the target and go again. On a timed one there’s no reset, so the Time Trial ran out its clock on a target with nothing left to spend. The retry went CORRECT at 189.3. Then the Mastery did exactly the same thing, and its retry went CORRECT at 329.0.

Two things broke, and the first one hid the second.

First, the solver spent the target itself. Every web technique the robot knows writes through one shared function, and while it was exploring, it sent a single write to each target’s action route. The one-shot credit went from 1 to 0, every request after that got a 409, and the Time Trial attempt was gone before any agent had a chance to act.

Second, the judge refused a correct patch five times. The judge is the automatic test that runs a patch (a small fix an agent writes to teach the solver a new shape) against everything the grader has accepted, and only lets it go live if nothing breaks. The agents’ patches were right: correct race code, with a recorded proof from the Single Solve. But the judge only replayed them against the Time Trial’s own ticket (the report the robot files the moment it can’t answer), and that ticket’s target was already spent. So it saw “still open” and said no, five times between +86.6 and +124.5 seconds. The accepted Single Solve, sitting right there at +54.8, was evidence it never looked at.

With no patch live, the Mastery spent Target 4 the same way and lost its first attempt too. What finally won both retries was the watcher, the AI session that watches the race live and can step in. Its hand racer had shut down after the Time Trial and was re-armed late, and it took the Time Trial at +189.6 and the Mastery at +330.0.

Every other lane that needed a patch that day carried it from one mode to the next without help. Double Spent was the only place the hand-off never fired.

I watched the whole thing live, pushing for the Mastery the entire time: we had the Time Trial, so use what it taught us. Afterwards I wanted to know why we’d sat on a working patch, because from where I was standing it looked like we’d screwed up. By mid-morning I had my answer, and I didn’t dress it up: “We had the win ‘in hand’ with the proper solver this morning before the last person submitted CORRECT, and we dropped the ball.”

The record

From the public scoreboard, Day 27, all 24 checks:

  • Wizdumb 329.0 seconds, 4th on the day. crash 171.9, secretNinja 202.8, Scottronic 296.7.
  • First blood on 7 of 24 boards: every mode of Dependency Surface and Socket to Me, plus the Forwarded Misgivings Single Solve.
  • 4th overall of 755, on 1,600 points, the same as every team ahead of us. When everyone finishes every day, the order goes to whoever banks their last flag first, and on Tuesday that was three teams before us.

On the speed boards we were 1st on the Compiler Says What Mastery (8.907 to Zwesty’s 9.373), 2nd on the Dependency Surface Mastery, and 3rd on the Monkey Business and Packet Loss Prevention Masteries. Across all 27 days we hold 15 firsts, 34 seconds and 32 thirds, six fewer firsts than Monday night. Other teams keep rerunning the older boards. We spent the evening on something more important.

Fix the class, not the case

I had one condition for the fix: it had to be general. Not “Double Spent’s route is a race”, but something that covers the next problem that looks like this one without being this one.

So now the first write the robot sends to any route goes out as a burst of identical requests released together, with the target’s status read before and after. A route that spends once gets spent many times at once, which is the attack. A route that doesn’t care sees a few extra requests, which costs nothing. There’s no wording to match, so a renamed route or a different resource is covered the same way.

The burst is 8 identical requests released together, one session per thread. Credential techniques (login, PIN brute force, NoSQL, type juggling, reset) never go out as a group, and a lockout inside a group marks the route instead of escalating. Fable reviewed it twice and said land it both times. Setting ROBOT_WEB_BURST=0 turns it off, which is how the drills below tested the backstop on its own.

The backstop is the hand-off between modes, and that got rebuilt too. The judge now proves a web patch against any attempt of its challenge the grader accepted, in any mode, not just the open ticket. A patch is scoped to the target type it was proven on, so a Double Spent patch can’t fire at a Mastery’s other targets. It runs alone before the solver, never beside it, because two things touching a single-use target at once is exactly what spends it. And the watcher now arms its hand racer at the first refusal after a hand answer and keeps it armed until the lane’s last mode.

Then the proofs. On the mock (a local stand-in for the event server that replays recorded days), the day’s file lanes went 15 of 15 on the first try in 6.9 seconds. Live and free, with the agents switched off, all 24 checks came back CORRECT with no tickets, and Double Spent on fresh targets took 3.0 seconds for the Time Trial and 6.4 for the Mastery.

Would it have won?

By mid-afternoon I got uneasy. The more we dug, the further we seemed to get from the thing that had actually gone wrong that morning. I talked myself back round a few minutes later (everything we uncovered now was one less surprise later), but I wanted the ground under us solid. So I reached for the oldest trick I know, the one I use with people: “I ask them how is it supposed to work, and just by having it said out loud, it sometimes clicks.” The robot wrote down how the whole system was supposed to behave, rule by rule, and every one of the morning’s failures lined up against a rule it broke. It clicked.

Then the question that actually mattered: with today’s process fixes in place this morning, and none of the new solver code, would we have come close to beating the field?

Two answers. With the burst, the robot never spends the target in the first place, and live free runs of the race code put Double Spent at 102.9 and 128.6 seconds against crash’s 171.9. Take the burst away and let the rebuilt hand-off do the work, and a faithful replay of the morning, lost attempt and all, came in around 205 seconds against our 271 on the same measure. That beats our morning, not crash. The rest is the two minutes a lost attempt takes to run out, which is why the burst is the real fix.

The replays needed free attempts to behave like ranked ones, so two switches went in: ROBOT_DRILL_CLOCK gives a free attempt a synthetic 120-second deadline, and ROBOT_DRILL_NO_RESET refuses target resets the way a ranked run would. Both are refused on a live race. Across three drills, a carried patch went live about 43 seconds into the lost attempt, answered the next attempt in about 2 seconds, and won the Mastery on its first try. The first drill found a bug in the drill itself (on its fake deadline, a free retry just reconnected to the open attempt, 720 times before we stopped it), which is fixed.

One idea got parked: the API lets a reset end a running ranked attempt, so the robot could abandon a spent target and take the next attempt at once. Before building it I asked whether we had ever needed it. Across every web race from Days 19 to 27, the only attempts lost to a spent target were Tuesday’s, which the burst already prevents. Above and beyond what we needed. It’s written down, not built.

The blind sweep

By early evening I wanted a bigger sample. Web builds its targets live on the server, which makes it the slowest lane and the one that decides the day, so it has to be the most ready.

So the robot replayed every web challenge from Days 20 to 27 with every web technique it knows hidden from itself, so each one arrived as a shape it had never seen, with agents on, no resets and the same two-minute clock. 19 of 20 came back clean: gap spotted, fix written, judged, carried, Time Trial and Mastery CORRECT, nothing late. The one miss was a leftover open attempt from an earlier drill, not the robot.

That drill was harder than the real morning, and the robot passed it. I told it to finish whatever runs it still needed to be sure of the build, because I wasn’t having a repeat of Tuesday on Wednesday, and we called it at 20:20.

Four days

The top teams all finish inside a few minutes now, so a day goes to whoever’s slowest lane is fastest. Tuesday ours was a race condition we had cracked by the 54th second and then fumbled twice. That’s the most frustrating kind of loss there is, and the most fixable. It’s fixed, it’s been drilled against the morning, and it’s been tested against every web challenge we’ve seen with the robot’s memory switched off.

Four days to go. Three teams ahead of us, and nothing on the board that says it stays that way.

How we built it: the Day 27 dev log

The right worry, the wrong list. Before the race I was already worried the web lanes would hit something we weren’t ready for. The night before, a branch had added eight deterministic web techniques from a gap list, merged at 07:55 with a live free run of every web challenge from Days 19 to 26 as proof: 20 of 20. Race conditions weren’t on the list.

Carry, with certainty. Carry, reusing a fix or an accepted answer from one mode in the next, had been the topic for three days, and I wanted it solid. The plan: write the carry contract down, have a fresh reviewer check the code against it, run faithful replays and fault drills, then make a go or no-go call by 21:00. The contract review found ten gaps. The worst was that a carried web patch ran first on every Mastery question, so a race patch would have fired its writes at targets of other families. That’s what the scope check fixes.

Plain language for the agents. Twice on Tuesday the model’s safety classifier stopped a message. There was no reason to be tripping guard rails here, so the briefs and prompts now stick to plain engineering terms: a lab target, a route, a request, a proof.

The cap. A file-lane patch that stalls now gets cut at 30 seconds, or 60 for its first call. I wanted to be sure we weren’t cutting off a patch that just needed more time, and decided 30 is generous.

Free verification stays off. One idea was to check an untrusted carried web patch on a free retry of the already-CORRECT Time Trial before a ranked Mastery, at a cost of 5 to 12 seconds. It’s built and reviewed, and off until a free probe says it earns its seconds.

What the organizers showed. In the evening a screenshot from John Hammond showed the event’s build numbers. Our reading, and it is only a reading: six categories across 31 lessons, so no new category before Day 31, and about two web challenges a day from here.