Update: A fix which prevents Composer from locally installing packages not explicitly referred to by your root composer.json, or not explicitly referred to by your dependencies, has been committed to the Github repository: https://github.com/composer/composer/issues/2690. This change is live, so make sure to update your copy of Composer when possible. If you still experience any unexpected replaced/provided packages, be sure to open an issue.
A few days ago, I spotted a post floating past on Twitter entitled “Composer: Replace, Conflict & Forks Explained”. There has been some recent complaining about Composer downloading the wrong packages as dependencies due to its “replace” feature misbehaving, so seeing something hit the blogs was not unexpected. The issue has been encountered previously so having it reoccur caused some consternation. The article states:
Recently there has been an increase of cases in which Composer installs a fork of a package instead of the package the user expects. […] First of all, this behavior is not a security issue in Composer.
The problem here is quite simple. A user defines a composer.json file that requires the package bloggs/framework. Someone else creates a package on Packagist.org called evil/framework whose own composer.json states that it replaces bloggs/framework. Next, a group of poor random victims, potentially thousands, use composer to install applications with a dependency on bloggs/framework. Composer does some internal wizardry and installs evil/framework when certain conditions are met. The victims didn’t request evil/framework but they get it anyway.
If a piece of software, upon running, downloads files under the control of a potential attacker it is called a Remote File Inclusion (RFI) vulnerability. If the new file finds itself in a scope where it is executed, it’s also a Remote Code Execution (RCE) vulnerability. Two vulnerabilities for the price of one.
These definitions of two vulnerabilities apply to all software…except Composer. It’s possible that it’s not a security issue in Composer because it is in Packagist.org. That works too perhaps.
If your dependencies lead to a conflict with a package Composer may decide to install a fork instead which does not have the same conflict. If you notice that an unexpected fork is installed when running `composer update` you can debug the dependency problem that lead to the fork installation. Use the conflict key in your composer.json to blacklist the fork.
So don’t worry. This is all YOUR fault for not debugging, using conflict keys and assuming composer wouldn’t install potentially hostile code you never requested.
The mitigating manual checks described in the referenced blog post are not documented in the Composer manual as security concerns. This is the first time that these suggestions are being clearly communicated, so I assume many readers are not currently following them. They are also deeply flawed. Obviously, they are flawed because expecting people to do things that they never suspected were necessary is futile, but there’s also no attempt to elaborate on their assumed effectiveness.
Consider a scenario where an attacker decides that all this conflict blacklisting crap needs to be defeated for the few actually using blacklisting. They will immediately try two things:
- Flood Packagist.org with replacement packages. Try getting an attacker to voluntarily remove packages or have Packagist.org’s team manually weed them out. That may a) create frustration to end users if the attack succeeds, b) impose a denial of service on those trying to block illegal replaces, and c) destroy trust in Composer as a secure platform.
- They will not use obvious names. Would you trust something called fpotencier/twig? What about phpfig/log, synfony/console or laraval/framework? There are an infinite number of innocent looking and trustworthy package names to apply to the infinite number of false packages an attacker could setup. Longer names are easier to fudge and less likely to draw attention from the big frameworks unless they break CI builds.
We understand that Composer’s behaviour regarding forks and package replacements is unintuitive. So I’ve proposed a number of changes yet to be implemented to the handling of replace & provide on our issue tracker at https://github.com/composer/composer/issues/2690.
So, the unintuitive feature which is definitely not a security vulnerability is going to be changed. Will its ability to inject hostile code be documented? Will it have an optional off switch added to the command line options?
Only packages which match a dependency by name (ignoring replaces/providers) or a dependency of any potential dependency identified recursively by name will be considered for installation. The error reporting should further query for alternative replacers/providers which the user can require in the root package to satisfy the dependency.
The feature is being removed at some future unspecified time.
There’s more though. The Packagist.org folk are also manually deleting packages which are found to be replacing valid packages improperly and labeling it as an abuse of Packagist. If it’s an abuse – where are the Packagist.org automatic checks to prevent it? If it’s an abuse – how can it possibly be a “feature”? If it’s an abuse – isn’t that also another one of those things that are definitely not a security vulnerability?
Anyway I deleted the offending fork, @mlebkowski @nediam please take notice and use https://getcomposer.org/doc/05-repositories.md#vcs instead of abusing packagist to fork packages. If it’s a real fork you intend to maintain you can submit it to packagist but then you should not use replace and you should definitely add a note explaining what your version does differently.
Saying one thing, but acting like it’s the other thing you don’t want people to call it, makes me think it really is the other thing. Probably because it is. Users can fall victim to a replace and it’s called “unintuitive”, but if a package states that it replaces something that might lead to the unintuitive behaviour, it’s an abuse.
Packagist.org hosts somewhere just short of 25,000 packages. That almost beggars belief in a community barely out of the shadow of PEAR’s dominance. Composer has become an integral part of how we develop and distribute software. It simply doesn’t fit well with me that Composer can have security issues of this extent, over extended periods of time, and then see blog posts denying their very existence under the cover of “users are holding it wrong” while fixing them anyway in public Github issues for the whole world to see.
The reason why I absolutely despair at people denying the obvious is that it’s a bad habit to fall victim to. When you can deny a security vulnerability exists, even when it obviously does exist, you can justify not fixing it or undertaking endless bikeshedding. In this particular case, there’s finally pressure there to get something done on a problem that has continued occurring despite repeated complaints.
This is not the first time an issue has been openly discussed concerning Composer’s attitude to security. Composer is too intrinsically a part of PHP’s ecosystem to be allowed to take security issues so lightly. Since users are obviously exposed, this issue needs to be fixed as soon as possible. Preferably by yesterday.
In the meantime – readers should do as the referenced blog post says. The critical part is checking what you actually installed (I’m assuming dry runs followed by installs are susceptible to race conditions with Packagist.org’s update cycle), not running any installation scripts and being 100% certain that your composer.lock file is free of creative spelling errors.
This is part rant and part poking fun, but I’ve grown weary with the sight of source code running through phpcs for PSR-2 compliance and finding that it’s riddled with dozens and dozens (and dozens of dozens) of errors and an apparently infinite number of warnings. If I actually fixed all the reported problems, I’d drive myself crazy. I’m not crazy. I’ve avoided insanity because I quit using a coding standard. It’s too much and I freely admit it: I loathe coding standards.
Or do I?
The problem with coding standards is not the notion of following conventions to ensure all programmer can quickly read and understand code (and other good stuff), but that someone created a tool to actually check compliance: PHP_CodeSniffer. This isn’t a complaint about the operation of phpcs, but to complain about the mere fact of its existence. Automated tools catch everything. Every minor deviation, every judgement call about what looks neat, tidy or pretty, and every crucial space character found out of place is tallied into a wall of text in your console demanding that all these crucial lapses are fixed post haste. It doesn’t even have the decency to use smilie faces.
Using the cover of such automated tools, we can make judgement calls about code quality, integrate style checks into Continuous Integration scoring schemes, complain about pull requests and patches, and generally impose a time penalty on writing code. There is a point at which common sense morphs into sheer nitpicking, and an automated tool is the perfect nitpicker.
Get Outta My Way!
For me, a coding standard must have flexibility and it must remain invisible 99% of the time. If the damn thing makes sense, a programmer should have it learned inside of a week. If they have it learned then why would they need constant monitoring? Rather than catering to everyone by dictating everything (and then failing to do even that!), a coding standard should instead state a subset of common sense rules that everyone already uses. In otherwords, it should be almost utterly pointless to an experienced programmer. There’s already a good example of this. PHP-FIG has PSR-1. It’s basic, adheres to standard practice, and you’d have to be doing something completely weird to fail compliance. In other words, it’s perfect.
Beyond PSR-1, we then have PSR-2. Plug PSR-2 into PHP_CodeSniffer and a gateway to Hell will spawn. Most of the standard itself is actually quite logical. In fact, most of it is! The problem is when PSR-2 descends into seemingly trivial matters or omits specific use cases, and then implicitly demands total unquestioning obedience. The trivial matters are those that just never occur to me as being problematic (yes, that would be “opinion”) or which my editor does by default. The omissions are cases where PHP_CodeSniffer feels the need to fill in the blanks, or worse, where an interpretation of the existing wording is in doubt. Both fill my screen and CI reports with utter junk (which is more opinion).
Am I expected to waste my time fixing this junk? I don’t think so. I have better things to do like writing more code, more tests, more documentation, and more rants about the state of security in PHPland. Perhaps eating and sleeping in between .
PSR-2 !== PSR-2
What has happened to PSR-2 is that it was made a standard rather than a living document, and it has not achieved the full benefits of either option. There’s as many PSR-2s as there are PHP programmers. Every individual has scope to re-judge the ambiguous bits, re-define words, and fill in the blanks as they see fit, whether that individual be you, me or the authors of coding standard tools. As a standard, it doesn’t quite sail smoothly. Even with the recently introduced notion of using errata, it still leaks like a sieve. It’s like having a living document by the back door but it also relies on retroactive reinterpretation of the existing text without actually changing the text per se. It’s working slowly but it’s also a thankless task for whoever is spending time on it.
I could spend time retraining old habits, reconfiguring editors, using pre-commit hooks to get phpcs reports, but that goes against the idea of limiting waste time and absorbing the occasional mistake if harmless. I simply do not want to spend time worrying about this unless I’ve done something obviously unforgiveable. All those minutes add up and they have a cost measurable in productivity and sanity. Another example of this silliness is the quest for perfect test coverage of source code. It’s another time sink exercise to resolve some imagined evil. A test suite with 100% code coverage can either have a perfect test suite or an unconciously manipulated test suite that doesn’t test all possible logic branches but manages the same score. Programmers will cheat the system when you push them too far. Those with the least experience will cheat the most, and I don’t blame them – experience doesn’t come overnight simply by setting out strict rules and arbitrary penalties. Automated tools can’t tell the difference. They are stupid animals.
The Perfect Coding Standard…
A perfect coding standard is, in my opinion, one which limits the rules, eradicates ambiguity, formulates multiple use cases and avoids trivialities. It’s ruleset count would be more than PSR-1 and less than PSR-2, but with greater focus, explanation and examples. For example, if I place a newline before the closing curly bracket of a class, will the planet instanstaneously implode? Probably not. Will a programmer notice it? If they do, I feel sorry for them. Does PHP_CodeSniffer currently torture me with it? YES. MAKE IT STOP. If I use a shorthand control statement, will the planet instantaneously expl… You get the picture.
Some of these rules make sense if a file is overburdened with them to the point of being illegible, but most are once off misfires and laziness. Harmless. If a Human saw them, they should be able to use their brain. If PHP_CodeSniffer sees them, it makes a console mess. It does not compute.
So a perfect coding standard doesn’t simply eliminate trivialities, it defines a scoring system to save us from overeager automated tools. The occasional slipup won’t penalise you too much and it won’t give you 100%, but neither will it see you sent off to make one character changes for the rest of your mortal life as a precondition for earning the approval of a frickin’ computer.
It gets better! Since such a coding standard would eliminate trivialities, omissions and be more explicit about those it retains, the chances of actually violating it would be less. The probability of scoring really well would be really high. If that point can be reached, you have a coding standard that works with programmers, isn’t overly judgmental, and doesn’t flood your terminal with nonsense.
Well, I can dream… Humans are not computers to expect perfection from. It’s actually irritating when people do.