Node.js published a security release on a Wednesday. On Friday morning I opened tickets to patch my websites, and the first thing the agents reported back was that four of them were already running the fix.
Nobody had asked them to.
That is the short version of my week. Between Tuesday and Saturday, I dealt with bot attacks on my sites, a critical PHP vulnerability, a Node.js security release, a Linux kernel issue on my servers, and a handful of library advisories. A lot of that work got done by AI agents, some of it while I was asleep.
But this is not a story about a clever prompt. It is a story about two years of fairly boring engineering decisions, and what happens when they finally pay off all at once.
Four days, five fires
Here is roughly how the week went:
| Day | What happened | How it got handled |
|---|---|---|
| Tue Oct 20 |
Bots probing my sites for weak spots | Automated IP blocking built and deployed on four sites the same day, then a round of hardening fixes the reviews found |
| Wed Oct 21 |
CISA warns about "PHP2Shell", two unauthenticated remote code execution bugs in PHP | Ticket opened that evening. Overnight, a routine deployment had already put the patched PHP in production on my main site |
| Thu Oct 22 |
Making the PHP fix permanent | Claude hardened the main site and ran a read-only compromise review. Then four agents fixed the other four sites in parallel, done in about 40 minutes |
| Fri Oct 23 |
Node.js security releases | Four sites already patched by routine rebuilds. Four agents moved them to the current Node line anyway, done in about 45 minutes. Two client sites flagged |
| Sat Oct 24 |
A Laravel auth-bypass advisory, a Linux kernel issue, more library advisories | Overnight agent fixed advisories in two of my packages. Kernel mitigation applied to both servers. A new hourly security watcher went live |
Across those four days, my issue tracker gained 28 security-labelled tickets. 18 are closed. The other 10 are waiting on a decision from me, on a client, or on work I am not willing to rush.
The "automatic" part was a design decision
Let's go back to the hook, because it is the most useful lesson in this post.
My web projects build their containers from a major version line, not an exact version. The PHP image follows "PHP 8.5", and Node is installed from the "Node 24" line. Every deployment rebuilds the image and pulls whatever the newest release in that line is.
That sounds risky, and it would be if nothing else were in place. But it means that every ordinary deployment, for any reason at all, also picks up the latest security patches for the runtime.
So when the PHP fix came out, the next routine overnight deployment of my main site shipped PHP 8.5.12 to production. The agent doing that deployment was fixing an unrelated issue. It did not know PHP2Shell existed. By the time I sat down to work the PHP ticket, the patched version was already running.
The same thing happened with Node. A rebuilt PHP base image forces everything built on top of it to rebuild too, including the Node install. So when the agents surveyed my sites on Friday, four of them reported Node 24.22.1, the patched release for that line.
I want to be precise here, because it matters. My routine dependency updates did not fix Node. Updating npm packages and updating the Node runtime are different things. What fixed it was a build that refreshes the runtime every time, and a steady stream of deployments that made sure those builds actually happened.
There is a second half to this design. A floating version line answers "give me the newest". It does not answer "never give me anything older than X". So the fixes I made this week added a floor: each build now checks that PHP is at least 8.5.12 and Node is at least 26.12.1, and the build fails if they are not. I pin floors, not patches.
Automatic updates get you there fast. A fail-closed check makes sure you stay there.
Two years of harness behind every one-line prompt
When I started the parallel work on Thursday evening, my instruction to each agent was a single line:
/start-issue (ticket link) and complete /sdlc
That one line works because of everything behind it. For about two years, I have been building a set of skills and scripts that describe how I want software delivered. The workflow for every ticket looks like this:
- Intake. A ticket with a clear goal, acceptance criteria and a work-item folder.
- Branch and implement. Work happens in its own branch and its own checkout.
- Test. New tests for the change, focused runs while working, and the full suite before a pull request.
- Document. Technical and operational docs updated in the same change.
- Review board. Several specialized AI reviewers check architecture, security, tests and quality. Findings go into a punch list, and the loop repeats until it is clean.
- Pull request and deploy gate. Merge, deploy, and then check that the running site is healthy and reports the expected version.
- Close. A completion report on the ticket with the evidence.
None of this was vibe-coded in a weekend. A lot of the work went into making the system check itself. Guards check the guards. Tests get mutation-tested to prove they actually fail when the code breaks. When a review round finds a problem, the fix gets reviewed again, because I learned the hard way that a fix written under pressure is the least-reviewed code in the repository.
It is nowhere near finished. But it is now reliable enough that I trust it to work tickets end to end.
The skills are plain Markdown files and scripts, so they are not tied to one AI tool. This week, Claude did the first PHP fix and built the new watcher. Antigravity ran the parallel PHP and Node rollouts and the Linux mitigation. Hermes, my always-on agent, kept working the regular backlog around the clock. Since Wednesday, it merged and closed 39 issues, and it stopped and handed 17 back to me as blocked.
Those 17 matter as much as the 39. An agent that stops and asks when the next step needs a human is more useful than one that keeps going.
Parallel work, with the same rules
On Thursday evening I replicated the main site's PHP fix as a ticket in each of the other four PHP sites and started four Antigravity sessions at once. Each one implemented the change, wrote its own tests, ran its own review, deployed to staging and production, and confirmed the running PHP version. All four tickets closed about 40 minutes after the first session started.
On Friday I did the same for Node, with one difference. My sites were already patched, so this was not an emergency. I decided to move everything to the current Node 26 line rather than stay on Node 24. Again, four sessions, four tickets, all closed in about 45 minutes.
The same survey also found something the automatic path could not fix. Two client sites I maintain are on Node 20, which reached end of life in April and no longer gets security patches at all. A floating version line does not help when the line itself is dead. Those tickets are still open, because upgrading a client's runtime across major versions is a conversation, not a script.
Parallel work is only safe because each agent follows the same delivery rules. Speed comes from not having to supervise each step. It does not come from skipping steps.
The new piece: an hourly security watcher
After the third night of alerts, I asked myself a simple question. What happens when this lands while I am offline for a day?
So I had Claude build a security alert watcher, through the same workflow as everything else. It runs every hour and does this:
- Reads CISA's Known Exploited Vulnerabilities list, GitHub security advisories and a couple of security news feeds.
- Removes duplicates, so one incident reported in five places becomes one record.
- Checks each incident against what my projects actually run: lock files, Dockerfiles, audits and dependency alerts.
- Sorts each project into affected, not affected or cannot tell.
- For an affected project, opens one ticket, posts an alert, and starts the fix in its own workspace, with automatic merging turned off.
- Tracks every incident on a dashboard, from "ticket created" to "deployed".
Two rules were not negotiable. If a source cannot be read, that is reported, never treated as "nothing new". And "cannot tell" is never shown as green. A failed check that quietly becomes a pass is worse than no check.
The watcher went live this morning. On its first live run, it matched a newly published body-parser vulnerability to one of my client sites, opened a ticket and started the fix. Ten minutes later there was a pull request with tests, a review, and a note listing what it had deliberately left alone.
It did not merge anything. It is waiting for me, which is exactly what I told it to do.
What I kept for myself
Speed is not the only goal. Some decisions stayed with me on purpose:
- Production merges from the watcher. It can find, file, fix and test. I approve the release.
- Changes to the servers themselves. The Linux kernel mitigation was assessed first, with a read-only check, before anything was changed. Then it was applied one server at a time, with health checks on every container after each one.
- Client upgrades across major versions. Those need a conversation with the client.
- Anything an agent marked as blocked. Those land in my queue with a question, not a guess.
I also kept the honest wording. The read-only compromise review found no sign that anyone exploited the PHP bug on my sites. It also listed exactly why that is not proof: request bodies are not logged, and some traffic could reach the server without passing through Cloudflare. Fixing those gaps is now on my list too.
Why I think this matters
The Node.js Brotli flaw that kicked off part of this week was reported by OpenAI's Codex Security researchers. Anthropic has described its models finding a 27-year-old bug in OpenBSD and a 16-year-old bug in FFmpeg. Old code is not the same as safe code. It may just be code nobody has looked at closely yet.
AI is making bugs easier to find, for defenders and attackers alike. I do not have evidence that any of this week's issues came from an AI-assisted attacker, and I am not claiming that. But I do think the gap between "a vulnerability is published" and "someone is scanning for it" is getting shorter. The only realistic answer for a small team is to make the gap on our side shorter too.
For me, that did not come from a magic tool. It came from:
- Builds that refresh the runtime every time, with floors that fail closed.
- A delivery workflow that agents can follow without me watching every step.
- Agents that stop and ask when they should.
- A watcher that notices the next problem before I do.
This week was the first time all of those pieces were tested at once, under real pressure. They held, mostly. The parts that did not are now tickets, and some of those tickets are already being worked by the same system.
If you are building something similar, or you are looking for someone who has, I would like to hear from you.

No comments:
Post a Comment