At 10:36 PM, a Phone That Samsung Never Built Rewrote 13 Products in My Client's Store

A short story based on a real incident: 4 visitors, 4 countries, and a CSV that had been waiting in a folder since June.

Retro comic: shadowy phone-masked figure triggers home automation; office worker finds 13 products rewritten overnight

The email came from my own bot at 10:36 PM on a Thursday. "Optimization started," it said, followed by the name of a CSV file and the little signature I gave it: n8n bot. 7 minutes later, a second email arrived: "File optimized."

The next morning I asked my client what the late batch was about. The answer came back in 4 minutes.

"That wasn't me."

I hadn't touched anything either. So who pressed the button at 10:36 PM, and how did they know where the button was?

I build automations for that client's online store. A CSV of product barcodes goes in, an AI rewrites the titles, the keywords and the bullet points, and everything gets pushed back to the shop. That night, 13 products got the treatment: a hamster cage, a turtle pool with plastic trees and a tiny beach, and a whole family of mosquito traps.

The Button Was a Link

My automations run on n8n, a self-hosted workflow tool. Each job starts with a webhook, a URL that kicks off the work as soon as something calls it. Before I built my client a small upload app, that was the whole interface: drop a CSV on the server, open a URL in a browser, wait for the email.

The app replaced the routine, but the URLs stayed. Public, no password, and they answered a plain GET, the exact request your browser sends when you click a link.

Technically, at 10:36 PM, somebody clicked a link. Your automation never knows who clicked, only that somebody did.

I opened the execution log. 1 run, started by the webhook, 7 minutes of work, 13 product pages rewritten: titles, keywords, 5 Amazon-style bullet points each. The store doesn't keep old versions of a product page, so the previous text was simply gone.

And the new text was fine. It was good, even. The AI had done its job carefully, for someone who wasn't us. Every dev jokes about "it works on my machine." This time it worked on my machine, for a total stranger.

Nothing broke, and that was the bug.

4 Visitors in 36 Minutes

Every request to n8n goes through a reverse proxy first (the gatekeeper that sits in front of the server), and the proxy keeps a log. I asked Claude Code to pull every hit on my webhook paths since the logs began in late July.

It returned 4 lines. 4 requests in more than 2 months, all on the same night. Trimmed, with the paths renamed, they look like this:

10:24 PM  GET /webhook/create-products    200  iPhone, Firefox
10:36 PM  GET /webhook/optimize           200  Android 12, Chrome
10:48 PM  GET /webhook/optimize-images    404  iPhone
11:00 PM  GET /webhook/update-categories  404  Android 16, Firefox

Each request came from a different IP address, in a different country, on a different phone. They didn't log in or send any data. They walked to 4 doors I had opened on that server over the years, 12 minutes apart, and pushed.

The first door was the product-creation job. It ran, found an empty folder, and went back to sleep in 1 second.

The second door was the optimization job. It found something to read.

The third and fourth were doors I had walled up in June, old automations I had switched off. They answered 404, the "nothing here" code, and the visitor didn't seem to mind. It worked through its list the way a Dark Souls player hits every wall looking for a secret passage: patient, methodical, and completely unafraid of dying.

The visitor had a list that still remembered June.

The Phone That Samsung Never Built

Every browser introduces itself with a user agent, a short line of text that says which device and software it runs on. The visitor at 10:36 PM introduced itself as a Samsung SM-G935F, a Galaxy S7 edge, running Android 12 and Chrome 153.

According to GSMArena's spec sheet, that phone launched with Android 6 and its last official update was Android 8. An S7 edge on Android 12 is a phone Samsung never built. (Someone could flash a custom system on an old S7, sure. A crawler doesn't bother.) A user agent is just text you type, which makes it the internet's favorite Jedi mind trick: "This is not the bot you're looking for."

The other 3 visitors wore costumes too. Their addresses looked like home internet connections, the kind you'd find in a living room rather than in a data center. Picture it: 1 bot, 4 borrowed faces, 4 living rooms in 4 countries, and 1 list.

I still don't know who sent it. It could be a scraper feeding some AI dataset, or someone mapping n8n servers for later. What I do know is that costumes don't explain the list. A scanner could guess /webhook/optimize. It doesn't guess 4 exact paths in a row, including 2 I had retired in June.

The real question was why opening that door did anything at all.

The Letter That Waited Since June

A bot opening the optimization link should have been harmless. The job reads the first CSV it finds in a drop folder, and if the folder is empty, it stops.

The folder wasn't empty. A file was sitting in it, dated June 17: a batch of 13 barcodes my client had uploaded through the app, processed that same day, marked done. Then it stayed there, on the table, for 3 and a half months.

The workflow has a cleanup step. At the end of every run, it packs the folder into an archive and deletes what it just processed. I opened the editor. Both nodes were greyed out, disabled since March 16. The commit that switched them off talks about fixing a comparison in an If node, and says nothing about cleanup. Somebody was debugging something else, turned the cleanup off to keep a test file around, and never turned it back on. In my repo, "somebody" has a short list of suspects, and I'm on it.

So the house never forgot. Every batch stayed on the table, ready to be served again to whoever rang. In The Matrix, déjà vu means they changed something. In my drop folder, it meant the same file was still sitting there.

Turning off a cleanup step is how you teach a server to remember everything, for anyone who asks.

And it got worse. The app I built refuses a new upload while the folder isn't empty, a safety check so 2 batches never collide. Which means that since June 17, my client couldn't launch a single optimization from the app. The letter on the table was also blocking the front door.

