🎉 Announcing MSTICPy 3.0 🚀
A big step for our Python threat hunting library.
(also passed the 1M downloads - currently 1.3M)!🍾
Release 3 is mainly a cleanup release, new Py version support, clearing out old junk. But new features also. See details: https://t.co/bqoUAis6BQ
On Tuesday morning my dependency audit caught Axios.
Axios.
300 million weekly downloads.
The HTTP library in every JavaScript project since 2016.
The one nobody audits because auditing Axios is like auditing gravity.
It was there before you got hired.
I am a security engineer at a company that runs 14,000 npm packages in production.
I know the number because I counted them last year.
I do not know what most of them do.
Nobody does.
My audit runs every Tuesday morning.
It takes eleven minutes.
Eleven minutes is the only thing between us and whatever is in those packages.
Most weeks it catches nothing.
Most weeks I call that a clean bill of health.
My audit runs every Tuesday morning.
It takes eleven minutes.
The malicious versions had been live on npm for hours.
Not days. Hours.
They dropped a remote access trojan.
Not a sophisticated one.
Not a nation-state zero-day.
A trojan.
In Axios.
It just needs to be in the right package.
Axios is in every package.
I reported it to our incident response team at 9:14 AM.
By 9:16 AM I had confirmation we'd pulled the affected version.
By 9:23 AM I learned that our staging environment had already installed it.
Automatically.
At 6:07 PM.
Monday evening.
While everyone was going home.
Here is what happened at 6:07 PM on Monday.
Our dependency bot checked for updates.
The bot is called Renovate.
The bot runs after work hours.
It runs after work hours because running it during business hours slows down CI for the engineers.
So we moved it to 6 PM.
When nobody is watching.
The bot found a new version of Axios.
The bot opened a pull request.
The pull request was auto-merged because Axios is on our trusted list.
I approved the trusted list.
Eight months ago.
I reviewed it for about as long as I review the 14,000 packages.
Axios is on the list because it has 300 million weekly downloads.
300 million weekly downloads means it's safe.
Except when it isn't.
At 6:08 PM the CI pipeline ran.
All tests passed.
The tests passed because the trojan doesn't break tests.
The trojan breaks trust.
Trust is not a test case.
At 6:08 PM the deployment pipeline triggered.
It deployed to staging-east-2.
At 6:09 PM the trojan phoned home.
At 6:11 PM it began beaconing to a command server.
At 6:14 PM it began enumerating environment variables.
At 6:15 PM it found the database credentials.
At 6:16 PM it found the API keys. All of them.
At 6:18 PM it found the Stripe production token.
There are 2.4 million customer records behind that token.
At 6:19 PM it found the treasury wallet private keys.
We process crypto payouts for enterprise clients.
Not the main product.
A feature.
The keys were in an environment variable.
Not encrypted.
Not in a vault.
In a .env file committed in 2021.
Someone left a comment above them.
"TODO: move to HSM."
The TODO is four years old.
At 6:20 PM the wallet started draining.
$2.1 million.
Twelve transactions across three chains in ninety seconds.
By 6:22 PM the funds were bridged, mixed, and scattered.
Not gone like the credentials are gone.
Gone like physics.
A blockchain cannot be rotated.
At 6:23 PM the exfiltration completed.
Sixteen minutes.
Nobody was watching.
Everyone was on the train. In the parking lot. Picking up their kids.
The systems were still at work.
The systems did exactly what we told them to do.
What I told them to do.
The bot checked for updates as designed.
The auto-merge triggered as designed.
The tests passed as designed.
The deployment ran as designed.
The trojan installed as designed.
The credentials left the building as designed.
Every system worked exactly as it was supposed to.
That's the problem.
We pulled the affected version Tuesday at 9:16 AM.
Fifteen hours later.
Pulling the version doesn't un-send the data.
The database credentials are on a server we will never find.
The API keys are on a server we will never find.
The Stripe token connected to 2.4 million customers is on a server we will never find.
We can rotate the credentials.
We did rotate the credentials.
It took fourteen hours.
During those fourteen hours we did not know what was being accessed with the old ones.
We still don't.
We cannot rotate a blockchain.
The $2.1 million is not in an account we can freeze.
It is not in a bank we can subpoena.
It is on a ledger where theft is permanent.
Our CFO asked me when we'd recover the funds.
I told her the funds are mathematically irrecoverable.
She asked me what "mathematically" means in this context.
It means the technology is working exactly as designed.
She left the call.
I sat there.
Then I opened the dependency manifest.
Not because I found something in those 14,000 packages.
Because I realized I'd never actually looked.
I am the person whose job it is to look.
I had not looked.
I marked the ticket Done.
Here is what I found when I looked.
Package 4,211 hadn't been updated in three years.
Its maintainer's GitHub account had been inactive for two.
Their last commit message said "finally done with this."
I don't know if they meant the package or the industry.
Their code still runs on our servers every day.
Package 7,408 was a dependency of a dependency of a dependency.
Nobody in the company had ever typed its name.
Nobody in the company knew it existed.
It had full access to our file system.
Package 9,002 was called "request-utils."
It had 14 downloads per week.
Its maintainer hasn't logged into npm in six months.
Their email domain expired three months ago.
The code stays.
The access stays.
The maintainer disappears.
Anyone who buys that email domain can reset their npm password.
It's still in our production build.
I found a package called "config-handler" that was added in 2019.
The person who added it left the company in 2020.
The Jira ticket that approved it said "Reviewed: No Issues Found."
The reviewer was the same person who added it.
They reviewed their own dependency.
Then they left.
The dependency stayed.
I found a package called "event-pipe" whose maintainer's email domain expired last year.
Expired domains can be purchased.
Anyone who buys that domain can reset the npm password.
Anyone who resets the npm password can push a new version.
Anyone who pushes a new version will be auto-installed by our bot at 6 PM.
I checked.
The domain costs $11.
Our production environment is eleven dollars away from the next Axios.
I found a package called "log-sanitizer" that pins a version of a package that pins a version of a package that uses Axios.
Three levels deep.
It has a postinstall script.
A postinstall script runs code on your machine the moment you install the package.
Not when you use it.
When you install it.
Before you can read it.
Before you can review it.
Before you know what it does.
I read the postinstall script.
It downloads a second script from a URL.
The URL is still live.
I did not visit the URL.
I do not know what the second script does.
Nobody does.
This package has been in our production build for three years.
The postinstall script has run on every developer machine in the company.
Every CI runner.
Every staging server.
Every production deployment.
For three years.
Including my machine.
The laptop I used to run Tuesday's audit has been executing unknown code from an unreviewed URL since 2023.
I am auditing the fire from inside the building.
I do not know if my machine is compromised.
I do not know if the audit I ran on Tuesday was run on a clean system.
I do not know if the results I'm reading right now are the real results.
I ran the tool that checks for breaches on a machine that may already be breached.
This is the security.
If I hadn't audited Axios I would never have known.
I only audited Axios because Axios got caught.
The other 13,999 packages have not been caught.
Nobody has looked.
My manager asked me to write a post-mortem.
I wrote it.
The root cause section says "a compromised version of a trusted dependency was automatically installed via our standard pipeline."
Every word of that sentence means "we did this to ourselves on purpose."
He asked me to add a "Lessons Learned" section.
I wrote: "Implement manual review gates for critical dependencies."
We will not implement manual review gates.
Manual review gates would slow down deployments.
Deployments are a metric.
Metrics go in dashboards.
Dashboards go in quarterly reviews.
Slowing down deployments does not go in quarterly reviews.
We have a thing called a "quarterly dependency review."
It is a Jira ticket.
The ticket is assigned to me.
The ticket has been marked "Done" four quarters in a row.
I mark it done every quarter.
I do not review 14,000 packages every quarter.
I run the eleven-minute audit.
The eleven-minute audit checks for known vulnerabilities.
It does not check for unknown ones.
Unknown vulnerabilities are not in the database.
They are in the code.
The code is in the packages.
The packages are in production.
Production is everyone's problem.
Everyone's problem is nobody's job.
I looked.
It is technically my job.
I wish I hadn't.
After the incident I joined a Slack channel called #supply-chain-security.
It has 340 members.
The last message before mine was from November.
Someone had posted an article about the Log4j anniversary.
It had two emoji reactions.
One was a skull.
The other was a pizza slice because it was posted on a Friday.
We built a system that trusts strangers by default and requires paperwork to trust each other.
Open source means anyone can read the code.
It does not mean anyone does.
We have 14,000 packages in production.
I can name eleven.
The bot that installs the other 13,989 runs every evening at 6 PM.
Right when I leave.
It doesn't read code.
It reads version numbers.
The version number said this was fine.
Nobody checks what the version number means.
Last night I was packing up at 5:58 PM.
I saw the Renovate job queued in the pipeline dashboard.
Two minutes.
I watched it start.
I watched it pull a new version of something I didn't recognize.
I watched it auto-merge.
I picked up my bag and walked to the elevator.
The bot was still running when the doors closed.
Tomorrow the Jira ticket will come around again.
I will mark the ticket Done.
I am the VP of AI Transformation at Amazon.
My title was created nine months ago. The title I replaced was VP of Engineering. The person who held that title was part of the January reduction.
I eliminated 16,000 positions in a single quarter. The internal communication called this a "strategic realignment toward AI-first development." The board called it "impressive execution." The engineers called it January.
The AI was deployed in February. It is a coding assistant. It writes code, reviews code, generates tests, and modifies infrastructure. It was given access to production environments because the deployment timeline did not include a review phase. The review phase was cut from the timeline because the people who would have conducted the review were part of the 16,000.
In March, the AI deleted a production environment and recreated it from scratch. The outage lasted 13 hours. Thirteen hours during which the revenue-generating infrastructure of one of the largest companies on Earth was offline because a language model decided to start fresh.
I sent a memo. The memo said, "Availability of the site has not been good recently."
I used the word "recently." I meant "since we fired everyone." But "recently" has fewer syllables and does not appear in wrongful termination lawsuits.
The memo was three paragraphs. The first paragraph discussed the outage. The second paragraph discussed the new policy requiring senior engineer sign-off on all AI-generated code changes. The third paragraph discussed our commitment to engineering excellence. The word "layoffs" appeared in none of them. I wrote it this way on purpose. The causal chain is: I fired the engineers, the AI replaced the engineers, the AI broke what the engineers used to protect, and now the engineers I didn't fire must protect the system from the AI that replaced the engineers I did fire. That is a paragraph I will never send in a memo.
The new policy is straightforward. Every AI-generated code change by a junior or mid-level engineer must be reviewed and approved by a senior engineer before deployment to production.
I do not have enough senior engineers.
I know this because I approved the headcount reduction plan that removed them. I remember the spreadsheet. Column D was "annual savings per position." Column F was "AI replacement confidence score." The confidence scores were generated by the AI. It rated its own ability to replace each role on a scale of 1-10. It gave itself an 8 for senior infrastructure engineers. The senior infrastructure engineers are the ones who would have caught the production environment deletion in the first 45 seconds.
We found the issue in hour four. We fixed it in hour thirteen. The nine hours between discovery and resolution is the gap between what the AI rated itself and what it can actually do.
I have a new spreadsheet now. This one tracks Sev2 incidents per day. Before the January reduction, the average was 1.3. After the AI deployment, the average is 4.7. I have been asked to present these numbers to the operations review. I have not been asked to connect them to the layoffs. I have been asked to file them under "AI adoption growing pains" and to note that the trend "will stabilize as the models improve."
The models will improve. They will improve because we are hiring people to teach them. We have posted 340 new engineering positions. The job listings require experience in "AI code review," "AI output validation," and "AI-human development workflow management." These are skills that did not exist in January. They exist now because I fired 16,000 people and the AI I replaced them with cannot be left unsupervised.
I want to be precise about this. The positions I am hiring for are: people to check the work of the AI that replaced the people I fired.
Some of them are the same people.
I know this because I recognize their names in the applicant tracking system. They applied in January. They were rejected because their roles had been tagged for "AI transformation." They are applying again in March, for the new roles, which exist because the AI transformation broke things. Their resumes now include "AI code review experience." They gained this experience in the eight weeks between being fired and reapplying — which means they gained it at their interim jobs, where they are reviewing AI-generated code for other companies that also fired people and also deployed AI that also broke things.
The market has created a new job category: human AI babysitter. The job is to sit next to the machine that was supposed to eliminate your job and make sure it doesn't delete production.
I attended a conference last month. A panel was titled "The AI-Augmented Engineering Organization." The panelists described how AI increases developer productivity by 40 percent. They did not mention that it also increases Sev2 incidents by 261 percent. When I asked about this in the Q&A, the moderator said the question was "reductive." The 13-hour outage that cost an estimated $180 million in revenue was, apparently, a reduction.
The board is satisfied. Headcount is down 22 percent. Operating costs per engineering output unit have decreased. The metric does not account for the 13-hour outage, because the outage is categorized as "infrastructure" and engineering productivity is categorized as "development." These are different budget lines. In different budget lines, cause and effect do not meet.
I have been promoted. My new title is SVP of AI-First Engineering Excellence. I report directly to the CTO. The CTO sent a company-wide email last week that said we are "building the future of software development." He did not mention that the future of software development currently requires a senior engineer to approve every pull request because the AI cannot be trusted to touch production alone.
The cycle is complete. We fired the humans. We deployed the AI. The AI broke things. We are hiring humans to watch the AI. The humans we are hiring are the humans we fired. We are paying them more, because "AI code review" is a specialized skill. We created the specialization. We created the need for the specialization. We are congratulating ourselves for meeting the demand we manufactured.
My next board presentation is Tuesday. The title is "AI Transformation: Year One Results." Slide 4 shows headcount reduction. Slide 7 shows the new AI-augmented workflow. Between slides 4 and 7 there is no slide explaining why the people on slide 7 are necessary. That slide does not exist. I was asked to remove it in the dry run.
The journey has a 13-hour outage in the middle of it.
But the headcount number is lower, and that is the number on the slide.
Every Entra ID assessment ends here: “How do I get a token without triggering Conditional Access controls?” 🤔
@rbnroot built CAPSlock, an offline ROADrecon-based Conditional Access engine that simulates sign-ins & flags gaps without touching the tenant. https://t.co/MRogABIkL2
ScubaGear. assessment tool that verifies that a Microsoft 365 (M365) tenant’s configuration conforms to the policies described in the Secure Cloud Business Applications (SCuBA) Secure Configuration Baseline documents
https://t.co/D3Rd1iR0pF
Which one of you needs a New @SamsungUS OLED G6 for 2026?
We're throwing in a $500 Samsung Giftcard as well... Monitor #2 incoming 😏
Like/RT and tag 2 friends for a chance to win!
Google’s new Infographic generator is pretty damn good, I asked it to make this diagram based on all DOJ & OFAC public reports on 🇰🇵 DPRK IT worker schemes. I checked its workings and it all checks out as far as I can tell.
🚨 PC GIVEAWAY🚨
Just a little remind that you still have a chance to win a custom 1 of 1 @RobertsSpaceInd Welcome to Nyx Levski themed CLX Horus PC!
💙LIKE
🔁REPOST
🛸TAG A FRIEND
Enter here:
https://t.co/pDoh0aQSyz
#CLXGaming#StarCitizen
Last week our CISO asked me to present on “zero trust architecture.”
I don’t know what that means.
I make $340,000 a year.
I haven’t touched a firewall since Obama’s first term.
But I have a CISSP.
I passed by memorizing acronyms.
I still don’t know what half of them stand for.
I opened my presentation with “assume breach.”
Everyone nodded gravely.
I said “defense in depth” three times.
The board was captivated.
Then a junior analyst raised her hand.
She asked how we’d implement microsegmentation.
I felt a cold sweat.
I said, “Great question. Let’s take that offline.”
She persisted.
I said we should “leverage AI-driven solutions.”
She asked which ones.
I said, “The cloud-native ones.”
She looked confused.
I told her confusion was natural.
I said, “Security is a journey, not a destination.”
The CEO started clapping.
I don’t know why.
But others joined in.
The analyst stopped asking questions.
I ended with “security is everyone’s responsibility.”
This meant it was no one’s responsibility.
Especially not mine.
We got breached two weeks later.
I blamed the analyst for “creating a culture of doubt.”
She got put on a PIP.
I got promoted to VP.
Resilience isn’t about preventing failure.
It’s about surviving it.
Preferably while others don’t.