The Software Industry Is Outsourcing Its Way Into a Security Nightmare

, 9 minute read

Imagine a company celebrating because it saved 40% on software development.

There’s a slide deck. A graph going up and to the right. Someone from finance is delighted. A manager has used the word efficiency at least eleven times, and the people who approved the decision are probably already discussing their bonuses.

Six months later, nobody knows why the billing system occasionally charges customers twice.

But look at those savings.

We’ve reached this weird point in software development where companies will spend millions building something, then decide the people who understand how it works are an unnecessary expense.

The thinking seems to be that code is code. Developers write code. Therefore any developer can work on any code. Just give them access to the repository, a Jira ticket and perhaps a Copilot subscription. What could possibly go wrong?

Quite a lot, as it turns out.

A codebase isn’t a colouring book

I’ve been building software professionally for over 18 years, and one thing I’ve learned is that the code is often the least interesting part of a complicated system.

The important stuff is usually everything surrounding it.

Why does this customer get billed differently? Why does that integration retry three times but this one doesn’t? Why is a field technically optional but absolutely cannot be empty for a particular customer? Why does this horrible-looking conditional exist?

Sometimes the answer is that someone wrote terrible code in 2016.

Other times it’s because removing that conditional will quietly break a business process that handles millions of dollars.

You don’t know until you understand the system.

And no, reading the README isn’t the same thing. Especially when the README was last updated when everyone was still arguing about whether jQuery was dead.

A mature codebase is full of decisions. Some good, some terrible, some made under pressure, and some that only make sense if you know what happened five years ago.

A developer who understands the business can look at a seemingly simple change and say, hang on, what happens to refunds when we do this?

Someone without that context might see a redundant check, remove it and proudly mark the ticket as done.

Both developers wrote code. Only one understood the problem.

The cheapest developer can become the most expensive decision

Before someone gets excited, this isn’t a dig at developers in any particular country. There are incredible engineers everywhere, and there are plenty of expensive developers who couldn’t debug a toaster.

The problem isn’t geography. It’s the belief that software engineering can be purchased by the hour like printer paper.

Find the lowest rate. Throw requirements over the fence. Count tickets. Repeat.

Except software doesn’t work like that.

A developer who spends two hours asking awkward questions before changing something might save a company weeks of cleanup.

A developer who closes twelve tickets without understanding the wider system might look fantastic on a dashboard right up until the production incident.

And guess which one tends to look more productive to someone whose entire understanding of engineering comes from a spreadsheet?

You can make an outsourcing arrangement work brilliantly. Clear ownership, proper onboarding, access to domain experts, good reviews, tests, and people who are actually responsible for what ships.

Or you can treat developers as interchangeable hands attached to keyboards and act shocked when the results are shit.

The second option seems particularly attractive to organisations that believe engineering quality is measured in story points.

AI has given the slop factory a turbocharger

Now add AI to the mix.

I use AI to write software every day. I love it. I’m building things faster than I ever could before, and I have no interest in pretending manually typing every line of code is some sacred craft.

But there’s a fairly important distinction between using AI to help build software you understand and asking AI to make changes to a system you don’t.

One is a productivity multiplier.

The other is a very efficient way to produce bugs you can’t explain.

A coding agent can inspect files, generate a convincing implementation, write tests and produce a pull request with a lovely summary of what it changed.

It can also misunderstand an undocumented business rule, introduce a security problem or confidently preserve the wrong behaviour.

If the person reviewing that code doesn’t understand the system either, who’s catching it?

The AI says the task is complete. The developer says the task is complete. The ticket says the task is complete.

Everyone agrees. Beautiful teamwork.

Meanwhile, the actual customer workflow is fucked.

AI doesn’t magically supply missing domain knowledge. It can help uncover it, and sometimes do a surprisingly good job, but it can’t guarantee that the requirements nobody wrote down have been respected.

That’s why the ability to generate code faster doesn’t automatically mean you’re delivering better software.

You can now produce a month’s worth of bad decisions before lunch.

Progress.

Security doesn’t care how many tickets you closed

The security side is where this gets especially ugly.

A system can appear to work perfectly while being dangerously insecure.

The button works. The form submits. The API returns a 200. Someone takes a screenshot and moves the ticket to done.

Fantastic. Did anyone check whether the endpoint verifies that the logged-in user actually owns the record they’re requesting?

