At this year's International PHP Conference in Munich, I was interviewed by Ines Chargui. I received the questions in advance: the first part focused on security, from Composer's new behaviour to PHP2Shell, while the second part looked back on the year and ahead to the next one. In addition to the video, I wanted a written version of the interview in which I have the opportunity to provide my answers more clearly than is possible in front of a camera.
The questions are reproduced as they were asked. Where they speak of AI, my answers speak of LLM-based tools: the more precise term makes it easier to discuss what these tools do, and what they do not do.
From warning to blocking
Question: Composer is moving from telling developers a dependency is dangerous to actively preventing them from installing it. How quickly does a vulnerability discovered today become a block in Composer, and where are the biggest delays?
The part that Composer is responsible for is fast. Packagist aggregates security advisories from the GitHub Advisory Database and the FriendsOfPHP Security Advisories database, and since Composer 2.9, composer update refuses to install a version that is affected by a known advisory. Once an advisory has been published, it takes minutes, not days, until it blocks.
The delays lie before and after that. Before, a vulnerability has to be fixed and an advisory has to be filed, with version ranges that are correct: ranges that are too broad block versions that are fine, ranges that are too narrow let affected versions through. This work is done by maintainers, many of whom are volunteers. After, the blocking only takes effect when dependencies are resolved, that is during composer update, composer require, and composer remove, but not during composer install from an existing lock file. Versions flagged as malware are the exception: since Composer 2.10, they are blocked during composer install as well. A project that deploys from its lock file and rarely updates its dependencies will therefore never be blocked by an advisory. This is why I recommend running composer audit in your CI pipeline on a schedule, and not just when your dependencies change: new advisories are published for versions you installed months ago.
When nothing changed, but everything fails
Question: What’s the biggest surprise developers will encounter when upgrading to the new Composer behavior?
That composer update fails although they did not change anything. A package they have not thought about in years, often a dependency of a dependency, has a known vulnerability. Where Composer used to print a warning that was easy to overlook, it now refuses to resolve the dependencies. For projects that have accumulated update debt over the years, this debt suddenly becomes visible.
The obvious reaction is to switch off the blocking. Please resist it. The vulnerability was there before; the only thing that has changed is that you now know about it. If you really cannot update right now, Composer lets you ignore a specific advisory and record the reason for doing so in your composer.json. That is a documented decision that can be reviewed later, rather than a switched-off mechanism that everybody forgets about.
The runtime is a dependency, too
Question: The PHP incident involved the runtime itself, while supply-chain attacks can target packages. How should developers build a security strategy that covers both?
PHP2Shell showed that composer.lock only describes part of what we run. The two vulnerabilities, CVE-2026-109618 and CVE-2026-109619, are in PHP itself. With mod_php, an unauthenticated request can trigger the exploit before any application code runs. Nothing in your application, in its dependencies, or in your Composer configuration could have protected you.
My answer is to treat the runtime as a dependency like any other. You need to know which PHP version runs where: on servers, in container images, in CI, and in appliances or vendor products that embed PHP and that you cannot update yourself. And you need to be able to roll out a PHP patch release as routinely as an update of a Composer package: through the same pipeline, verified by the same tests, with the same confidence. An inventory that lists your Composer packages but not your PHP runtime, its extensions, and the system libraries it links against is incomplete.
Making updates boring
Question: How can development teams move from reacting to security incidents to anticipating them?
Teams that are caught off guard by security incidents rarely lack information. They lack a routine. If updating PHP or a dependency is a project that has to be planned and estimated, and is then postponed, every security fix becomes an emergency. If updates are small, frequent, and boring, an emergency is just a normal update that happens to be urgent.
This requires a test suite that you trust, static analysis, and automated deployment. It requires knowing what you run. It requires being deliberate about dependencies: every dependency is a decision that you have to keep making again and again, so fewer dependencies mean fewer decisions. And it requires using supported PHP versions. For PHP2Shell, the PHP security team made an exception and published minimal source backports for PHP 7.4, PHP 8.0, and PHP 8.1. You should not plan on such exceptions.
The first hours after a disclosure
Question: When a critical vulnerability is disclosed, what should developers prioritize first, and what mistakes should they avoid?
First, find out whether and where you are affected: which systems run the affected software, in which versions, and which of them are reachable from the internet. Without an inventory, this step takes the longest, which is why the inventory needs to exist before the disclosure. Then patch, starting with the systems that are exposed, ideally through your normal deployment pipeline.
There are three mistakes to avoid. The first is treating a mitigation, a WAF rule for example, as the fix. For PHP2Shell, PHP core developers stated clearly that no mitigation fully blocks attacks and that the official patches are the only permanent defence. The second is assuming that a patched system is a clean system. Patching closes the hole, but it does not remove a compromise that happened before, and clean logs do not prove that nothing happened. The third is using the urgency as a reason to skip tests, or to combine the security update with a larger upgrade under time pressure.
Automation and its limits
Question: Can we automate security decisions without creating a false sense of security?
Yes, and we should. Composer blocking known-vulnerable and known-malicious versions by default is a good example: a decision that many people got wrong by not making it at all is now made correctly by default.
A false sense of security does not come from automation itself, but from misunderstanding what it does. Automation can only act on what is already known. A successful composer audit means that there are no known advisories for the versions you have installed. It does not mean that your dependencies are secure, just as 100 % code coverage does not mean that your tests verify anything. The second risk is the override: the first time the automation gets in the way, somebody switches it off globally, and nobody ever switches it back on. Every exception should be explicit, limited to a specific advisory, justified, and reviewed regularly.
Finding faster, fixing slower
Question: Will AI help developers find and fix vulnerabilities faster, or give attackers an even greater advantage?
Both. LLM-based tools lower the cost of finding vulnerabilities, for defenders and attackers alike. At the same time, the window between disclosure and exploitation keeps shrinking: in the case of PHP2Shell, attacks were observed even before the fixed releases were available.
Whether defenders come out ahead does not depend on how fast vulnerabilities are found, but on how fast they are fixed and how fast these fixes are deployed. The bottleneck is people: maintainers who triage reports, write fixes, and coordinate releases. Many of them are volunteers who are already struggling with a flood of low-quality, machine-generated vulnerability reports. If these tools help us find more vulnerabilities, we have to fund the people who fix them. This is why supporting maintainers, as The PHP Foundation, on whose board I serve, does for PHP itself, is security work.
Security by default
Question: What news in the PHP universe/software development was most exciting for you this year?
The work of the Composer and Packagist team. Advisory blocking has been the default since Composer 2.9. Composer 2.10 added malware filtering that is enabled by default for everybody who uses Packagist.org, together with a unified policy configuration. Packagist made stable versions immutable, so that a re-tagged release can no longer silently replace a published one. And Composer 2.11 adds a cooldown policy. Taken together, this moves supply-chain security in the PHP ecosystem from something you have to opt into to something you get by default.
What I find most remarkable is that this is not the work of a large security vendor, but of a small team that maintains infrastructure the entire PHP ecosystem depends on. I wrote about the pressure this infrastructure is under in Composer and Packagist under supply chain stress. This team does more for the security of our ecosystem than most of its users realise.
Accountability does not move to the tool
Question: If AI agents become genuine participants in software development alongside developers, how should we rethink ownership and accountability when AI-generated code introduces bugs, vulnerabilities, or unexpected behavior?
I would question the premise. An agent can be a useful tool, but it cannot be held accountable, and therefore it is not a participant in the sense of the question. Whoever merges the code owns it, exactly as with code written by a colleague, by a contractor, or by the maintainers of an Open Source dependency. “The agent wrote it” is no more an excuse than “I copied it from Stack Overflow”.
What does change is where responsibility is exercised. If you only review what an agent has produced, you gradually lose the understanding you need to take responsibility for it. Reviewing is not the same as specifying. This is why I think that the failing test, written by a human and expressing what the code is supposed to do, is the part of the work that must not be delegated. Owning code means owning its specification.
Faster than we can understand
Question: Where do you see the biggest tension between the speed AI promises for software development and the need to keep increasingly complex applications understandable, maintainable, and controllable?
Code can now be produced faster than it can be understood. Every line of code that nobody on the team understands is debt. Margaret-Anne Storey describes this in “From Technical Debt to Cognitive and Intent Debt” as a shift from debt in the code to debt in our heads. This debt comes due at the worst possible moment: during an incident, under time pressure, or when an upgrade has to happen right now because the runtime has a critical vulnerability.
Readable code and green tests are not enough. When I had an agent implement a new metric for one of my own libraries, sebastian/complexity, the code was readable and the tests were green, and I still could not judge whether the result was correct. Control does not come from code that looks right, but from knowing what it is supposed to do and being able to check that it does.
Plausible is not correct
Question: What do you think developers and engineers are currently getting most wrong about AI, and what will they have to unlearn about software development over the next few years?
They mistake plausible for correct. LLM-based tools produce code that looks right, and tests generated together with that code confirm what the code does, not what it is supposed to do. A test suite that has been derived from an implementation cannot find the errors in that implementation.
What we will have to unlearn is the idea that writing code is the bottleneck of software development. It never really was, and now it certainly is not. The scarce skills are understanding the problem, specifying the solution, verifying the result, and deciding what not to build. We will also have to unlearn measuring progress in code produced: code is a liability, and producing more of it faster is not progress in itself.
Disposable software
Question: What development in software development did you find disappointing this year?
The wave of agent-generated rewrites of established tools: Composer in Rust, yet another testing framework built over a weekend. They attract attention, but nobody has committed to maintaining them for the next ten years, to handling their security reports, or to keeping them backward compatible. That is disposable software. It is fine for experiments, but you should not build on it.
What makes tools like Composer or PHPUnit valuable is not their code. It is years, sometimes decades, of maintenance, of decisions about what not to do, and of reliability that others can build on. That cannot be generated over a weekend.
Find out what your tests are worth
Question: Do you have a secret tip for our audience: a tool, a technology, or a productivity booster that everyone should try out?
Mutation testing with Infection. It makes small changes to your code, replacing < with <= or removing a method call, for instance, and then checks whether your tests notice. Every change that goes unnoticed shows you code that your tests execute but do not verify.
Run it once, on a small part of your codebase to start with. It is a humbling experience, especially for code with high code coverage, because code coverage only tells you which code was executed, not which code was verified. And it is particularly revealing for code and tests that were generated by an agent.
Running the right tests
Question: Your New Year's resolution for 2027: What topic would you like to focus on in the new year?
How PHPUnit executes tests. I want PHPUnit to run the right tests, in parallel, and only as often as necessary: only the tests that are affected by a change, independent tests at the same time, and tests repeated where needed to expose those that only pass by chance.
The reason is simple: when code is produced faster, feedback has to be faster, too, and it has to be trustworthy. A test suite that takes thirty minutes is not run often enough, and a test suite with flaky tests is not believed. Both undermine exactly what we need most right now: the ability to verify.
Policy as code for dependencies
Question: What trend do you currently see that you think will gain momentum in the coming year?
Dependency policies that are enforced by the package manager itself. Composer's new policy configuration brings together advisories, malware, and abandoned packages, supports custom policies from external sources, and with Composer 2.11 gains a cooldown that refuses versions that have only just been published. Organisations will define their own rules and have them enforced on every developer machine and in every CI pipeline.
The Cyber Resilience Act will add momentum, because knowing your dependencies is becoming an obligation for many organisations rather than just good practice. Such policies need care, however: a cooldown that keeps a malicious release out also delays a security fix. Policies need well-considered exceptions, otherwise they will be switched off.
Unverified
Question: How would you describe the state of software development in just one word?
Unverified. We produce more code than ever and verify less of it: code generated by tools, dependencies that nobody has looked at, and runtimes that we did not know we were running.
The good news is that the tools for verification have never been better: tests, static analysis, mutation testing, dependency audits, and software bills of materials. We just have to use them.
Finally, I would like to thank Ines Chargui for the interview and for her thoughtful questions.