The machine that drives your printers is a server, and it is almost always the least maintained server you own. It sits on the warehouse floor or under the front desk, it runs whatever OS image it shipped with, and it listens on the network so printers can find it. Every CUPS advisory of the last two years has started from that last clause.
This post is a threat model for that machine. The short version: inventory what listens on it, remove what you do not need, and prefer a print path that only makes outbound connections.
What the 2024 CUPS chain actually did
On September 26, 2024, Simone Margaritelli disclosed a chain of four CUPS-related issues.
| CVE | Component | Role in the chain | NVD CVSS v3.1 |
|---|---|---|---|
| CVE-2024-47176 | cups-browsed |
Binds UDP 631 on any address and trusts packets from any source | 5.3, from the GitHub CNA; no NVD primary score |
| CVE-2024-47076 | libcupsfilters |
cfGetPrinterAttributes5() does not sanitize IPP attributes from the server |
8.6 |
| CVE-2024-47175 | libppd |
ppdCreatePPDFromIPP2() writes unsanitized attributes into a generated PPD |
9.8 at NVD, 8.6 from the GitHub CNA |
| CVE-2024-47177 | cups-filters |
foomatic-rip executes whatever FoomaticRIPCommandLine holds |
none: rejected as a duplicate of the other three |
Two things about that table are worth stating, because most coverage got them wrong.
The widely repeated 9.9 is not an NVD score, and no CVE in this chain carries it. Margaritelli's own write-up attributes the 9.9 to a Red Hat engineer's assessment relayed through the CERT/CC report, and says he is not a CVSS expert. The highest published NVD base score in the chain is 9.8, on CVE-2024-47175.
CVE-2024-47177 is no longer a live identifier: MITRE rejected it as a duplicate of the other three, so it has no CVSS score. The foomatic-rip command execution it described is real and is still the last step of the chain. The CVE number for it is not.
The chain walkthrough is in the write-ups by Elastic Security Labs and Uptycs.
Read the table as a sentence: a packet arrives, a printer appears, a file is generated from the packet's contents, and a later print job runs a command out of that file.
cups-browsed trusted the network, and that was the whole bug
cups-browsed exists to make printers appear without configuration. It listens on UDP 631 and, in the vulnerable configuration, accepts an announcement from any device that can reach it, then adds the advertised printer to the system.
Two properties make that expensive. The listener is reachable from anywhere the machine's network allows, including a compromised laptop on the same guest VLAN. And the payload is not merely data; it becomes a printer definition, a file that later drives a filter pipeline.
Code execution does not happen at packet arrival. It happens when somebody prints to the printer that quietly appeared. That delay is the part worth internalizing: the window between exposure and execution can be days, and nothing in your monitoring is watching the printer list.
The mitigations published at the time were the obvious three: stop and disable cups-browsed, block UDP 631 at the host and network edge, and patch. Those remain the right first moves.
The 2025 CVEs kept the same shape
The pattern did not end in 2024. Two more CUPS advisories landed on September 11, 2025, both fixed in CUPS 2.4.13.
CVE-2025-58364 is unsafe deserialization and validation of printer attributes in libcups, causing a null pointer dereference. NVD scores it 6.5, and the vector matters more than the number: AV:A, adjacent network. An attacker on the same subnet can crash cupsd and cups-browsed in a default configuration. It is a denial of service, not code execution.
CVE-2025-58060 is an authentication bypass in the cupsd scheduler, not in libcups. When AuthType is set to anything other than Basic, a request carrying an Authorization: Basic header has its password left unchecked. NVD scores it 8.0 with AV:L, so it is a local vector rather than a network-reachable one. A roundup of both is a reasonable starting point, but read the NVD records for the vectors.
Neither is a remote-code-execution chain, and both are the same class of problem: a service on the print host parses input it received from the network, and the parsing or the authorization around it was wrong.
CUPS is not unusually bad code. It is old code with a large attack surface, parsing input from devices it cannot vouch for, on machines nobody patches. Most projects in that position would have a similar CVE history.
A threat model for the box next to the printer
Write this table for your own print host. Here is the version we use.
| Asset | Entry point | Realistic attacker | Mitigation |
|---|---|---|---|
| Print host OS | Network listener (cups-browsed on UDP 631, IPP on TCP 631, mDNS, SSH, remote desktop) |
Anything on the same LAN or VLAN, including compromised staff laptops and IoT | Remove listeners; firewall the rest to specific sources |
| Print host OS | Malicious printer definition installed by an announcement | Same | Disable automatic printer discovery; manage queues explicitly |
| Print content | Files readable in spool directories | Local user, or anyone with code execution | Least-privilege service account; do not run the print agent as an administrator |
| Credentials on disk | Config files, keychains, tokens | Local user, stolen machine | Encrypt at rest, bind tokens to the device, revoke on loss |
| The printer itself | Raw port 9100, embedded web server, firmware | Anything on the LAN | Segment the printer VLAN; change default admin passwords |
| Your upstream account | A credential copied off the print host | Whoever gets the box | Per-site credentials, scoped to one workspace, revocable individually |
Two rows are the ones teams forget. The printer's own embedded web server is a device on your network with a default password and old firmware. And the credential on the print host is a credential for your cloud account, so the blast radius of a stolen warehouse PC is however wide you made that credential.
The hardening list, in order of payoff
Start at the top. Each item is cheaper than the one below it.
Disable discovery daemons you do not use. On a machine with explicitly configured queues, cups-browsed buys you nothing.
systemctl stop cups-browsed
systemctl disable cups-browsed
Firewall UDP and TCP 631 so only the hosts that need them can reach the machine, and confirm with a scan from another box rather than trusting the config.
Patch the print stack specifically. Print packages are often excluded from update automation because a bad update breaks production printing. That exclusion is exactly how a machine ends up two years behind.
Run no inbound services at all, if you can manage it. No SSH from the general network, no remote desktop, no web UI. Use whatever your fleet management tool already provides for access.
Give the print agent its own low-privilege account. It needs to submit jobs, not install software.
Segment the printers. A printer VLAN with no route to anything but the print host is a small amount of work and removes several rows of the table above.
Inventory the queues. A printer nobody configured is an incident, not a curiosity.
A host that only dials out deletes a whole class of entry point
Every item in the first two rows of that threat model depends on something on the print host accepting an inbound connection. If nothing does, those rows disappear. You are left with local attacks and credential theft, which are real, but far smaller.
That is the architectural argument for a print host that establishes its own outbound connection to a control plane and waits for work. The private side dials out, the firewall keeps its default deny for inbound, and there is no listener to find.
RocketPrint is a remote printing API and desktop Station for software platforms: your backend POSTs a print job, and a small app beside the printer prints it, with no inbound networking, no drivers for raw label formats, and a durable job record. Station opens an encrypted WebSocket outbound and keeps it open, so there is no port forwarding, no VPN, and no inbound rule to write. It prints only jobs for printers it reported itself, on the account it signed in to. Enrollment is a browser authorization that issues a token bound to that device, and a station can be revoked from the console. The mechanics are in how a cloud printing API reaches a printer behind a firewall, and the trust boundaries in our remote printing security review and on the security page.
What outbound-only does not fix
It does not remove CUPS. On macOS, Station submits raw jobs through CUPS with the raw option, so the local print stack is still in the path and still needs patching. Outbound-only changes who can reach the parser, not whether the parser exists.
It does not protect the printer. A label printer with an open port 9100 is reachable by anything on its VLAN regardless of how your print agent connects.
It does not help a Linux print host today, because we do not offer a Linux Station for self-service installation. macOS and Windows are supported. If your print host is a Linux box, and for many warehouse and kitchen setups it is, the hardening list above is your answer and we are not, at least not yet.
And it does not make the machine unattended. A print host still needs patching, a known software inventory, and someone who notices when it goes offline for three days. What happens to queued work while it is offline is a separate problem, covered in recovering a warehouse print queue after an outage. Station specifics are in the Station documentation.
The takeaway is narrower than "use a cloud service". It is this: go count what is listening on the computer next to your printer. On most sites the answer is several things, at least one of which nobody chose.
END / cups-cves-threat-model-machine-next-to-printer
More field notes →