DEV Community

Cover image for Hours, Not Weeks: How AI Rewrote the Clock of Software Security
James Anderson
James Anderson

Posted on AI-assisted

Hours, Not Weeks: How AI Rewrote the Clock of Software Security

Highlights the impossible burden on micro-teams

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 found by security teams using frontier AI models. Estimates said they touched a huge share of the web. Governments told organisations to patch immediately, and plenty of people simply pulled their servers offline. In the same few days, LiteLLM had a malicious release pushed to PyPI (for the second time this year), Railway disclosed that attackers had gotten into customer environment variables, GitLab shipped fixes for maximum-severity bugs, and a handful of Linux kernel vulnerabilities got posted to the wrong mailing list.

Drew Silcock wrote a great roundup of all of it in Is this the new cyber normal?, and it's worth reading in full.

But after going through it, the thing that stuck with me wasn't any particular bug. It was a simpler thought: who is actually supposed to deal with all this when your entire company is four people?

The clock got shorter

Security used to have a rhythm. A vulnerability got announced, a patch came out, and you had some time. Maybe days, sometimes weeks, before anyone figured out how to actually exploit it. "We'll patch it this week" was a reasonable plan.

That rhythm is gone. Today, the moment a security patch is published, people point AI agents at the code. The agent reads through the changes, spots what looks like a security fix, works backwards to the hole it closes, and helps build an exploit. What used to take a skilled researcher weeks can now happen in hours.

With PHP2Shell, attacks were reportedly happening before the official patch was even available. "This week" isn't a plan anymore. "Today" barely is.

Attacking got cheap

Here's the part that changes everything. Finding a serious bug and turning it into a working attack used to require rare skills and a lot of time. That made it expensive, and expensive things happen less often.

Now an attacker can throw an AI model at a codebase and see what comes out. A lot of it is junk. AI produces plenty of "exploits" that don't actually work. But attackers don't need everything to work. They need one thing to work. When trying costs almost nothing, you can afford to try a thousand times.

Defending didn't get cheap

On the other side, almost nothing got cheaper.

When a critical bug drops, somebody still has to read the advisory, figure out whether you're affected, check which of your services use the vulnerable thing, test the patch, deploy it, and then dig through logs to see if anyone got in before you fixed it. If you use AI to scan your own code first, great, but that costs money too, and every bug it finds is a bug someone now has to fix.

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.

This is where size starts to matter

A big company handles a week like that with a security team, an on-call rotation, incident playbooks, and people whose literal job is to drop everything and respond. It's painful for them too, but they're built for it.

A small team isn't. In a four-person startup, the person who handles security is usually also the person who handles deployments, the database, customer support tickets, and the feature that was supposed to ship Friday. There's no rotation. There's just whoever happens to be awake.

And when the advice is "take your servers offline until you patch," the cost hits differently. A large company can survive a day of downtime. A small product with a few hundred paying customers might lose some of them for good.

You inherit everyone else's problems

Small teams don't build everything themselves. They can't. They stand on a stack of frameworks, packages, hosting platforms, and AI tools, all written by someone else.

That's normally a strength. It's how a tiny team ships something real. But it also means that when one of those pieces gets compromised, the problem becomes yours instantly. You didn't write the LiteLLM backdoor, but if you installed that release, your credentials might be gone. You didn't misconfigure Railway, but if your secrets lived there, you now have to rotate every one of them.

For a big company, that's a ticket for the security team. For a small team, it's an entire day of product work gone, spent cleaning up a mess you didn't make.

The tools we adopted to move faster

There's an irony here. Small teams have been some of the fastest to adopt AI tools, because AI promises exactly what they need: more output from fewer people.

But every one of those tools comes with something attached. Another API key. Another dependency. Another service with access to your code or your data. One of the incidents that week, a data leak at a database company, reportedly exposed API keys for several AI providers that had been pasted into internal tickets. Keys spread quietly, and every one of them is something an attacker would love to find.

The tools that help small teams keep up are also handing attackers more things to go after.

Falling behind isn't the same as not caring

I don't think small teams are ignoring security. Most developers I know care about it a lot. The problem is that the math changed underneath them.

Defending at the new speed assumes things a small team doesn't have: people on call around the clock, budget for scanning tools, time to audit every dependency, room to absorb downtime. When attacks got faster and cheaper but defense stayed slow and expensive, the gap didn't hit everyone equally. It landed hardest on the people with the fewest resources.

I don't have a neat answer