What about tenant isolation? Authorisation? Sensitive data in logs? Race conditions around payments? Webhooks that accept whatever someone sends them? Dependencies that haven’t been updated since the pandemic?

Those problems don’t always announce themselves with a nice red error message.

And they’re exactly the sort of things that get missed when the goal is to ship as much code as possible with as little review as possible.

An AI model can generate insecure code. So can a human. The difference is that AI can help produce an impressive amount of it very quickly, and the output often looks polished enough to discourage a second look.

A green test suite isn’t a magic shield either. Tests written from the same mistaken assumptions as the implementation can happily confirm that the wrong thing works exactly as intended.

The real question isn’t whether the code compiles.

It’s whether anyone involved understands what could go wrong when it runs.

Nobody owns the mess, but everyone owns a dashboard

There’s another lovely feature of the race to the bottom: responsibility gets spread so thin that it practically disappears.

One team writes the requirements. Another implements them. Someone else reviews the pull request. A different team deploys it. Support discovers the problem. Operations gets paged at 2am.

And then everyone has a meeting to discuss how this could’ve happened.

Maybe they create a new Jira workflow.

Maybe they add another approval step.

Maybe someone suggests a retrospective, because nothing says we’re learning like scheduling an hour to discuss the consequences of the previous hour-long meeting.

What rarely gets asked is whether the people doing the work had enough context, time and authority to do it properly in the first place.

You can’t process your way out of missing engineering ownership.

Someone has to understand the system beyond their assigned ticket. Someone has to care about what happens after the code merges. Someone has to be allowed to say this change is a bad idea, even if it makes the sprint report look worse.

And someone has to listen.

The senior developer who asks questions isn’t slowing you down

I’ve noticed something funny about experience.

When you’re starting out, you think the hard part is writing code.

Then you get older and realise the hard part is figuring out which code should exist at all.

A good engineer doesn’t just ask how to implement a feature. They ask why it’s needed, what it affects, what happens when it fails and whether there’s a simpler way.

Sometimes the most valuable thing they do all week is stop a bad idea before anyone writes a line of code.

Good luck measuring that in commits.

It’s easy to look at an experienced developer asking questions and assume they’re being difficult. Especially when someone else can paste a prompt into an AI tool and have a working demo fifteen minutes later.

But a demo isn’t a production system. A completed ticket isn’t a successful outcome. And the absence of immediate complaints doesn’t mean nothing broke.

The industry has spent years obsessing over developer velocity while sometimes forgetting to ask whether we’re moving in the right direction.

Driving off a cliff at 200km/h is technically very fast.

The bill always arrives eventually

The trouble with bad software decisions is that they often take time to become expensive.

A rushed change might work today. The missing validation might not matter until a particular customer does something unusual. The broken assumption might sit there quietly until the business launches a new product or processes its millionth transaction.

Then the bill arrives.

Outages. Security incidents. Customer complaints. Lost revenue. Emergency fixes. Weeks spent figuring out why something that used to work doesn’t anymore.

And suddenly those development savings don’t look quite so impressive.

Of course, by then the original spreadsheet has been archived, the project has a new name and the people who approved the strategy have moved on to something else.

The developers left holding the mess get told to improve quality without slowing delivery.

Preferably by Friday.

Outsourcing isn’t the villain. Treating engineering like disposable labour is.

There’s nothing inherently wrong with outsourcing development. Or hiring distributed teams. Or using AI to generate code.

I’ve worked remotely for years. I’ve collaborated with developers around the world. And I use AI constantly because it makes me better at what I do.

The problem starts when businesses confuse access to people who can write code with access to people who can responsibly build and maintain a system.

Those aren’t the same thing.

If you’re going to bring new developers into an existing product, invest in onboarding. Give them access to people who understand the business. Document the weird rules. Make ownership clear. Review changes properly. Test the stuff that matters. And don’t punish people for slowing down when something looks wrong.

If you’re using AI, great. Use it to understand the codebase, write tests, spot risks and get the boring work done faster. But don’t mistake a convincing explanation from a model for actual understanding.

And if you’re making decisions about engineering teams, perhaps stop asking how cheaply you can get code written and start asking how much it costs when the code is wrong.

Because software isn’t finished when someone merges a pull request.

It’s finished when it solves the problem, keeps solving the problem and doesn’t quietly create five new ones.

You can outsource the code. You can’t outsource the consequences.

Although I’m sure someone is already preparing a slide deck explaining how to do exactly that.

Comments