← All research

ENDOR-VUL-2026-16041

Server-Side RCE via SES Compartment Escape in variables:resolve

01Record

ProjectNocoBase
Component@nocobase/plugin-flow-engine
CWECWE-94
CVSS9.9
Disclosed2026-08-08
Endor referenceENDOR-VUL-2026-16041
WriteupLink

02Detail

Description: NocoBase evaluates user-supplied template expressions inside an SES Compartment to resolve {{ }} placeholders in workflow config. Four defects compound into a full escape, and every one of them is a real defense that does not hold:

  • The ACL grants variables:resolve to any loggedIn user, which includes the lowest-privilege member role.
  • preprocessExpression() rewrites only literal ctx. and ctx[ tokens through indexOf, so passing ctx into an arrow function parameter and aliasing it renders the rewriter blind.
  • HttpRequestContext stores the raw Koa context in a TypeScript private field. private is a compile-time fiction that compiles to an ordinary instance property, so the Reflect.has check in the Proxy trap returns true for exactly the field the Proxy was built to hide.
  • The SES lockdown() call that would harden intrinsics is commented out behind a // TODO.

The result is arbitrary SQL through sequelize.query() from a single authenticated POST, returning the users table with password hashes in the response body. On PostgreSQL with a superuser database role, COPY … TO PROGRAM runs a shell command on the database host, which is the scope change from application database read to OS execution that carries the score to 9.9. Affects NocoBase through 2.0.38.

Impact: Authenticated SQL Injection escalating to Code Execution on the database host, from the lowest-privileged user in a single request.

The chain is closed in the shipped code. GitHub Security Advisory GHSA-42wx-r3jw-6c5h was assigned and is still in draft as of 2026-08-24, so there is no advisory page to cite. This record will be re-keyed to the advisory when it publishes. Full analysis is in the technical whitepaper.

Discovered by Peyton Kennedy (p80n).