29 august 2026 · düsseldorf
watchbucko.online
The streaming app I built for one living room, on a real domain since 26 July. The first hole showed up that evening, and my fix for it opened the second one by morning.
I put the TV app I built for my living room on the public internet. Within hours, anyone who found it could have taken my account.
The app is called Bucko. I had been building it since April for my own TV, and on 26 July it got a real address at www.watchbucko.online. A real URL changes exactly one thing. Anyone can type it. Not my friends, anyone.
01Bucko is the TV app I built for my own living room, on one provider account. One codebase builds the website, the phone remote, the Samsung package, a desktop app and a Chrome extension.
02Hours after it went public on 26 July, the guest sign-in turned out to be handing out the master key. Closing it opened a second hole by morning. Both fixed within a day.
03Afterwards I wrote down one rule. Gate an endpoint by what it returns, not by what it's called. The front door now asks for your own account, and mine stays behind the password.
04The living-room Samsung browses 53,000 channels and 177,000 films on a browser engine that shipped in 2019. It still plays every evening.
Becoming Bucko
The first commit, 7 April 2026, is "Initial IPTV ESP32 firmware - WiFi + Xtream API portal with favourites". It was going to be a hardware thing, running on a small chip.
That idea survived one day. On 8 April it became a web app, and an hour after that the log reads "chore: remove old ESP32 / hardware files, web-only repo now". The hardware was gone on day two, before the thing had a name worth keeping.
The name took three tries. ipTV Local, then Bucko, then watchbucko.online. The dark rebrand landed on 12 June. The phone became the brain of the operation and the TV stayed cinematic.
The day before that, 11 June, it reached the actual living room, installed on the Samsung. The same day the phone became a remote. Pair by QR code, see what is playing, hand off between phones.
Bigger than the machine
All of it has to fit on the weakest screen it runs on. On the Samsung the catalogue is bigger than the machine browsing it. The design doc states the budget in four words. "TV heap budget ≈ 250MB." That is all the memory the app gets there.
That ceiling makes the decisions for you. The TV used to re-download the whole channel list on every launch, so it boots from a cache now. A view with four channels at once got designed and then killed by the hardware, which can only decode one.
A catalogue that size arrives dirty. One film comes in from the provider two dozen times under two dozen names, which is why the detail screen for The Dark Knight offers 25 sources behind one Play button. Bucko now picks the name that appears most often across those copies. The old rule took the shortest one, which is how Harry Potter and the Order of the Phoenix turned up on my TV as "Harry Potter i Zakon Feniksa". Counting copies instead of characters means an Arabic film stocked mostly in Arabic keeps its Arabic title.
The same catalogue got much bigger on 5 August without anything new arriving. One file in the code claimed that MKV, a common video file format, is something "no browser demuxes at all". That is true of the engines behind Safari and Firefox. Chrome's handles it fine, and lifting the check moved 130,134 of that day's 178,126 films from unplayable to playable, in one commit.
Going live
The domain was never the plan. The app ran fine without one. What forced it was a browser rule. The provider sends every stream unencrypted, the app is served encrypted, and a browser will not play the one inside the other. The TV and desktop builds are exempt from that rule. The web app has to obey it, and the web app is the one I wanted to be able to hand to someone.
Every fix I could find was a proxy, something in the middle that re-serves the stream. The first one ran on Vercel, whose monthly allowance live TV eats in about 30 hours. The one that stuck runs on a Cloudflare Worker, and the code says why. "Worker egress is unmetered, unlike Vercel's." It is one household watching one connection at a time, which is the only reason that arrangement sits right with me. That same morning I pointed the domain at the live app.
The hole
The first hole was there by launch evening. The app has a guest sign-in, for someone bringing their own account. The code behind it would set the app's access cookie for anyone who asked, and the value in that cookie was the access password itself. One request, no credentials, and you were holding the gate's own key.
That cookie unlocked more than the app. It also unlocked the part of the app that hands out my real provider account, the one the TV uses. Two steps. Ask the guest sign-in for a cookie, then ask for the account. The app had been public for a few hours and its worst path was that short.
The evening fix split the session in two. Type the password and everything opens. Bring your own account and the app opens, and my credentials stay locked. The password had to be changed on top of that, because any cookie already out in the world stayed a working key until the lock itself changed.
I went to bed thinking it was closed. It was. The same fix had opened the next one on the way past.
To keep the sign-in form working while my credentials stayed locked, I had the gate hand a guest session to anyone who so much as loaded the sign-in screen. That session opened two relays. The first gave out the address of my Cloudflare proxy and its token, so a stranger had an open pipe running on my account. The second fetched any URL you pointed it at and streamed the answer back through my Vercel account, wearing the app's domain.
I proved all three from a clean terminal against the live site, before touching any code. Three requests, no password. The commit that closed them is titled "stop handing every visitor a session, and close the two relays it opened".
A guest session costs a real account now. The sign-in checks the typed credentials with the provider, and issues a cookie only if the provider says the account is real. Both relays stopped trusting that session, and the leaked token was rotated. The rule I wrote down afterwards was to gate an endpoint by what it returns, not by what it's called.
What the weekend left behind is a front door that asks for your own account. It leads with "Sign in with your Xtream account" and "Credentials stored locally in your browser only", a flip that landed on launch day, hours before the first hole. The TV had my provider account baked into its package for a while, which reversed my own plan. On 24 July it went back to pairing itself on first launch. The phone remote never ships credentials at all. It only steers.
One connection
The provider account allows one connection at a time. Everything about Bucko follows from that. Whoever holds the slot keeps it, and whoever asks second gets an error back from the provider and a stream-limit message from the app. The provider settles that, not my code. The TV usually wins because the TV is usually already playing.
One slot also means a dropped stream has to be recovered in place, so the player got the one quality I actually wanted from a TV. Stubbornness. A stalled stream gets a watchdog and a retry that backs off and then never gives up, like a real TV.
The domain still resolves
The repo has been quiet since 7 August. The last real commit made the TV's home-screen icon follow the brand, and a merge closed the branch forty-six seconds later. The domain is still there and strangers can still type it. What changed that weekend is what they get when they do, which is a gate asking for their own account while mine stays behind the password. It still plays every evening.