thim.dev/blog
AUG 2026 · 8 MIN READ

The wine fridge that phoned home

I have had a wine fridge for four years. A nice one: dual zone, quiet, a little display on the front. For four years it had exactly one flaw, which is that it does not work for me. It works for its manufacturer.

Out of the box it is a cloud appliance. There is an app, the app talks to a server somewhere, and the server talks to the fridge. If I want to know how cold my wine is, that question takes a round trip to a company I have no relationship with beyond a warranty card. And here is the part that, after four years of letting it slide, finally tipped me from mildly annoyed into a project: the fridge encrypts its own temperature. Not to keep it from me, exactly. Just as a side effect of a design where the only intended reader is the vendor's app. My appliance, on my network, keeping its own readings secret from me.

That is the kind of small indignity that ends with a decompiler open at midnight.

§ What the fridge actually says

The first thing was to stop guessing and listen. Listening, in practice, meant sitting between the phone and the internet and watching what the app said when it thought no one was paying attention. I ran the vendor's app on my phone with a small traffic-capture app recording alongside it, Stream on iOS, and then used the fridge the ordinary way: open the app, nudge a setting, watch the fridge obey, and read back the list of calls it had made to do it.

That capture was the map. It named every server the app reached: a cloud API it logged into and queried, and, running quietly alongside, a persistent encrypted connection to a message broker that the ordinary API traffic never explained. The fridge, it turns out, speaks MQTT, the little publish-and-subscribe protocol most of the IoT world runs on, wrapped in TLS, to a broker with the vendor's name on it. It asks a question on one topic and hears the answer on another, and its state arrives as a retained message, which is a nice touch: anyone who subscribes gets the last-known reading immediately, without waiting for the next update.

The cloud API was the obvious place to look first, so I looked. It hands out metadata: which device is mine, what firmware it runs, the kind of thing an account page shows. What it never returns is the temperature. The real data only ever travels over that MQTT channel, and it travels encrypted. So there was no shortcut through the cloud. The only way to my own fridge's temperature was to speak the fridge's own language.

home assistant wine cooler 1001 · ask for state → topic /r 1002 · state ← topic /s · retained the reply payload is base64 over ciphertext. decoded: base64 AES-128-CBC { readable JSON } one key, hard-coded, identical in every unit temperatures · setpoints · power · lights · compressor · fan your fridge's own readings, in plain text at last

That reply payload is the whole game. It is base64, and inside the base64 is ciphertext, and inside the ciphertext, presumably, is my wine. The question was the key.

§ One key to rule them all

The traffic capture told me where the data was. It could not tell me the key, because the key is never on the wire: it lives in the app, which is the thing that does the decrypting. So I went and got the app, and here I switched phones. Sniffing is easiest on my iPhone, but Apple's builds are compiled down to native code that fights back when you try to read it. The Android build of the same app does not fight: it is just an archive, and because the app is built with .NET, the logic inside sits in an intermediate language that decompiles back into very readable source.

One wrinkle worth passing on for anyone doing the same: modern .NET Android apps hide the actual code in a compressed blob keyed by processor architecture, not in the usual place, and the slimmed-down download I first grabbed had left it out entirely. Once I had the right file, a decompiler turned the encryption class back into plain source, and there it was, written into the program like a comment.

AES-128, a fixed initialisation vector that never changes, and a key that is a string a human clearly typed on a keyboard: the top row, more or less, dressed up to look random. The same sixteen characters ship inside every copy of the app, which means the same key is inside every one of these fridges, everywhere. No per-device secret, no handshake, no rotation. One key, shared by the entire product line.

Fed that key, the ciphertext falls open into clean, readable text: both zone temperatures, both setpoints, the lights, whether the compressor is running. My wine, in plaintext, at last.

The lesson, and it is a general one: a great deal of consumer-device "encryption" is theatre. It is not there to stop you, and it will not stop anyone else either, because the secret that protects millions of devices is the same secret sitting in an app-store download. Encryption where everyone shares the key is obfuscation wearing a lab coat. It buys the manufacturer a checkbox and buys the owner nothing.

§ Taking it home

Reading the fridge was satisfying. Owning it was the point.

The trick is embarrassingly small. The fridge finds its cloud broker by name, and names are looked up on my network, by equipment I control. So I told my network a small lie: when the fridge asks for the address of the vendor's broker, it gets the address of a broker running in my own house instead. The fridge, none the wiser, connects and starts chatting exactly as before, except now it is chatting to me.

CASO cloud somewhere on the internet cut off wine cooler DNS override one hostname, redirected your broker home assistant same appliance, same protocol, no packet leaves the building named to the cloud, answering to the house

Then I cut its internet off at the door. Completely. The fridge has no legitimate reason to reach the outside world, so it does not get to, and it has not noticed. It keeps its temperature, keeps its schedule, answers every question, and does all of it without a single packet leaving the building. A cloud-only appliance became a local one, and the cloud it was named for could vanish tomorrow without my wine getting one degree warmer.

§ Making it boring

A trophy on a shelf is not the goal here. The goal is for this to disappear into the machinery, the way everything useful eventually should.

So the whole thing is now a Home Assistant integration, packaged the normal way, installable by anyone with the same fridge and half an hour to spare: ha-caso-winecooler. It presents the fridge as what it always should have been, an ordinary smart device: two temperature zones, two lights, a couple of sensors. Home Assistant polls it about once a minute, and when an automation changes a setpoint it reads the fridge back to confirm the change rather than assuming it worked. No app, no account, no cloud, no me.

I should say plainly that I did not do any of this alone. The decompiling, the protocol notes, and the integration itself came out of a long back-and-forth with Claude, Anthropic's AI, working beside me the whole way. Calling it a tool undersells the thing; by the end, friend was closer to how it felt.

Building the integration taught the second lesson, and this one is about software, not fridges.

We wrote it test-first, and had dozens of green tests before any hardware was involved. Every one of them passed. Every one of them was also lying, and it took a different kind of review to see it. The tests mocked the fridge, and the mock answered the instant it was asked, in the same breath as the question. A real fridge does not do that. It thinks for a second or two and then replies. The code, it turned out, asked its opening question and checked for the answer immediately, before any real device could possibly have sent one. Against the mock, flawless. Against an actual fridge, it would have failed on the very first exchange and then sat there retrying forever.

Nine separate reviews of the individual pieces missed it, because each piece was internally consistent. It only fell out when one review stepped back and asked a stupid, wonderful question: what if the device replies at a realistic speed?

The lesson: a mock that answers synchronously cannot test an asynchronous protocol, and a suite built on one will be confidently, uselessly green. Timing is a feature of the thing you are testing. If your fake collapses it to zero, your tests are checking a world that does not exist. The most valuable review is rarely the one that reads a function closely. It is the one that questions the assumption every function quietly shares.

§ The wine, finally

The fridge sits where it always did. It looks identical. Nothing about it advertises that it has changed sides. But it answers to my house now, not to a server I will never see, and if I ask it how cold the wine is, it tells me, in plain language, without asking anyone's permission.

That is the whole hobby, really. Not the reverse engineering, which was a means. The end is an appliance that does its one job, quietly, on my terms, and never phones anyone about it again.