Two High-severity findings that both execute before the isolate exists, one reflected XSS that puts attacker data inside a script block, and the timeline that explains why the V8 isolate contributed nothing to either. When a product's premise is "write arbitrary code and we will run it safely," the sandbox is not a feature. It is the product. Everything else is a UI on top of one promise. So the question I ask about these platforms is never whether the sandbox is any good. Sandboxes are usually fine. V8 isolates work. nsjail works. The question that actually pays is narrower: When does it start? A sandbox is not a property of a system. It is a line in a source file. Above that line, attacker-influenced work runs with whatever privileges the host process has. Below it, the boundary applies. Everybody audits below the line, because that is where the interesting-looking code is. The bugs live above it. Activepieces published three advisories in March, patched across 0.80.0 and 0.83.0, CVEs assigned in August. Two of them are the same architectural fact hit at two different points on the same timeline. The third is a different surface with the same underlying failure. This piece is about that timeline. Credit, up front I want the attribution clean before any of the technical content, because incomplete-fix and co-discovery work only stays useful if the record is accurate. CVE GHSA Severity Reported by CVE-2026-73081 GHSA-3pfv-m69p-5fv5 High p80n-sec, kodareef5, Aviral Srivastava CVE-2026-73083 GHSA-gr3h-c2j7-r52g High p80n-sec, Aviral Srivastava, q1uf3ng CVE-2026-73084 GHSA-hc39-cm5m-q8g7 Moderate Aviral Srivastava, diemoeve I opened the XSS advisory on March 11 and I am a credited co-reporter on the two High findings, which p80n-sec opened on March 30 and on which a third researcher was credited in each case. Three people converging on the same class inside a three week window is not a coincidence and it is not a competition. It is what happens when a surface is under-audited and several people start pulling the same thread. The advisories name everybody, and so does this. The Target and the Four Modes Activepieces is an open source workflow automation platform, roughly 23,400 stars, self-hosted by people wiring SaaS integrations together. You build flows on a canvas. One step type is a Code step, where you write JavaScript that runs as part of the flow. The Code step is the entire reason a sandbox exists. Four execution modes ship: UNSANDBOXED no isolation SANDBOX_PROCESS process isolation only SANDBOX_CODE_ONLY V8 Isolate, no Node.js APIs SANDBOX_CODE_AND_PROCESS both SANDBOX_CODE_ONLY is the mode worth attacking, because it is the only one where the V8 isolate is load-bearing. In the other three, either there is no isolate or there is a process sandbox underneath it doing the real work. If the isolate is going to fail meaningfully anywhere, it fails there. The Lifecycle, and Where the Line Sits The productive move is not to attack the isolate. It is to trace what happens to a Code step between submission and execution, and mark the exact statement where the boundary begins. Roughly: 1. HTTP request creates or edits a flow 2. Request-level schema validation 3. Step record persisted 4. Worker picks up the flow 5. Worker writes step to disk 6. Worker COMPILES the step /tmp/x') export const code = async (inputs) => { return { ok: true } } The require and the execSync are top-level module body. They run during importFresh(), in the host engine process, with full Node.js access. The code function, the only part anybody was ever going to sandbox, does nothing. The one-line version: they isolated the output of the untrusted computation instead of the computation. Impact under SANDBOX_CODE_ONLY is a complete sandbox bypass, and the secrets involved are the ones that matter. AP_ENCRYPTION_KEY decrypts every stored credential on the instance. AP_JWT_SECRET lets you forge tokens. Add filesystem read and write and network reach into internal services. The advisory records that the reporter confirmed root-level execution and secret exfiltration against activepieces/activepieces:latest in that mode. Impact is confidentiality and integrity. No availability impact was demonstrated, which is why VA:N in the vector rather than reaching for the bigger number. The fix is the elegant one. User code is no longer loaded through require() or importFresh at all. The compiled module is read from disk as text and executed in its entirety inside the isolate via compileScript. Read that difference carefully, because it is the whole lesson. Before: pass a path to a loader, and the loader executes. After: pass bytes to a compiler that runs inside the boundary. Same file, same content, and the second one cannot execute outside the isolate because nothing outside the isolate ever runs it. Then the wrapper. The module is wrapped so exports and module are defined but require is not. Any top-level attempt to pull in a Node built-in now throws ReferenceError inside the isolate rather than executing in the host. That is a much better failure mode than blocking a list of module names, because it removes the capability instead of enumerating the bad uses of it. The Matrix Nobody Draws Put the two High findings against the four modes and the picture stops being about two bugs. Mode CVE-2026-73081 CVE-2026-73083 UNSANDBOXED Affected No isolate to bypass SANDBOX_PROCESS Affected No isolate to bypass SANDBOX_CODE_ONLY Affected Complete bypass SANDBOX_CODE_AND_PROCESS Affected Bypass occurs, contained by process sandbox Two things fall out of that table. The compile-time injection defeats all four modes, because it fires before any of them exist. The mode setting is not a mitigation for it. It is not even a variable. And the isolate bypass fully defeats exactly one mode, which is precisely the mode that exists to provide code-level isolation without the overhead of process isolation. SANDBOX_CODE_ONLY is the configuration where the isolate is the only boundary, and it is the configuration the bypass completely removes. Where the isolate is redundant, the bypass is contained. Where it is load-bearing, it is not a boundary at all. So across both findings, in every mode, the security of the system reduced to process isolation. The V8 isolate contributed nothing to either outcome. That is not a criticism of V8 isolates, which are a sound primitive. It is what happens when a strong boundary is installed at the wrong point in a pipeline. This is why the advisory's workaround for the bypass is not "harden the isolate." It is: do not run in SANDBOX_CODE_ONLY, use SANDBOX_CODE_AND_PROCESS, and treat process-level isolation as the minimum baseline for multi-tenant deployments regardless. CVE-2026-73084: Data in a Code Channel Moderate. CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N, 6.1. CWE-79, CWE-94. Affected below 0.83.0, fixed in 0.83.0. Different surface, same underlying failure. This is the one I opened, on March 11. /api/redirect is the OAuth callback target. It embedded the user-supplied code query parameter directly into the response's inline block without escaping. A crafted URL breaks out of the script context and executes arbitrary JavaScript in the Activepieces origin when a logged-in user opens it. PR:N and UI:R. An unauthenticated attacker, a logged-in victim who opens the link. From there: the victim's session tokens, or authenticated API calls made on their behalf. The fix does the correct thing rather than the tempting thing. The tempting thing is to escape the value. The correct thing is to stop putting it in a code channel at all. The endpoint now renders through an HTML-escaping template, passes the OAuth code via a tag, and reads it into the script from there. The value can never be interpreted as JavaScript source because it is no longer in a position where JavaScript source is expected. CSP and X-Content-Type-Options: nosniff are added as defence in depth. On process: I filed this on March 11 and followed up on March 29 noting a month of silence. The maintainer response on March 31 was that the team had only just been notified, because the reporting channel was not sending them alerts. That is a failure mode worth naming, since a private advisory that nobody receives looks identical from the outside to a vendor ignoring you. Once they saw it, the fix and the credit were handled properly. What Connects All Three Strip the sandbox architecture out and the three findings are one sentence: Attacker-controlled data reached a context that executes. A step name reached a shell, and a shell executes. A module path reached require(), and require() executes. A query parameter reached a block, and a script block executes. And all three fixes have the same shape, which is not escaping. It is keeping data in a data channel. An identifier allowlist plus an argv array, so the path is an argument and never syntax. Reading the module as text and compiling it inside the boundary, so no loader outside the boundary executes anything. A tag read into script, so the value is never in source position. Escaping asks the developer to correctly predict every way a parser might interpret their string. Channel separation removes the parser from the equation. One of those degrades when somebody adds a field in six months and one does not. The Audit, Generalised The method here is not clever and that is the point. For any system that promises to run untrusted code safely: Find the line. Locate the exact statement where the sandbox is created. Not the config option. The statement. Enumerate everything above it that touches attacker input. Not just the code body. Every field on the record: names, IDs, paths, settings, metadata. All of them crossed the same trust boundary and none of them are going inside. For each one, ask what parses it. A shell, a loader, a template engine, a query builder, a deserializer. Anything that turns a string into behaviour is a sink, and above the line there is no boundary to catch it. Check what "load" means in your runtime. require, import(), eval, vm.runInThisContext, Function, dlopen. Several of these execute as a side effect of loading, and if the thing you sandbox is their return value rather than their input, you have sandboxed the wrong artifact. This generalises well beyond one workflow platform. Every CI runner, every build service, every serverless platform, every AI agent framework that lets a user supply code has this shape: a compile or load phase in a privileged process, followed by an execution phase in a restricted one. The security attention goes to the second phase. The first phase is where you should look. What To Do Self-hosting Activepieces: upgrade. 0.80.0 or later closes both High findings, 0.83.0 or later closes the XSS. If you are running anything at or below 0.79.4, you are exposed to all three. If you cannot upgrade today: restrict flow create and edit permission to trusted users, run the worker as a non-root least-privilege user, and do not run SANDBOX_CODE_ONLY. Block /api/redirect at the reverse proxy if OAuth is not in use on your deployment. If you build one of these platforms: go and find the line where your sandbox starts, then list every attacker-influenced value your code touches before it. That list is your actual attack surface. The sandbox is doing an excellent job on everything after it. CVE-2026-73081 | GHSA-3pfv-m69p-5fv5 | High | Fixed 0.80.0 CVE-2026-73083 | GHSA-gr3h-c2j7-r52g | High | Fixed 0.80.0 CVE-2026-73084 | GHSA-hc39-cm5m-q8g7 | Moderate 6.1 | Fixed 0.83.0 Fix PR: activepieces#12310 Reported alongside p80n-sec, kodareef5, q1uf3ng and diemoeve. Full credit lists are on the advisories.
Breaking Down Three Activepieces Vulnerabilities in Code Execution Pipelines
Full Article
Original Source
Read the full article at Hackernoon →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.