The third week of October 2026 was a rough one for anyone who runs software on the internet.
Two critical bugs in PHP, nicknamed PHP2Shell, were f...
For further actions, you may consider blocking this person and/or reporting abuse
I run into these security issues too, and it’s terrible to admit, but I don’t really find myself worrying about them. Maybe I don’t fully grasp how serious they are, because it’s very hard for me to assess. Say there’s a PHP vulnerability that attackers can exploit, and since a significant share of database-backed websites use PHP, they might gain access to some sites’ data. In the worst case, since we’ve digitalized absolutely everything, this could cause actual financial harm to someone who only got involved in the digital world out of necessity or convenience—which means almost everyone on this planet.
At the same time, there’s the AI question, which I think is orders of magnitude more urgent. That’s why I wrote Are we prompting our own extinction?, because I feel this is a more serious issue.
As a small business owner, when I build something as a hobby, I try to make things that don’t involve personal data. In fact, I’ve never built a program that required people to log in. For example, neither my implementation of the pipeline operator in TypeScript nor its sandbox requires a login. Since I suspected roughly nobody would use it, I simply added an AI with a $5 monthly spending limit. But I don’t collect any data from anyone.
So my first line of defense is building applications that are inherently safe. Of course, as this example already suggests, having an AI in the terminal answering questions introduces risks that I can’t fully assess either.
Thanks for the honesty. Building things that hold nothing worth stealing is one of the best defenses there is, and it’s underrated. Your $5 cap is smart too: it limits the damage even if the key leaks. The part I’d still watch is everything around the app, like the server, dependencies, and that API key, since attackers don’t always want your data; sometimes they want your machine or your credits. And I get why AI’s bigger risks feel more urgent. Maybe the two connect: the same speed that worries you long-term is already showing up in security today.
The asymmetry line is the one that lands hardest — AI made finding problems cheaper for everyone but fixing them cheaper for no one, and the attacker only has to find while you have to fix. For a four-person team that flips the priority: shrinking the patch queue matters more than speeding it up, which usually means fewer dependencies and more managed services, not better tooling. Do you think the realistic answer for small teams is auto-update everything and accept the breakage risk, or aggressively cut surface until the queue is survivable?
Cut surface first, then auto-update what’s left. Auto-updating a huge dependency tree just trades security breakage for random breakage. Fewer dependencies and managed services make the queue small enough that auto-updates become safe, and the few things you can’t cut get human eyes. Shrinking comes before speed.
'Shrinking comes before speed' is the right ordering — auto-update only becomes safe once the tree is small enough that a breakage is diagnosable, not just another alert in a queue you can't read. The human-eyes carve-out for what can't be cut is the part I'd keep explicit: that's usually where the real risk concentrates. What do you use as the cut test — if a dependency's failure wouldn't page anyone, does it stay, or is that exactly the one to drop?
That’s the one to drop. If nobody would notice it failing, nobody will notice it compromised either. Unwatched means unneeded.
That's a sharper cut test than I expected — unwatched doesn't just mean unneeded, it means unnoticed when compromised too. If a failure wouldn't page anyone, there's no one watching the compromise either. Does that rule hold for transitive dependencies you didn't choose, or is that where the cut test gets harder to apply?
That’s where it gets harder. You can’t drop a transitive dependency directly, only the parent that drags it in. So the test moves up a level: judge each direct dependency by its whole tree, not its own code. A small library pulling two hundred packages costs more than it looks.
The real cost of a week like that is the 2am feeling of reading an advisory and not knowing if you're in it. One of PHP2Shell's crash paths ran through multipart parsing, so a site with no uploads could refuse multipart at the proxy in minutes and buy the hours the patch needs. Switching the trigger off beats patching fast.
That’s a practical point. Knowing which features you don’t use lets you shut whole attack paths off instantly. Turning things off is underrated, because nobody plans for it until 2am.
Nobody plans for it until 2am is the whole story. A kill switch you've never flipped is just a hope with a config key.
Exactly, which is why the switch should get flipped on a calm Tuesday afternoon, long before anyone needs it at 2am.
Maybe the biggest opportunity is using automation to reduce the defender's response time too: map dependencies, identify affected services, flag exposed secrets, and prepare patch checks in one workflow.
It won't remove the need for human judgment, but it could make the first hour after a disclosure much less chaotic for a small team.
Agreed, the first hour is where automation helps most. The catch is that it only works on an accurate inventory, so the boring list comes first. And every tool wired into secrets and dependencies is attack surface too, so it deserves exactly the same scrutiny.
The part that keeps rattling around my head is the asymmetry: attacking got cheap, defending didn't. A four-person company can't hire its way out of that, and it can't patch-everything-today either — there aren't enough hours in the day the advisory drops.
The only lever small teams actually have is shrinking the surface before the clock starts: auto-update the boring layers (base images, frameworks, the stuff nobody writes blog posts about), so human attention is reserved for the internet-facing and the weird. LiteLLM getting hit twice in one year is the perfect example — that's not an app you patch, that's a dependency you either pin and watch or replace.
Curious what your triage rule looks like in practice. When three criticals land the same morning, what decides the order — exposure, exploit maturity, or just whoever screams loudest?
Exposure first, always. If it isn’t reachable from the internet, it waits. Then exploit maturity: anything with public exploit code or active attacks jumps the queue. Loudness should count for nothing, though in practice it honestly often wins. Writing the order down beforehand is what stops the screaming from deciding.
For a four-person team I'd aim at one number: how many minutes it takes to answer "are we affected?" Committed lockfiles plus dependency alerts on the repo turn that from reading the news into getting told, and only about things you actually run.
The other half is secrets. When an incident like Railway's says rotate, the slow part is finding every place a secret lives and what breaks when it changes. A short list per secret (where it's stored, what uses it, how to rotate it), written on a calm day, turns that 2am job into a checklist.
Measuring the minutes it takes to know if you’re affected is a brilliant metric, simple enough that a tiny team can track it. The secrets list is the part most people skip, and it’s what turns panic into routine. Writing it on a calm day is the whole trick. Both belong in every team’s README.
Smart contracts get the harshest version of this clock. You can't patch a deployed contract, the fix is an upgrade, and if that upgrade sits behind a timelock the new code is public for a day or two before it's live. With AI reading diffs in hours, that delay is now an exploit window.
Same idea as switching the trigger off above: pause first, then upgrade. Does your team have a switch like that for the SaaS side?
A timelock turning a safety feature into a public countdown is brutal. On the SaaS side, the closest equivalent is feature flags and a maintenance mode that can cut off a risky endpoint instantly. Most small teams only build that after their first incident, which is the problem.
The part of your post that stuck with me is the sentence about digging through logs to see if anyone got in before the fix landed. Patching has an end state. That question usually has no answer, and not because nobody has time: a small team's logs are built for debugging, not for proving a negative. Two weeks of retention, request lines with no bodies, and no baseline to compare Tuesday against.
It is worth fixing before the next advisory rather than during it, and the bar is low. Pick the endpoints that parse untrusted input and record, for those only, source address, user agent, status and response size, kept long enough to outlast an average embargo. Put a counter on 4xx and 5xx volume per endpoint so a spike is visible without opening a log viewer. Then on advisory day, export those slices first thing, because retention rolls over before an investigation finishes. None of that makes you monitored. It makes "were we hit" a question you can answer with evidence instead of a shrug, which is the difference between closing an incident and closing a ticket.
This is the most useful comment the post has gotten, thank you. You’ve put words to something I only gestured at: patching ends, but the question of whether someone got in before the patch can linger forever.
The distinction between logs built for debugging and logs built for evidence is the key. Most small teams keep exactly what you describe, two weeks of request lines with nothing that would help reconstruct an attack. When the advisory lands, they’re trying to answer a security question with tools designed for fixing bugs.
I like that your approach is narrow. Only the endpoints that parse untrusted input, only a handful of fields, kept long enough to outlast an embargo. That’s cheap enough that a small team might actually do it, which matters more than any perfect setup nobody builds.
Two small additions I’d make. Record exactly when each fix was deployed, because without that timestamp you can’t define the window you’re investigating. And be careful what goes into those slices: logs full of tokens or personal data become their own liability.
Exporting first thing on advisory day is a habit I’ll be stealing. Have you ever had those slices actually answer the question?
The asymmetry you describe is the real change. Attacking got cheaper and faster, while defending still depends on a small team knowing what it runs. Gautam's point about how many minutes it takes to answer "are we affected?" is the measure I would track before buying any new tool.
Shuttlewright's comment that an inventory beats a faster scanner matches what we see in small companies. When an advisory like the LiteLLM package issue lands, the slow part is finding every service, container and CI job that pulls that dependency, and every environment variable it could read. A single list of services, owners and secrets turns that into a search.
Paul Lovelace's suggestion to shrink the surface is the cheapest defence of all. The "unwatched means unneeded" rule from the thread is a good test: if nobody would notice a service going away, it probably should.
Did your team keep the secrets list in code or in a separate register?
Thanks for pulling the thread together so well, that’s a better summary than the post itself. On the secrets list, I’d keep it in code, right next to the config that uses each secret. A separate register quietly drifts the moment someone adds a key without remembering to update it. In the repo, it changes in the same pull request and gets reviewed just like everything else. It should only ever hold names, locations, owners and rotation steps, never the actual values themselves. The goal is that finding everything a leaked key touches becomes a search, not archaeology at 2am.
the part that matches what we see is the secrets blast radius. when a malicious release or a platform leak lands, the real cost for a small team is rotating everything at once because the same key lives in five places. what helped us was one scoped key per service per environment, plus a rotation script we dry run every quarter, so incident day means running a script instead of hunting for where a key got pasted.
do you think small teams should delay dependency updates by a few days so the first wave of bad releases gets caught by someone else?
Scoped keys plus a rehearsed rotation script is exactly the right pairing. On delays: yes for routine updates, a few days of cooldown catches many poisoned releases. Security patches are the exception, since waiting days is exactly what attackers count on now. The hard part is that both arrive looking identical in the update queue.
The line that hit me: "AI made finding problems cheaper for everyone. It didn't make fixing them cheaper for anyone. And the attacker only has to find. You have to fix."
I felt this heavily this week, just from a different angle. I had an editor catch that an article I wrote sounded too much like AI. Finding that problem took her two seconds. Fixing it meant me deleting 1,500 words and rewriting the whole thing from scratch over the weekend.
The asymmetry of effort is real. AI lowers the cost of generation and finding, but it doesn't lower the cost of understanding and fixing.
For small teams, it's terrifying because you're not just fixing your own mistakes. You're fixing the mistakes in the dozens of open-source packages you imported just to get the product out the door. You didn't write the vulnerability in LiteLLM, but you're the one stuck rotating your API keys at 2 AM.
Great breakdown of the new reality. I think the only way small teams survive this is by automating the fixing (strict dependency management, automated tests, easy rollbacks), because trying to manually audit everything is a losing game.
Your editor story is a perfect example, and a little painful to read. Two seconds to spot, a whole weekend to repair. The same asymmetry shows up anywhere AI makes producing things cheap but understanding them stays expensive.
I agree that automating the fixing side is the only scalable path. Of your three, easy rollbacks might be the most underrated. When you can undo a release in a minute, a broken update becomes an inconvenience instead of an emergency.
The one caveat I’d add, though, is that automation inherits whatever you feed it. Tests only catch what someone thought to test, and dependency tools only manage the dependencies you know about. So the boring work still comes first: knowing what you run, keeping the tree small, and deciding ahead of time what counts as urgent.
Automate the fix, but shrink what needs fixing. Small teams need both to stay ahead.
The AI era seems to turn everything into a downside: now you have to worry about everything, and it costs both time and money. But I think that, in the near future, developers will establish rules for using AI and avoiding problems. That’s always happened with new technologies, but this time it may happen faster. The internet and mobile technology, for example, brought both benefits and security risks by making communication easier.
Ultimately, time will tell. Developers will get better at this and create better tools, both with and without AI. Security checks will become a standard part of the development workflow, along with end-to-end tests that simulate attacks before software is deployed to production. It may sound simple, but I think it’s an endless battle between attackers and defenders. 🫡
I share that optimism. Every big shift, from the early web to mobile, went through a messy phase before good habits and tools caught up, and you’re right that this time it may happen faster. The risky part is the gap in between, while attacks have already sped up and defenses are still catching up. That’s where small teams feel it most. Endless battle indeed. 🫡
The small-team framing is the right one, because the gap is rarely knowledge - it is calendar. When the time between disclosure and exploitation shrinks, the teams that cope are the ones that already know what they run. An accurate inventory beats a faster scanner: you cannot patch in hours what you cannot list in minutes. After that, the next highest-yield habit is a standing rule for what gets emergency treatment versus the normal cycle, decided in advance, not during the incident. Speed comes from preparation, not panic.
Well said. The problem is calendar, not knowledge. Deciding in advance what counts as an emergency is underrated; making that call at 2am, half-awake, is where small teams lose hours.
Small teams shipping AI code without a security person is the real story here
Yeah!
The part about attackers only needing one exploit while we have to fix every issue is scary. For small teams, security is becoming a resource problem as much as a technical one.
very cool
sweet
I build and run small tools too, and the hardest part is knowing what’s exposed when something breaks. Keeping the dependency list small helps more than adding another scanner.
Solo dev, node stack so php2shell missed me. Still ran npm audit that week and found two deep deps that needed updates. "You inherit everyone else's problems" is exactly it.
This article nails the brutal reality of modern security! "AI made finding problems cheaper for everyone. It didn't make fixing them cheaper for anyone" is the exact asymmetry small teams are struggling with right now.
When patch-to-exploit timelines shrink from weeks to hours, a 4-person team simply can't run a traditional patch cycle without completely freezing product velocity. The security burden on micro-startups is getting unsustainable. Fantastic write-up!
Small teams do not need to copy an enterprise SOC; they need a short, repeatable response loop. Asset inventory, dependency updates, secret handling, a tested restore path, and named owners for alerts go further than a large pile of tools with no operating rhythm.
at four people the security plan is whoever saw the alert first :/