From the Kitchen
There is a job in every kitchen that nobody volunteers for: the deep clean. Pulling the equipment away from the walls. Getting underneath the things that have not moved in months.
You do it because of what you find. Not the mess you knew about. The mess you did not.
In August I did a deep clean on CheddaBoards. This issue is what I found under the equipment — a door I thought was locked, a fix that undid itself every time I applied it, and, in the middle of all that, the best month this project has ever had.
Last issue I said the long version was coming. This is the long version.
The Door That Was Not Locked
I was doing a security pass on the backend. Reading my own code the way an attacker would, which I both recommend and do not recommend, because you will find something.
I found something.
One of the login functions trusted its caller. It accepted an email address and handed back a fully privileged session for the account attached to it — without verifying that the request had come through the layer that actually checks who you are. In theory, someone who knew an account's email and knew how to talk to the canister directly could have walked in as that account.
Nobody did, as far as I can tell. But that is not the defence. The defence is the fix.
The frustrating part: the codebase already had the correct pattern, sitting right next door. Another function was properly gated so that only the verification layer could call it. The vulnerable one just was not using it. An earlier design had been half-replaced along the way, and the old trust assumptions had quietly survived the renovation.
So the fix was one line, more or less. Gate the function. Only the verifier gets in. And once that door was locked, I went along the corridor trying the other handles — every one I found with the same weakness got the same lock.
Except locking that first door was not done when I thought it was done. And that is where the real story starts.
The Fix That Kept Un-Fixing Itself
I deployed the fix. I tested it. The gate was not there.
I checked the source. The fix was in the source. I built again, deployed again, tested again. The gate was not there.
I went deeper and inspected the compiled binary — the actual WebAssembly the network was running. The new value was in the binary. I could read it, sitting right there in the compiled output. And the running canister was still behaving as if it had the old one.
Two things that cannot both be true, both true. The code says one thing. The runtime does another. This is the kind of bug that makes you quietly question whether you understand anything at all.
Here is what was actually happening. If you write Motoko, this next bit might save you a night of your life.
The CheddaBoards canister is declared as a persistent actor. That means every top-level variable is automatically stable — its value survives upgrades. Normally that is exactly what you want. It is the reason your leaderboard data lives through a deploy instead of being wiped by one.
But the gate's expected identity was a top-level variable. So its value had been persisted the first time it was ever set — back when it held an old placeholder. And on every upgrade since, the network faithfully restored that saved value, silently overwriting whatever the new source code said it should be.
The literal in my source file was a suggestion. The stable memory was the law.
I kept changing the code. The value never changed.
The fix for the fix: reassign the value inside the canister's post-upgrade hook — the function that runs after every deploy — so the source value forcibly overwrites whatever was restored from stable memory. Build. Deploy. Verify against the live system, not the deploy logs. Gate live. Door locked.
Two lessons, earned somewhere around 4am:
One. In a persistent actor, changing a literal changes nothing. If a top-level value must track your source code, set it in the post-upgrade hook. Otherwise it is frozen at whatever it was the first time it persisted.
Two. Never trust the deploy message. Hash the binary you built. Compare it against the hash the network says it is running. The tooling telling you success is not the same thing as the network running your code.
Persistence, Round Two
I thought I was done with persistence for the month. Persistence disagreed.
Late in August I set up a second development machine and shipped a backend fix from it for the first time. The deploy was rejected. Not failed — refused. The newer compiler on the new machine wanted to move the canister to a different persistence model under the hood. That migration is one-way. The network looked at an irreversible change arriving unannounced and said no.
Two weeks earlier, persistence had silently kept a value I wanted changed. Now the same stubbornness flatly refused a change I did not know I was making. The same force, pointing both directions. The system that will not forget is also the system that will not let you casually rewrite what remembering means — and this time it was protecting me.
The old code kept running the whole time. I took a snapshot anyway, pinned the persistence mode in the build config, and the deploy went through clean.
Lesson three, to sit alongside the two from 4am: know which persistence model you are building for, especially the day you change machines. Your compiler's defaults are not your canister's history.
The Week a Stranger Showed Up
While all of this was happening, the numbers kept moving on their own.
In the middle of the month I found a game I had never heard of, by a developer I have never spoken to, live and taking scores on the open web.
I did not find it through analytics. I found its privacy policy.
Because the developer had written CheddaBoards into it. Properly. What data the leaderboard stores. What the anonymous identifier does and does not know about you. How a player can ask for their entries to be removed. In two languages.
I cannot fully explain what that felt like. Somebody I have never met read the documentation, integrated the SDK, shipped a game, and then described my infrastructure to their players — accurately — as a normal, boring, dependable part of how their game works.
That is the whole goal. Infrastructure is working when it stops being interesting.
And in a nice bit of timing: days before I found that policy, I had shipped the feature that makes its promise real. Developers can now remove leaderboard entries themselves, straight from the dashboard, with an audit log behind it. A player asks, the developer clicks, done. No email to me required.
The Month in Numbers
Everything else August shipped, in brief.
CheddaBoards is now on the new Godot Asset Store twice — the addon and a full game template, both approved. SDK v2.2.3 and v2.2.4 went out: sessions that survive restarts, account-linking fixes, and one consistent nickname rule across every layer. There were four different rules before. Do not ask. Sessions now last thirty days and quietly renew themselves while a player is active — sign in once, stay signed in.
The deep clean kept paying for itself in unexpected places. Watching live traffic, I noticed achievement syncs taking up to thirty seconds — one hop per achievement, and long enough that the hosting layer sometimes cut the request off at the wire. The whole flow got rebuilt as a single batched call. Thirty seconds became two. Players just see their unlocks arrive, which is the point.
The moderation tools above. A docs overhaul (which has since grown into a full documentation site), plus a complete worked example: Godot's own Dodge the Creeps tutorial game with CheddaBoards added, so you can see the whole integration in one small, familiar project. And every Chedda site now deploys from version control — the drag-a-folder-onto-a-website era is over, which removes one more way for a tired chef to serve the wrong plate.
The platform crossed 34,000 submissions processed, from more than 2,100 players, across more than 75 games — with no marketing spend, no launch moment. Just listings, word of mouth, and games quietly showing up.
Also, at some point this summer, a C SDK for CheddaBoards appeared on GitHub. I did not write it.
No promises on cadence, as ever. But a quiet fact worth leaving here: leaderboard reads already come straight from the chain — no proxy, no server of mine in the path. Issue 2 was the honest note about the plumbing that remains. The thing I keep circling is how much more of the kitchen can run like that.
One Thing From the Kitchen
Chefs taste everything. Not because they doubt the recipe. Because the recipe is not the food.
The sauce that has split does not announce itself. The seasoning that got skipped does not leave a note. The label on the container tells you what someone intended to be inside it, once. The only way to know what you are actually serving is to put a spoon in it.
Deploys are the same. The success message is the label. The running system is the sauce.
Taste it.