This incident exposes the terrifying fragility of modern supply chains, where a simple logging error nearly compromised the entire PHP ecosystem. It serves as a powerful reminder that "failing loud" is often the only thing standing between a minor bug and a global security disaster.
Deep Dive
Voraussetzung
- Keine Daten verfügbar.
Nächste Schritte
- Keine Daten verfügbar.
Deep Dive
PHP barely avoided disasterHinzugefügt:
Yesterday, the PHP community barely avoided absolute [music] disaster. That's how this blog post here starts, explaining how a supply chain vulnerability was detected in in composer, and it had the possibility for things to go [music] very bad for PHP.
It's funny how how this thing happens just a week or two after that previous package that got compromised. I made a video about that if you want to [music] watch it after this one. So, let's read through here to see what actually happened. [music] In short, for a 14-hour window yesterday, which is already a couple of days, yeah, last week basically, any PHP project that was hosted on GitHub ran composer install after shivammathur set a PHP in GitHub actions, which is basically all PHP projects that rely on PHP used this action here.
That ran that workflow against its main branch and was triggered by on push, on pull request target, or on schedule.
Other triggers are likely impacted, but these are the most common. When you had that situation, you had a chance that the GitHub token, typically write enabled, could would be publicly logged in GitHub actions log on every build.
For those 14 hours, any malicious actor can uh monitoring one of those uh repos could have pushed [snorts] new tags and commits without anyone noticing. If properly leveraged, this would have been the largest supply chain breach the PHP ecosystem has ever seen. To make matters worse, we're not even done yet. The same change may roll out again in a week at GitHub's discretion, and there's a chance it bleeds beyond PHP in other ecosystems. That's pretty bad. Uh we're going to continue to read through the rest of the post as well, but what they're saying here is that for 14 hours, if people were monitoring GitHub actions and actually got a hold of your GitHub token, they would have been able to make changes to any repository that leaked their token uh without anyone noticing, tag those changes, which would then automatically be released through composer, and anyone who pulled them in would get the affected release. Just imagine this happening with Laravel, for example, which has, I don't know, let's see, like an average of 300,000 installs per day. So, for 14 hours, let's just say 150,000 installs could have been affected by this. Could just pull in the affected version without anyone ever noticing.
That's That's the the extent of where this uh vulnerability goes to. As far as people know, I don't think this has been exploited, but we'll read about it. The person who discovered Damien here uh maintains a set of GitHub actions for the Magento community. They exist to lower the barrier uh uh the barrier to entry into the Magento and Magento OS ecosystem, so that a company wanting to ship or store an extension can go get working with CI without much fuss. Yeah, that's that's what all these actions do, right? To just make it easier to to use GitHub actions or CI platforms. For me, that means maintaining a wide variety of GitHub actions that work across many versions of Magento and Magento OS. To handle that, I main uh maintain a fairly complex action called supported version, which holds the complexity matrix for every supported version of Magento and Magento OS.
Yeah, it's it's like the more GitHub actions I can avoid, the better in my case, to be honest, but yeah, it's not my thing. Uh I have for Tempest, I'm so happy that that other people are helping me out with GitHub because anything DevOps {slash} CI related is is really like, nah, just give me PHP. Uh while I was working on this action for the Magento release yesterday evening, my test started failing intermittently after some small changes to this action. The failures didn't track with anything I changed. When I went to debug the failure, I saw the following. In base IO, which I think is is a composer class, we'll we'll see in a minute. Your GitHub OAuth token for GitHub contains invalid characters and then just prints the old token. I was surprised more than anything. My first instinct was that screwed something up.
Bash scripting processing the composer auth I I feed into my actions. I've seen secrets in my logs before whenever a script clobbers a string with new lines, so this looked like my fault. It took me about an hour, I'm not a very fast person, to realize that my actions weren't at fault. Nothing my actions Nothing I changed could have triggered this outcome. Shortly thereafter, I concluded that something outside my actions was wrong and wrong in a way that probably wasn't unique to me. So, I asked Claude, as you do, giving it all the context I just uncovered. Claude pointed out two things I'd seen but not noticed. The logged token wasn't a typical PAT. This is like a personal access token. It had a GitHub app ID embedded in it, specifically this one. That small an ID suggests a very old GitHub app, most likely GitHub Actions bot. And then Shiva matter said a PHP automatically inserts that token into composer auth as the GitHub OAuth value. That makes sense. At this point, I still wasn't too concerned. I naively thought this was one a one-off bug in how tokens had been generated. I knew the GitHub Actions token expires when the job ends.
I had looked at workflow token security on my repos before, and the leak was on a pull request, so the token's permissions were already fairly restricted. I didn't lose sleep over it.
I merged my changes to my main branch from my pull request, fully expecting that this would recover and I could move about my day. Then my main branch CI failed on a completely different job and I became concerned. Earlier yesterday, uh 10 sec router was breached because their main branch's cache was points poisoned by a pull request. Yeah. I was very concerned that something like that uh was going on here. It was at this point with the read writes token visibly in my logs that I knew something was up and that I had to dig in and act quickly. I had Claude triage the the error message from the log in base IO here, figuring that it would do a better job at scouring Composer's project code for the offending line faster than I would. And it uncovered it immediately.
And so this is the code that throws the exception and then actually includes the token in the exception message, which is probably not really a smart thing to do, but you know, who thinks about these things, right? Allowed characters for GitHub tokens are these plus dots, which are at some point used for GitHub app app integration tokens. So basically, if your Composer checks if your token doesn't match a given format, it will throw an exception because then it's an invalid token. But what it also does is print that invalid token in the exception message. It also uncovered, and huge props to Claude here, this would have taken me hours on my own, that that GitHub was rolling out a change to their token formats. Notice about upcoming new format for GitHub app installation tokens. So the token format has an installation tokens, which will be changing to uh yeah, another format. A change to the token format. At this point, I knew very distinctly that, which is my inner child telling me, and now you, that something is undeniably wrong. How I acted. Here's what I did in order. I disabled all actions in my organization, better safe than sorry. I pinged the major OS Discord and asked them to pause all GitHub actions immediately. I submitted a security advisory report to the composer project based on my quick analysis. I reached out to Jessica who would have previously connected with on on fake repository hoping she would quickly and quietly get me in touch with people at the large frameworks at risk.
My request she reached out to Taylor and asked him to pause Laravel GitHub actions. I pinged ModMail users on Laravel's Discord. Shortly thereafter their actions were disabled. I'm not sure which channel did it. I called a colleague and started trawling GitHub repos to confirm I wasn't the only one affected. I pinged Barry on Slack informing him to disable his GitHub actions as well. As an aside, I'm a small fish compared to many of the well-known developers in the PHP ecosystem. I didn't know if anyone would take me seriously and I wanted to make sure no one was hurt simply because I wasn't loud enough. I fully understand responsible disclosure. I filed several HackerOne reports before and I've received bounties for some of them. But when the breadth of concern is this large, this painfully obvious, this critical, and had been 4 hours since I'd opened the advisory without a response, I felt I needed to take my own action through quiet fast channels to make sure the largest parts of the ecosystem were protected as quickly as possible. And what were the chances I could get get Niels or Jordi, these two are the maintainers of composer, on a call minutes after I submitted an advisory?
First, what were the chances I could get a HackerOne report submitted and reviewed by GitHub in the same time frame? You have to understand when something like this happens, you don't want people to know about it because there might be bad people out there who will abuse it as soon as it's getting known. You want the right people to know about it who can fix it. In this case, that's composer who's the root of the issue. You want to make sure that the the big players like in Symphony and Laravel are are aware of it as well. But you really don't want to be out there in the open shouting there's a huge security issue because it it it's it's not good. In reality, likely no one was compromised. In reality, likely no one next week will be compromised when GitHub rerolls the change out. Composer has already issued patches to all major versions of Composer combined, even non-supported non-LTS versions. Ash even matter has has already bumped their main line to update to a secure version of Composer as well. That being said, the bulk of the reason that this wasn't as impactful is primarily due to three components. First, Composer validator fails fast. The exception bubbles up and the job dies. This causes the GitHub token to be revoked the moment the job exists. From the attacker's perspective, the token is live for the seconds between Symphony console writing the message to standard error and the runner tearing down the job. That window is real, but it's narrow. To be clear, it's not narrow enough to be unexploitable. I have a proof of concept which showcases direct writes to main using the stolen token in this very narrow window. With a sufficiently sophisticated polling setup, you can do this in under a second. The second reason is that the failure was loud. While I was running through GitHub trying to figure out whether I was the only person hit, I could see the failure landing in CI logs all across most of the top PHP repos, Laravel, Composer itself, WooCommerce, OpenEMR, Sylius, Laravel Debugbar, and many other smaller projects. Anyone watching their own CI saw it within minutes. If it was quiet, then it would have gone on far longer. Who reads the build logs of a passing job? The third reason is that Composer shipped patched releases and the advisory went out the next morning. GitHub paused the format rollout shortly afterward. The response time from notification to resolution was remarkable. Now, the potential impact.
Imagine someone saw what I saw earlier than I did, and who decided to do something with it instead of reporting it. The primitive you have is GitHub token from a push event on a repo's default branch printed in plain text to world readable log live for some number of seconds before the job ends. The tokens permissions vary by repo, but for the long tail of older apps and most maintainer configured workflows, the default is still contents rights. The legacy permissive default. That's enough to push commits and tags to the repo it came from. Worse, the leak doesn't necessarily end precisely when the failing step ends. A pattern I have in my own workflows, and that a lot of Magento and broader PHP workflows have, looks like this. The intent is fine.
When CI fails, freeze the workspace state into an artifact so a human can debug it later. In this bug's context, the step compounds the leak. The core concept is that it extends the life token window, and the upload runs after composer has crashed and printed the token to standard error, but before the runner can tear down uh the job and revoke the token. Until the upload finishes, the token is still accepted by the GitHub API. For a small sandbox, that's seconds. For a fat integration test fixture with database dumps, it's many minutes, yeah. And an attacker polling failed runs gets a longer window to act, not a shorter one. In the PHP ecosystem, push access to a repo is push access to packages, yeah. Packages pulls GitHub for new tags, that's true, yeah.
The moment you push version blah blah blah to vendor package, packages publishes it, and the next composer install or composer update anywhere in the world pulls your release. Composer plugins execute code during install, although not entirely true, you have to accept this. That's That's what we saw in the in the previous video. But But still, it's a good point. So, a poisoned tag on a sufficiently popular package is a remote code execution on every developer's machine, CI runner, and production deploy that runs composer install between the tag landing and rollback. Look at the names on the list above. Composer itself was failing in CI during this window. So was Laravel, so was Sylius. A successful XZ and push against anyone of those is a top 50 in history supply chain incident. Against several of them simultaneously with the same primitive, it would be the largest one ever in PHP and probably top three against all ecosystems. The Magento angle, since it's the corner of the world I actually live in, Magento and Mejo as distributions are composer projects. Composer self update pulls from getcomposer.org, which redirects to GitHub releases. A poisoned composer binary would execute under every Magento store deploys pipeline in the next build. Most e-commerce platforms do not have the supply chain hygiene of, say, a rail shop. The blast radius would not have been bounded by who reads Hacker News. It is It is pretty true. And I mean, we've become pretty lazy because of the tools we're using. I talked about this in the previous video as well, like the dependency hygiene concern. It's convenient that we can just, you know, pull in stuff that was published to GitHub and just is available through composer and it's all automated. It's It's super convenient. And it has so much potential of doing so much harm.
So, the mitigating factors, a short list of things that intentionally or otherwise kept this from going off.
Composer fails fast and fails hard. I do remember when when running the the 100 million row challenge, that one of the tokens got leaked there in a GitHub action while we were debugging some stuff. I actually got an email from GitHub telling me that my token leaked, but I guess that since these these are GitHub action specific tokens, maybe GitHub doesn't check on those and only checks for like real personal access tokens that were generated on on the account or organization level. But that that would have been helpful, I reckon.
Maybe a good lesson here is that you should never ever print out tokens in exception messages, but you know, you live and you learn. GitHub token is repo scoped even after even a successful exploit against Laravel framework doesn't get you anywhere near Laravel Passport. Each repo's token can only act against a repo. Although for mono repos, you get a much wider scope. But yeah, stuff like Laravel Passport indeed is is a separate package. The 6-hour ceiling, GitHub hosted runners kept workflow tokens at 6 hours. Okay. Branch protection blocks the obvious move.
Pushing to main directly on a protected branch fails. Yeah, and which is good. Don't know if that really works though because you can tag releases on other branches, which is the the exploit from that previous video.
You can do that just fine and composer will pick it up even if it's on another branch. So I don't know if that that's really good mitigation here. The rollout was gradual. Not every repo was on the new token format at the same time.
Really good. And composer maintainers moved Yeah, really fast. Exactly. Now that's really great. What a typical public PHP maintainer has to do, upgrade composer. And that's really that's really simple by the way.
Just run a composer self-update. You can tighten permissions. Don't rely on legacy permissive defaults. Absolutely.
Protect your tags. The blast radius of this leak is overwhelmingly an attacker pushing a tag and packages publishing it. Enable GitHub's tag protection rules or branch protection rules. Yeah, that is indeed a very good idea. Sweep your action logs for the window. Check your tag and commit history for the window.
Mhm. [clears throat] Deploy the fixes before the next rollout. I think the the biggest fix here and it's it's chief a matter action updates composer. As far as I understand that already happened. So, when it comes to GitHub actions at least, anyone using shivamuthu should be fine as far as I understand.
But of course it's it should do all of this as well. What GitHub has to do, harden the runner secret maskers against console reframing. Today it matches registered values as exact substrings.
Yeah. Yeah, so GitHub tokens are masked in output but it it's apparently not not strict enough. Yeah, that is something GitHub should do indeed. Coordinate disclosure with the major package managers before the next rollout wave. Good point as well. Maybe we need a little more than just a small blog post. And automate testing of the common GitHub action setups for the top 50 programming languages. What other ecosystems have to do, grab your code base for hard coded GitHub token regexes. Yeah, good idea.
Never interpolate a rejected credential into a user visible error. This really is important. Other credential paths the same way and reach [snorts] out to GitHub security ahead of the next rollout. A final note on disclosure. I want to spend a couple of paragraphs on how this felt from the reporters side, not as a complaint but as a description of a structural problem someone with power should think about. The model for responsible disclosure assumes there is one vendor on the other end with a security team, a queue and a person paid to read the queue. Most of the time that's exactly how it works. You write up the bug, you submit it, they triage, they patch, you go home. This one doesn't have that shape. The root cause was GitHub. The fix that mattered most quickly was at composer. The blast radius was every PHP framework maintainer on Earth and we have no shared security inbox. There was no way to reach all of them on a Tuesday evening in under an hour as someone they've never heard before. Every minute the leak went undetected was a minute an attacker with a scraper could have been turning failing builds into push to tag access. So, the responsible path and the fast path diverged. The responsible path was file with the composer security advisory queue, file a hacker one against GitHub, wait. The fast path was open Discord, ping mod mail, open X, find someone who knows the people you need, cold call a colleague and hope they're taking you seriously by people who have no reason to know who you are.
I picked both paths. I'm still not sure if I was right. I'm fairly sure that traversing the fast path was less wrong than waiting for the advisory queue to process what it would have been. What bothers me is that someone less well connected than I am with the same bug at 10:00 p.m. on a Tuesday doesn't have a faster lever to pull than guess who knows the Taylor Otwell's of the PHP ecosystem.
And that this is a pretty like important issue that he's raising. If you're reading this and you have power in this picture at GitHub, HackerOne, one of the major ecosystem maintainers, I would like for there to be a phone number, a pager, a shared cross-vendor for this is an ongoing ecosystem scale leak and I need an adult. I don't know what that looks like in detail, but submit a form and pray is not adequate for incidents that look like this one. And I don't think we should keep relying on the fact that the next reporter happens to have my network, albeit small as it is.
Especially in the modern era of AI as the discovery to abuse window titans, time to response becomes more critical than ever before. I I actually think this is the most important part of of this whole blog post. We need to think about this. Very well put. Thank you, Damien, for the effort you put into it.
And I'd like to actually see if someone from composer, etc. replied. I do wonder about the value of validating tokens client-side. Wouldn't it be enough to just let the API throw an error? And here is Jordi from composer saying, "Yeah, I tracked down the commit which was added this yesterday and it was 13 or 14 years old.
I I a PR open already to delete the validation entirely from an upcoming release and I don't think it's relevant now that tokens are not really entered manually anymore. Back then you had to go and create it and type it in and if you messed up the copy paste, it was probably helpful to show you what was wrong, but today the landscape is very different. This is just like makes sense, right? But in hindsight, maybe not the best idea. Yeah, on your last point regarding the disclosure path, please watch out for a PHP foundation next Monday. We've already been working on a plan for this especially considering the changes AI has brought through these processes. I entirely agree with you on how tricky this is in situations where a huge amount of people and projects are affected and communication becomes extremely challenging. This Monday, by the way, that's today, the day I'm recording this, I will follow up on this as well.
I think [music] there are some some cool things coming. The PHP community barely avoided an absolute disaster. We're lucky that that the person who caught this had good intentions and wanted to help out.
>> [music] >> So, let's keep this as the second reminder now on this channel at least that PHP isn't immune for these kind of things [music] and that dependency hygiene really is a good thing to keep in mind. So, yeah, let me know your thoughts in the comments down below and I'll see you in the next one.
Bye-bye.
Ähnliche Videos
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 views•2026-05-28
How agent o11y differs from traditional o11y — Phil Hetzel, Braintrust
aiDotEngineer
450 views•2026-05-28
Re: 🗣️📍theprophedu📍2026 GST 103 CLASS (E-EXAM REVISION)
theprophedu
636 views•2026-06-04
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation💯✅
LearnwithSahera
1K views•2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 views•2026-05-29
Search Algorithms Explained in 60 Seconds! 🤖💨
samarthtuliofficial
218 views•2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 views•2026-05-30
Introduction to Problem Solving Part - 1 | Lecture 1 | Intermediate DSA
ascensionix
107 views•2026-05-29











