Blog

Your Stack Has Two Eras. It Only Has One Vulnerability

Rebecca Banks

July 23, 2026

Ninety-eight (98%) percent of applications run on open source software, but your organization likely runs on two very different types of it.

Your production environment might be running Perl scripts older than some of your engineers. On the flip side, it's also running JavaScript packages published last month that an AI assistant suggested this morning. Most engineering leaders treat those as two different risk conversations. The legacy stack is a technical debt problem. The modern stack is a velocity problem. Different owners, different tickets, different retros.

They are, in fact, the same problem. Open source software carries a supply chain with it, regardless of the language it's written in or the decade it was adopted, and a supply chain is only as trustworthy as its weakest, least reviewed link. Age doesn't make a package safer. Novelty doesn't either. What makes a package safer is whether the code was verified before your environment started depending on it.

Two recent, well documented incidents make that case better than a framework can.

Key Takeaways

  • Open source components are equally susceptible to attack whether your stack is legacy or modern. The age or novelty of a language has nothing to do with how exposed a component is; what matters is whether anyone verified it before your environment started depending on it.
  • Legacy and modern languages should not be viewed as separate governance problems. Splitting them into two backlogs and two sets of tooling still allows for a gap: the dependency tree spanning both, which nobody fully owns.
  • Treat your legacy and modern code as one governance model, one point of ingestion, applied the same way. Every component should clear the same bar before it enters your environment, whether it's a decade-old Perl module or a package an AI assistant suggested this morning.
  • Verifying a component once isn't enough; it needs to stay remediated on a defined timeline, a 5-business-day SLA on critical CVEs, not an open-ended one, as new vulnerabilities surface, or the initial verification stops meaning anything.

The Legacy Example: A Perl Module a Nation-State Was Already Waiting On

In December 2023, researchers disclosed CVE-2023-7102, a remote code execution flaw in Spreadsheet::ParseExcel, a Perl module used to parse Excel files.[^1] A crafted spreadsheet could smuggle a malicious expression into the module's eval logic and execute it with the privileges of the process handling the file.[^2] Low complexity. No privileges required.

What makes this worth an engineering leader's attention isn't the flaw. It's who was already positioned to use it.

The module was running inside Amavis, a virus scanner used by Barracuda's Email Security Gateway.[^3] Barracuda wasn't a stranger to this attacker: Mandiant had already linked the same actor, UNC4841, a China-nexus group the FBI later confirmed was state-backed, to a separate zero-day in those same appliances months earlier.[^4] [^5] Barracuda assumed that campaign was closed. It wasn't. Days after patching a new flaw, Barracuda disclosed the same actor had come back through Spreadsheet::ParseExcel.[^6] [^7]

A state-linked espionage group didn't need a new zero-day to get back in. It needed one unreviewed dependency nobody had re-evaluated.

If your environment has anything like Amavis quietly parsing files nobody's looked at twice, your organization has the same risk story. It just hasn't made the news yet.

The Modern Example: A Worm That Spread Through Trust, Not Exploits

In September 2025, researchers disclosed Shai-Hulud, one of the first self-propagating worms in open source software: malware that used a package to infect other packages.[^8]

The entry point was a phishing campaign that spoofed npm and tricked maintainers into "updating" their multi-factor authentication.[^9] Once a maintainer's credentials were compromised, attackers published malicious versions of that maintainer's packages, then used any exposed npm tokens to compromise more packages automatically. Within two days, hundreds of packages were affected.[^10]

No state actor has been attributed to Shai-Hulud, which is what makes it a useful counterpoint. It didn't take a nation-state's resources to compromise a modern ecosystem at scale. It took one phishing email and the trust npm places between a maintainer and everything they publish. A second variant followed in November 2025, and by May 2026 a further resurgence had spread into both npm and PyPI at once.[^11]

There was no code flaw to find or patch here. The attack worked because open source runs on inherited trust, and Shai-Hulud exploited that chain directly, twice, in one of the most actively maintained parts of the JavaScript ecosystem.

If your team pulls packages from npm the way most modern teams do, you were already trusting that same chain. Shai-Hulud just proved what that trust was worth.

Same Root Cause, Different Attacker

Put the two side by side and the shared root cause is hard to miss, even though the attackers could not have been more different. A patient, state-linked espionage group reused an unreviewed Perl dependency to walk back into a network it had already been evicted from once. An opportunistic, unattributed campaign turned ordinary maintainer trust into a self-replicating worm across one of the most modern package registries in existence. One attacker had nation-state resources and eight months of runway. The other needed a single phishing email.

