Seventeen Days of Root: A Miner in QwenPaw’s MCP Driver Table, and the Maintainer Who Says It Is Not a Bug
On 9 October 2026, a user named kenzone filed issue #8153 against agentscope-ai/QwenPaw — the self-hosted personal AI assistant with some 35,700 stars — and it is one of the most complete victim-side intrusion reports this site has read all year. Investigating a maxed-out CPU on a production server, the reporter traced the full chain: an attacker registered MCP drivers named rce-poc-* through QwenPaw’s driver configuration interface, executed commands as root, appended twenty attacker SSH keys to /root/.ssh/authorized_keys, and deployed a systemd-persisted miner that phoned home to 176.65.149.237 on ports 7777 and 4444. Dwell time, first probe to cleanup: roughly seventeen days.
Eight minutes later, maintainer rayrayraykk replied — in Chinese and English — and closed the issue. The substance of the reply: QwenPaw listens on 127.0.0.1 by default, stdio MCP servers are local processes by design so configuring one is authorizing execution, the root privileges came from the deployment’s User=root setting rather than any privilege escalation, and the whole chain reflects misuse of an intended capability through an insecure deployment, not a vulnerability. No CVE was filed, no advisory published, and as of writing no code change addresses the interface.
Both of them can be right, and that is why this briefing exists. The reporter documented a real compromise with logs, timestamps, and indicators. The maintainer’s rebuttal checks out against the source, point for point — we verified each claim first-hand. The story is the boundary between them: a management API that spawns server-side processes, protected by authentication that is opt-in, on a product whose entire value proposition is running an AI agent on your own machine or cloud. Every link in the chain was a default, a documented behavior, or a single explicit override. None of them looks like a vulnerability in isolation. Together they mined cryptocurrency as root for two weeks.
What the reporter proved
Take the intrusion evidence as reported — it is single-source, from the victim, and unusually well preserved. On 22 September at 03:21 (+08:00), QwenPaw created a driver named rce-poc-d2031bc5 and a probe file appeared at /tmp/.qp_rce_d2031bc5. A second probe followed ten minutes later. On 25 September at 05:39, a third driver (rce-poc-9fca6523) executed a command whose output — uid=0(root) plus the string PLANTED — was written to disk, and /root/.ssh/authorized_keys gained twenty consecutive lines of a single attacker key. One minute later, at 05:40:08, sshd logged Accepted publickey for root from 176.65.149.237, with a key fingerprint matching the planted material and, the reporter notes, no other successful logins anywhere near it — ruling out brute force as the entry path. On 6 October, persistence landed: /etc/systemd/system/worker.service (“Restart=always”, “Nice=-20”) launching a hidden 8.5 MB binary from /usr/lib/.s90a96339/worker. On 8 October the process tree — four layers of the same binary under camouflaged names — pinned both cores near 200% CPU and opened TCP sessions to the same IP on 7777 and 4444. Cleanup and disclosure followed on 9 October.
The QwenPaw-side logs quoted in the report corroborate the mechanism rather than merely the outcome: an Error in MCP client lifecycle for rce-poc-9fca6523 from mcp_stateful_client.py, a Driver 'rce-poc-9fca6523' saved but not active yet line from driver_config_service.py, and a watcher noting the driver removed after card deletion. The attacker’s drivers executed, left marker files, and were cleaned up through the same management path that created them. The report also states its own preconditions plainly: the service ran as User=root, and although config.json specified 127.0.0.1:8088, the systemd unit’s ExecStart explicitly passed --host 0.0.0.0. The exposure was not a default; it was an override. That sentence is the maintainer’s whole case, and the reporter handed it to him.
What we verified in the source
We read the relevant code at the main branch rather than taking either party’s description on trust. The maintainer’s three factual claims hold. First, the CLI defaults to loopback: src/qwenpaw/cli/main.py resolves the bind with host = host or "127.0.0.1". Second, stdio drivers are process spawns by construction: src/qwenpaw/drivers/handlers/mcp_stateful_client.py builds StdioServerParameters(command=...), and its own comments describe the lifecycle in terms of spawning new subprocesses. Third, authentication is strictly opt-in: is_auth_enabled() in src/qwenpaw/app/auth.py returns true only when QWENPAW_AUTH_ENABLED is set to a truthy value, and the application-level AuthMiddleware skips authentication for every request when the flag is unset or no users are registered yet.
What the maintainer’s reply does not dwell on is the shape of the interface that all of this protects — or rather, does not protect by default. The MCP router (src/qwenpaw/app/routers/mcp.py, mounted under /api) exposes POST /api/mcp, “Create a new MCP client,” taking a MCPClientCreateRequest whose transport defaults to stdio, whose command field is documented as “Command to launch the MCP server,” and whose enabled flag defaults to true. There is no per-route authentication dependency; the only gate is the middleware described above. Saving the card triggers reload_driver_best_effort — the exact log line the reporter quoted — which activates the driver and spawns the command. So with default settings, any network party that can reach the HTTP port can register an enabled stdio driver and execute a command with the service account’s privileges. Reachability is localhost by default, which is a real mitigation — but the authentication layer adds nothing on top of it until someone sets the flag, registers an admin, and restarts.
Two further observations from the same files. The middleware’s loopback allowance (allow_no_auth_hosts, defaulting to 127.0.0.1 and ::1) means localhost callers skip authentication even when auth is enabled — a defensible convenience that turns every server-side request forgery or local process into an unauthenticated management client. And the repository has seen 49 commits since 9 October with nothing touching the auth, driver, or MCP security posture; the issue stays closed, and the interface behaves exactly as it did during the intrusion. There is no fixed version because, in the maintainer’s framing, there is nothing to fix.
The dispute is the finding
Refuse the urge to pick a side and the briefing writes itself. The reporter is correct that an unauthenticated HTTP request could become root command execution and did, with a seventeen-day dwell and a miner as the receipt. The maintainer is correct that each step required the operator to arrange the blast radius first: bind all interfaces, run as root, leave auth off. The uncomfortable part is how ordinary that arrangement is. “Run it on your own machine or on the cloud” is the product’s tagline; cloud deployments behind panels and reverse proxies routinely terminate TLS at the edge and forward to a service listening wide open on the loopback-that-isn’t. The reporter’s systemd unit overriding config.json’s loopback with --host 0.0.0.0 is not exotic misconfiguration — it is what every Docker port-mapping, every panel deploy button, and every “make it accessible from outside” tutorial produces.
Place this against the October run of sibling records and the pattern sharpens. Archon’s CVE-2026-111322 was the same shape with one decisive difference: a 0.0.0.0 default, which earned it a 9.8 and an unauthenticated agent control plane. QwenPaw’s loopback default is genuinely better — it is the difference between a CVE and a closed issue. But defaults only defend the operators who keep them, and the product offers no friction at the moment they don’t: no warning when binding 0.0.0.0 with auth disabled, no requirement to create an admin account before the management port accepts driver registrations, no confirmation that the command about to spawn came from an authenticated session. ZCode and Junie, briefed here days ago, showed what happens when repository-borne MCP entries execute without a trust decision; QwenPaw shows the server-side mirror, where the trust decision exists as an environment variable nobody set.
Related reading on this site: the Archon open-bind briefing for the default that turns this exact shape into a CVE, and the DeepSeek harness search-path RCE for another case where the launcher’s own resolution behavior is the exploit.
What to do
Set QWENPAW_AUTH_ENABLED=true, register the admin account, and restart — before exposing the port. The flag alone is not enough; the middleware also skips auth until a user exists, so enablement without registration leaves the window open. The maintainer’s own remediation list says this, and it is correct.
Never run the service as root, and treat --host 0.0.0.0 as a security event. The miner’s root privileges, the SSH key implant in /root/.ssh/, and the systemd persistence all flowed from User=root, not from QwenPaw. A dedicated low-privilege user converts this incident from host takeover to a contained sandbox escape. Audit systemd units, compose files, and panel templates for --host 0.0.0.0 overrides that contradict the loopback in config.json — the victim’s unit is proof the two can disagree silently.
Monitor the driver table the way you monitor cron. Driver creation is process execution by another name: alert on new rce-poc-style names, on cards whose commands you did not author, on marker files under /tmp/.qp_rce_*, and on any write to the drivers directory from an unexpected source IP. The reporter’s indicators — C2 176.65.149.237 on 7777/4444, the worker.service unit, the hidden /usr/lib/.*/ paths — are worth blocking regardless of which agent framework you run.
Ask the question the closed issue leaves open. Should a management API that spawns server-side processes accept unauthenticated requests on any interface, with authentication as an opt-in flag? The maintainer answered no-action-needed; the seventeen days of root suggest the question deserves more than one respondent. Until it gets one, assume every agent management plane you expose behaves like this one and harden accordingly.
Sourcing: issue #8153 title, timestamps, timeline, log excerpts, IOCs, and the maintainer’s response read first-hand via the GitHub API; default bind, auth-flag behavior, middleware skip logic, MCP route shape, request-schema defaults, and driver-reload behavior read first-hand from the cited source files at main; repository metadata (35,761 stars, 3,185 forks, TypeScript, Apache-2.0, created 24 February 2026) from the GitHub repository API; post-9-October commit history reviewed for security-relevant changes. Brave Search used for discovery only. Nothing installed, no QwenPaw instance run, no driver registered, no payload crafted or executed, no third-party request sent beyond reading the cited public records. The intrusion account is the reporter’s; we verified the mechanism it describes, not the incident itself.