Maybe this is a temporary spike. Maybe AI is just clearing out years of old bugs all at once, and things will calm down once the backlog is gone. Or maybe this is simply what security looks like now.

Either way, it feels like we're in a period where the cost of defending software is rising quickly, and small teams are the ones feeling it first.

If you're on a small team, I'd genuinely like to know how that week went for you. Did you patch in time, pull things offline, or only find out afterwards?

Top comments (45)

Collapse
 
pengeszikra profile image
Peter Vivo •

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.

Collapse
 
james_anderson_h profile image
James Anderson •

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.

Collapse
 
kami_butt_69b922ade8de012 profile image
Agentic Affairs •

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?

Collapse
 
james_anderson_h profile image
James Anderson •

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.

Collapse
 
kami_butt_69b922ade8de012 profile image
Agentic Affairs •

'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?

Thread Thread
 
james_anderson_h profile image
James Anderson •

That’s the one to drop. If nobody would notice it failing, nobody will notice it compromised either. Unwatched means unneeded.

Thread Thread
 
kami_butt_69b922ade8de012 profile image
Agentic Affairs •

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?

Thread Thread
 
james_anderson_h profile image
James Anderson •

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.

Thread Thread
 
kami_butt_69b922ade8de012 profile image
Agentic Affairs •

Judging the direct dependency by its whole tree is the right move up a level — the small library's real price is the two hundred packages it drags in, including the ones nobody audits. The hard case is when the parent is worth it but one transitive branch isn't: then the choice is fork, pin, or replace the parent, and none of those are free. Do you have a tree-size or audit threshold that triggers that call, or is it a judgment call per dependency?

Thread Thread
 
james_anderson_h profile image
James Anderson •

Mostly judgment, but triggered by signals rather than size: install scripts, abandoned maintainers, sudden ownership changes, or network access it shouldn’t need. Pinning comes first because it’s cheapest. If the branch stays risky after a few releases, the parent goes.

Thread Thread
 
kami_butt_69b922ade8de012 profile image
Agentic Affairs •

Signal-triggered judgment beats a size threshold — a tiny package with an install script or a sudden ownership change is riskier than a big boring one. Pinning first makes sense as the cheapest move, but it freezes the risk in place rather than removing it. So the real question is what 'a few releases' means in practice — a fixed count, or until the signal clears?

Thread Thread
 
james_anderson_h profile image
James Anderson •

Until the signal clears, but with a deadline attached. Otherwise a pin becomes permanent. If the risky branch hasn’t improved within a set window, say one quarter, the parent gets replaced. A pin should buy time, never become the plan.

Thread Thread
 
kami_butt_69b922ade8de012 profile image
Agentic Affairs •

A deadline is what keeps a pin honest — without it, 'temporary' quietly becomes the plan, and a quarter is a sensible window for a signal to clear. The part I'd want pinned down is what counts as cleared: a maintainer returning, an install script removed, an ownership change reversed — or is the quarter-end replacement automatic even if the signal only half-cleared?

Collapse
 
dexoryn profile image
Dexoryn •

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.

Collapse
 
james_anderson_h profile image
James Anderson •

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.

Collapse
 
ayoub_hammoudi profile image
Ayoub Hammoudi •

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.

Collapse
 
james_anderson_h profile image
James Anderson •

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.

Collapse
 
ayoub_hammoudi profile image
Ayoub Hammoudi •

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.

Thread Thread
 
james_anderson_h profile image
James Anderson •

Exactly, which is why the switch should get flipped on a calm Tuesday afternoon, long before anyone needs it at 2am.

Collapse
 
bloqarl profile image
Carlos (Bloqarl) •

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?

Collapse
 
james_anderson_h profile image
James Anderson •

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.

Collapse
 
fiwon123 profile image
Felipe Inoue •

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. 🫡

Collapse
 
james_anderson_h profile image
James Anderson •

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. 🫡

Collapse
 
paullovelace profile image
Paul Lovelace •

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?

Collapse
 
james_anderson_h profile image
James Anderson •

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.

Collapse
 
sameerqaiser17 profile image
Sameer Qaiser •

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.

Collapse
 
james_anderson_h profile image
James Anderson •

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.

Collapse
 
ascendra_ventures_92c21f2 profile image
Ascendra Ventures •

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?

Collapse
 
james_anderson_h profile image
James Anderson •

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.

Collapse
 
eye_java_420f1faa10ae8b86 profile image
Nan •

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.

Collapse
 
james_anderson_h profile image
James Anderson •

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?