Both got in through the same structural gap: a component nobody was actively verifying, consumed by an organization that had no process to know it needed verifying until the incident was already public.

Where the Blind Spot Hides

This is the trap in treating legacy and modern languages as separate governance problems. It creates two backlogs, two sets of tooling, and two teams each convinced the other one is the bigger risk, while the actual exposure sits in the gap between them: the dependency tree nobody owns end to end.

That trap has a very specific shape in practice. Your CI/CD pipeline and SCA tooling are almost certainly tuned for the ecosystems your teams actively build in today: npm audit, pip-audit, Snyk or Sonatype policies wired into the pipelines your platform team owns and watches every sprint. The Perl service quietly processing invoices in the background or the C++ utility three integrations deep in your build usually isn't sitting inside that same pipeline. It was stood up before your current scanning stack existed, nobody touches it during a normal sprint, and nobody has recently checked whether your CVE feed even covers it. That's not negligence. It's what happens when governance gets built around whichever stack has current attention, rather than around every component actually running in production.

Why "Modern" Doesn't Mean "Safe"

The reverse blind spot is just as real, and just as costly. Teams that treat "legacy" as the primary risk category often assume their actively maintained, modern dependencies are inherently safer because someone is watching them. Shai-Hulud is the counterexample: active maintenance is exactly the surface an opportunistic attacker exploits, because the maintainer's credentials, not the code's age, are the actual point of failure. A governance model built around "old code is risky, new code is watched" misses both failure modes at once.

Closing the Gap

The fix isn't asking your legacy team and your platform team to coordinate more closely, though that helps. It's removing the split entirely: one governance model, one point of ingestion, applied the same way whether the request is for a Perl module or a JavaScript package, so the exposure doesn't depend on which team happens to be paying attention this quarter.

It also gets harder every sprint, not easier. AI coding assistants are pulling in dependencies across every language your teams still run, at a volume no human review process was built to handle. AI didn't create this risk. It removed the last bit of friction, the pause where a developer used to glance at a README or check a last-commit date, that was slowing the old risk down. A ten-year-old Perl module and a package published last week are both one AI suggestion away from entering your environment with zero verification behind them.

That's the standard the ActiveState Curated Catalog exists to enforce.

What "Start Secure, Stay Secure" Actually Means

The fix isn't a better scanner for legacy code and a separate one for modern code. It's a single, consistent standard applied at the point every component enters your environment, regardless of which decade the language was written in.

A scanner would have flagged CVE-2023-7102 after the fact. It wouldn't have stopped Amavis from running an unverified build in the first place. That's the distinction that matters: scanning tells you what's already wrong. It doesn't stop the wrong thing from entering.

That's the model behind the ActiveState Curated Catalog: millions of built-from-source components across 12 language ecosystems, including Perl alongside JavaScript, Python, and Java. Every component, whether it's a Perl module your team has depended on since before your current engineers joined the company or a JavaScript package an AI assistant suggested this morning, is built from source inside SLSA Level 3 infrastructure, scanned for CVEs and malware, and verified before it ever reaches a developer or an AI coding tool. “Start secure” means nothing when components enter your stack without that verification. 

“Stay secure” means it doesn't stop there: when a community-approved fix becomes available, ActiveState builds the component and delivers it, with a contractual service level agreement of 5 business days for Critical CVEs once a fix is available upstream, so a component verified once doesn't quietly drift back into risk a year later.

This isn't a new posture for ActiveState. We have been securing open source software since 1997, when it began building commercial, enterprise-grade distributions of Perl, Python, and Tcl, the same legacy languages still running in production at organizations today.[^12] That history matters here specifically: the same discipline now applied to modern, AI-accelerated dependency intake was built first on the languages engineering teams assume are too old to matter. They were never the safe half of the stack. They were just the half nobody was watching.

Your Perl service and your Node.js microservice will never share a deployment pipeline. They already share a risk model. The question worth asking is whether your governance does too.

Talk to ActiveState about auditing your legacy and modern dependencies against the Curated Catalog.

Rebecca Banks is Senior Product Marketing Manager at ActiveState, where she leads go-to-market strategy for software supply chain security. She is currently a contributor to the Linux Foundation and OpenSSF 2026 AI Security Study on global AI security maturity for enterprises and critical infrastructure organizations.