That explained how. It didn't explain who, and while I was staring at the logs, something else started to bother me more than the stranger.

I Couldn't Find a Human in It

TITLE "The Chain With No Human In It" + subtitle "10:36 PM, 1 public link, 13 product pages rewritten". Metaphor: cross-section of a dark two-story house at night, 5 rooms connected by a glowing cable, an envelope travelling from room to room. Style: 1950s EC horror comic, heavy black inks, halftone shading, dramatic shadows. Palette: navy #14213D, amber #FCA311, muted red #C1121F, bone white #F1EFE9, black #0B0B0B. Content: room 1 CRAWLER (a figure wearing a smartphone as a mask at the front door), room 2 WEBHOOK (an unlocked door swinging open), room 3 AUTOMATION (big gears turning by themselves), room 4 LLM (a typewriter rewriting 13 product tags: hamster cage, turtle pool, mosquito traps), room 5 BOT EMAIL (an envelope stamped DONE sliding under a door), and in the attic a tiny grey human reading the email with a speech bubble "that wasn't me". Highlight: the open door and the travelling envelope glow amber, the human stays small and grey. Legend: sticky note bottom-left, "amber = machine action / grey = human action". Footer: © rentierdigital.xyz. NOT flat corporate vector, NOT stock infographic.
A night-time house where machines rewrite a store unattended

In 2014, Stephen Hawking told the BBC that once humans build AI, "it would take off on its own" (as reported by ABC News). I always pictured that moment as something big, with a red screen and a countdown.

It looked like this:

The only human job in the whole chain was reading an email and typing "that wasn't me." HAL 9000 at least refused to open the pod bay doors. My bot opened every door it was asked to, then emailed me a receipt.

I sent more machines. I put Claude Code on the n8n instance, with the tooling from 1 open-source repo that made it an n8n architect, and a second agent ran a read-only security audit of the whole server. Its report came back with things I didn't enjoy reading.

The n8n instance was 24 versions behind, on 2.17.3. GitHub's advisory for CVE-2026-42231 (the public ID of a security flaw) describes a bug in the way n8n parses XML sent to webhooks: an attacker can poison the app's internals and end up running code on the server. It was critical, 10 out of 10, needed no login, and was fixed in 2.17.4. My instance had been 1 patch away from that fix since the advisory went public in April. And the doors that flaw walks through are the webhooks, the same public, password-free doors my visitor had just used.

The root account on the server still accepted passwords from the internet. The auth logs showed 4,533 failed attempts in 7 days, all rejected, while every real login in the last 30 days had used a key. Something had been knocking on that door too.

And the dark-humor bonus: I once wrote a whole post on why anyone can trigger your n8n webhook. Then I left 3 of mine wide open.

Locking the House

Every webhook now asks for a secret in a request header, and only answers POST, which a browser click never sends. No secret, no run: n8n answers 403, access denied, and doesn't even start the workflow. Gandalf on the bridge, in HTTP: you shall not pass. I fired 6 bad requests at it and counted 0 executions.

Then I shut the doors from the outside. The reverse proxy now refuses /webhook, /form and /mcp for any request coming from the internet, and the app talks to n8n over the internal Docker network instead, a path the internet can't see. Simplified, the proxy rule looks like this:

labels:
  - traefik.http.routers.n8n-hooks.rule=Host(`n8n.example.com`) && (PathPrefix(`/webhook`) || PathPrefix(`/form`) || PathPrefix(`/mcp`))
  - traefik.http.routers.n8n-hooks.priority=1000
  - traefik.http.routers.n8n-hooks.middlewares=n8n-hooks-deny
  - traefik.http.middlewares.n8n-hooks-deny.ipallowlist.sourcerange=127.0.0.1/32

In plain words: any request for those paths gets a 403 unless it comes from the server itself. The audit had found that the server also answered directly on its IP, skipping Cloudflare, so I tested both routes. Both answered 403.

n8n went from 2.17.3 to 2.41.5, and this time the version is pinned. The Dockerfile used to say latest, which in practice meant "whatever was latest the last time somebody rebuilt the image." A Dockerfile that says latest is a jar labeled "food." Before touching anything, the 24 GB database got copied cold, with the old image tagged on the side as a way back. The total downtime was 25 minutes.

The 2 cleanup nodes are back on, tested with a fake batch: a barcode that doesn't exist in the store, nothing to rewrite, folder archived and emptied in 10 seconds. Root takes keys only now. A dormant file-transfer account that hadn't seen a login in 30 days is locked, and 13 old workflows that still carried webhooks are archived, so a misclick can't bring one back to life.


How it got in, I can explain now. Where the list came from, I can't. It held 2 doors I walled up in June, so whoever wrote it had seen my server before that. It could be a synced browser history, an old email, or a link pasted in a chat and scraped, and my proxy logs only go back to July.

The watchdog workflow still wakes up every 45 minutes. Last night it found the folders empty and went back to sleep.

At 3:12 AM, the proxy logged 1 request on /webhook/optimize and returned a 403.

Somebody still has the list.

Sources

This post may contain affiliate links. If you click them, I might earn a small commission — costs you nothing, and helps me keep shipping quality articles every day for your reading pleasure.


When a stranger rewrote 13 products in your client's store through an open webhook, the real problem wasn't the attack. Your automation had no way to know who was pressing the button. The demo-vs-product checklist in the welcome kit covers exactly this: authentication, rate limiting, and the 8 criteria that separate a prototype from something you'd actually ship.

→ Get the welcome kit