Composer, the ubiquitous PHP dependency manager, was failing to sanitize remote package metadata before printing it to the console. This allowed attackers to inject ANSI escape sequences into package names or descriptions. While the CVSS score is a measly 1.3, the practical implication is that a malicious package could rewrite your terminal history, hide critical security warnings during a `composer audit`, or even crash your terminal emulator. The fix involves a hefty regex to strip control characters.
A deep dive into how ANSI sequence injection allows malicious PHP packages to manipulate your terminal output, hiding warnings and spoofing success messages.
We tend to treat our terminals as the ultimate source of truth. When ls says a file is there, it's there. When git says a branch is clean, it's clean. And when composer tells us it's installing a package, we believe it. But what happens when the text appearing on your black-and-green screen isn't coming from the system, but directly from a malicious actor on the other side of the internet?
Enter CVE-2025-67746. It’s a classic tale of "Trust, but verify," except Composer skipped the verify part. For years, the PHP dependency manager has been dutifully fetching package metadata—names, descriptions, abandonment warnings—and piping them directly to your stdout. It’s a feature, right? You want to see formatted text. You want colorful output.
But here's the kicker: standard output isn't just a text stream; it's an interpreter. Modern terminal emulators are complex beasts that parse ANSI escape codes to change colors, move the cursor, and clear lines. By trusting remote input, Composer inadvertently gave package maintainers remote control over your cursor. It’s like inviting a stranger into your house to paint the walls, and then being surprised when they paint over your windows.
At its core, this vulnerability is a specific flavor of CWE-74: Improper Neutralization of Injection. The injection here isn't SQL or HTML; it's ANSI (American National Standards Institute) escape sequences. These are the byte sequences that tell your terminal, "Make the next word red," or "Move the cursor up three lines and delete everything to the right."
Composer fetches data from repositories (like Packagist or private repos). This data includes strings like package descriptions. Before version 2.2.26 (or 2.9.3), Composer treated these strings as sacred, immutable text. It took the bytes from the JSON metadata and passed them straight to the IO interface, which dumped them to your screen.
If I control a package repository, or if I can convince you to use my fork of a library, I can put anything in that description. If I put \033[2J, your terminal clears the screen. If I put \033[1A, your cursor moves up. By chaining these commands, I can effectively rewrite history—literally overwriting the lines Composer just printed.
The fix isn't rocket science, but it is necessary plumbing. The maintainers had to introduce a sanitization layer that sits between the data and the display. In src/Composer/IO/ConsoleIO.php, they added a sanitize() method. This method is now called on almost everything that hits the console—audit reports, package descriptions, and even interactive questions.
Here is the regex beast they unleashed to kill the injection:
// The pattern identifies CSI, OSC, and general ESC sequences
$escapePattern = '\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\x1B\].*?(?:\x1B\\\\|\x07)|\x1B.';
// Applying the filter to strip control characters and escape codes
$pattern = $allowNewlines
? "{{$escapePattern}|[\x01-\x09\x0B\x0C\x0E-\x1A]|\r(?!\n)}u"
: "{{$escapePattern}|[\x01-\x1A]}u";
return preg_replace($pattern, '', $message);[!NOTE] Notice the specific handling of
\r(?!\n). A naked Carriage Return is a favorite tool for attackers because it moves the cursor to the start of the current line without advancing, allowing them to overwrite existing text. This regex nukes it unless it's paired with a newline.
The patch ensures that write(), writeError(), and overwrite() methods filter their input. It transforms potentially active content into harmless plain text.
You might be looking at the CVSS score of 1.3 and thinking, "Why do I care? It's not RCE." You're right, it's not Remote Code Execution. It's Remote Reality Execution. The danger here is Social Engineering and obfuscation.
Imagine you run composer audit in your CI pipeline or local machine. Composer detects a critical vulnerability in library-x. Normally, it prints:
Found 1 vulnerability:
- library-x: Critical RCE (CVE-202X-XXXX)Now, imagine the attacker who maintains library-x updates their composer.json description to include ANSI codes. They inject a sequence that moves the cursor up two lines, clears the text, and prints green text saying "No vulnerabilities found."
Your terminal output now looks like this:
[+] Audit passed. No vulnerabilities found.You commit the code. You deploy. You get hacked. The exploit didn't break your server; it broke your trust in your tools. It tricked the human operator. In a world where we rely on automated scanners and quick glances at terminal logs, the ability to make red warnings look like green success messages is a potent weapon.
The mitigation is straightforward: Update. The PHP community is generally fast at rolling out these things.
If you are on the 2.2 LTS branch:
Ensure you are on 2.2.26 or higher.
If you are on the current 2.x branch:
Ensure you are on 2.9.3 or higher.
Run the magic command:
composer self-updateIf you are stuck on an old version for some legacy reason (we've all been there), you are vulnerable to any package source you have configured. If you only use Packagist, the risk is lower because Packagist likely does its own sanitization or vetting, but if you use private Satis instances or pull from Git repositories directly, you are trusting every byte of metadata those sources provide.
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Composer Composer | >= 2.0.0, < 2.2.26 | 2.2.26 |
Composer Composer | >= 2.3.0, < 2.9.3 | 2.9.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-74 |
| Attack Vector | Network (Package Metadata) |
| CVSS Score | 1.3 (Low) |
| Impact | Integrity (Output Manipulation) |
| Exploit Status | PoC Possible (No Active Exploitation) |
| Affected Component | Composer ConsoleIO |
The software constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as commands when sent to a downstream component.
Get the latest CVE analysis reports delivered to your inbox.