Oct 2, 2026·5 min read·1 visit
Authenticated attackers can execute tasks inside other tenants' environments by specifying a foreign environmentId during task replay, bypassing tenant isolation.
A critical Broken Object Level Authorization (BOLA) vulnerability was identified in Trigger.dev before version v4.5.2. An authenticated attacker could trigger a run replay and supply an arbitrary target environmentId belonging to a completely different tenant. Because the server failed to validate whether the target environment belonged to the same project or organization as the source run, it would execute the task within the victim's environment, resulting in unauthorized cross-tenant write operations and remote task execution.
Trigger.dev is an open-source background jobs framework designed to facilitate complex task orchestration and scheduling. The platform exposes various API endpoints to trigger, monitor, and replay task executions. Security assessment of the task replay workflow revealed a critical Broken Object Level Authorization (BOLA) vulnerability, tracked as GHSA-QXPP-QJG8-X4JV.
This vulnerability allows an authenticated attacker to inject and execute arbitrary tasks in a victim's execution environment. The attack is executed by manipulating the target environment identifier during a task replay request. Because the backend does not validate organization or project boundaries when resolving the target environment, cross-tenant execution is achieved.
The vulnerability affects all self-hosted and cloud deployments of Trigger.dev prior to version v4.5.2. This vulnerability has been remediated in the official v4.5.2 security release.
The root cause of GHSA-QXPP-QJG8-X4JV lies in the logical implementation of the task run replay service, specifically within the ReplayTaskRunService component. In Trigger.dev, task runs are associated with runtime environments which belong to projects and organizations. When a user requests a replay of a completed task, they can optionally specify override options including a new target environment identifier via the environmentId parameter.
The service handled this override by querying the database for the provided environment ID using the internal lookup function findEnvironmentById. This lookup did not apply any multi-tenancy constraints or check whether the target environment belonged to the same project or organization as the original task run.
Because the lookup operated on a global database scope, any valid environment ID could be resolved. Consequently, when the service proceeded to queue and schedule the replayed task, it initiated execution context within the retrieved target environment, even if that environment belonged to an entirely different customer tenant.
To understand the precise vulnerability and its remediation, analyze the difference in apps/webapp/app/v3/services/replayTaskRun.server.ts before and after the security patch. In the vulnerable version, the service class accepted the input and immediately resolved the target environment.
// Pre-patch implementation
export class ReplayTaskRunService extends BaseService {
public async call(existingTaskRun: TaskRun, overrideOptions: OverrideOptions = {}) {
const authenticatedEnvironment = await findEnvironmentById(
overrideOptions.environmentId ?? existingTaskRun.runtimeEnvironmentId
);
// ... execution continues without verifying project tenancy
}
}The security patch introduced in commit 34b1a181c2a1d33a53ebab88f84b05f81fea4254 introduces a strict boundary check. If an override environment ID is supplied and differs from the source environment, the server retrieves both records and compares their respective project identifiers.
// Patched implementation
export class ReplayTaskRunService extends BaseService {
public async call(existingTaskRun: TaskRun, overrideOptions: OverrideOptions = {}) {
if (
overrideOptions.environmentId &&
overrideOptions.environmentId !== existingTaskRun.runtimeEnvironmentId
) {
const [overrideEnvironment, sourceEnvironment] = await Promise.all([
this._prisma.runtimeEnvironment.findFirst({
where: { id: overrideOptions.environmentId },
select: { projectId: true },
}),
this._prisma.runtimeEnvironment.findFirst({
where: { id: existingTaskRun.runtimeEnvironmentId },
select: { projectId: true },
}),
]);
if (
!overrideEnvironment ||
!sourceEnvironment ||
overrideEnvironment.projectId !== sourceEnvironment.projectId
) {
logger.warn("Refusing to replay a run into an environment outside its project", {
taskRunId: existingTaskRun.id,
sourceEnvironmentId: existingTaskRun.runtimeEnvironmentId,
overrideEnvironmentId: overrideOptions.environmentId,
});
throw new Error("Cannot replay a run into an environment outside its project");
}
}
const authenticatedEnvironment = await findEnvironmentById(
overrideOptions.environmentId ?? existingTaskRun.runtimeEnvironmentId
);
}
}The remediation is complete because it relies on project-level matching rather than user input. By querying both environmental records from the database and demanding strict equivalence of their projectId values, the system prevents cross-project resource pollution.
Exploitation of GHSA-QXPP-QJG8-X4JV requires the attacker to possess a valid account on the target Trigger.dev instance. This grants the attacker the ability to execute their own tasks and perform replay operations. The attacker must also acquire the target environment identifier of the victim's workspace.
With these prerequisites satisfied, the attacker generates a standard HTTP POST request to the task replay endpoint of one of their own tasks. By inserting the victim's environment identifier into the overrideOptions JSON object, the attacker directs the execution context to the victim's workspace.
POST /api/v1/runs/run_attacker_1/replay HTTP/1.1
Host: app.trigger.dev
Authorization: Bearer tr_secret_attacker_api_key
Content-Type: application/json
{
"overrideOptions": {
"environmentId": "victim-environment-id-here"
}
}Upon receiving this request, the vulnerable server schedules the execution of the attacker's tasks inside the victim's environment. The tasks run with the permissions, environment variables, and connections configured in the victim's workspace, leading to unauthorized write operations.
The impact of this Broken Object Level Authorization vulnerability is high, representing a complete breach of tenant isolation. An attacker executing arbitrary tasks inside a victim's environment can read sensitive secrets, manipulate external databases connected to the background tasks, or execute server-side code within the runner infrastructure.
Furthermore, because background tasks are executed on the victim's compute resources, an attacker can consume the victim's execution quotas, resulting in resource exhaustion and financial loss. The scope of exploitation is classified as Changed because the attacker leverages an account in one logical scope to manipulate assets in an entirely separate security domain.
Applying the CVSS v3.1 framework, this vulnerability yields a score of 9.9. The high severity is driven by the ease of exploitation, low privileges required, and severe consequences for confidentiality, integrity, and availability.
To mitigate this vulnerability, administrators of self-hosted Trigger.dev instances must upgrade to version v4.5.2 or later. This release enforces the structural checks required to block cross-project task replays.
For organizations operating self-hosted instances that cannot immediately upgrade, access to the Trigger.dev API and dashboard should be restricted using network-level controls or web application firewall policies. Modifying database records or applying temporary application-level middleware to drop replay requests containing the overrideOptions.environmentId field can also serve as temporary workarounds.
Development teams should establish automated security tests to verify authorization boundaries during API operations. Specifically, test cases should assert that attempting to access, update, or execute resources across different project identifiers results in an authorization failure.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Trigger.dev Trigger.dev | < 4.5.2 | 4.5.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-639 / CWE-285 |
| Attack Vector | Network |
| CVSS v3.1 Score | 9.9 (Critical) |
| Exploit Status | poc |
| KEV Status | Not Listed |
| Vulnerability Class | Broken Object Level Authorization (BOLA) |
The system fails to check if the user is authorized to access or modify a resource when they provide a direct identifier.
A critical Server-Side Request Forgery (SSRF) vulnerability in Trigger.dev prior to version 4.5.2 allows authenticated organization members to configure webhook alert channels with unvalidated target URLs. This can lead to internal network scanning and cloud metadata extraction.
A Server-Side Request Forgery (SSRF) vulnerability exists in the rmcp OAuth client, which is part of the Model Context Protocol (MCP) Rust SDK. The vulnerability arises from insecure processing of the resource_metadata parameter in WWW-Authenticate headers returned by a malicious or compromised MCP server. The client parses and fetches absolute URLs from this header without validation of scheme, origin, or network routing, allowing remote attackers to initiate HTTP GET requests to local network interfaces, RFC 1918 private subnets, or cloud metadata endpoints.
A module allowlist bypass vulnerability (CVE-2026-92945 / GHSA-7q3f-wx44-378m) was identified in the vm2 sandboxing library prior to version 3.11.7. This flaw permits unauthenticated or untrusted code running within the sandbox environment to bypass explicit module restrictions. When the transitive resolution option is disabled, the system fails to validate file system path boundaries, allowing prefix-sharing sibling directories to be resolved and loaded, thereby escaping intended sandbox restrictions.
CVE-2026-92941 is a critical sandbox-escape and trust-manipulation vulnerability in the vm2 library (versions 3.11.3 to 3.11.6). This security flaw allows untrusted code executing within a NodeVM sandbox environment to compromise the global TLS trust store of the host Node.js process. By leveraging a design flaw where the host's native 'tls.setDefaultCACertificates' can be executed via a proxy wrapper, combined with a bridge unwrapping bypass in the 'url' module, an attacker can modify the process-wide default root Certificate Authorities. Consequently, all subsequent outbound TLS/HTTPS clients running on the host thread are forced to trust attacker-signed certificates, facilitating transparent Man-in-the-Middle (MitM) attacks. The vulnerability was resolved in version 3.11.7 of vm2 by introducing built-in member-level sanitization before applying read-only proxy wrappers.
A critical engine-level reachability failure in Node.js 26 running V8 14.6 allows attackers to escape the vm2 sandbox environment. When consecutive prototype properties are modified using sequential assignments, a V8 optimization bug fails to invalidate the PromiseThenLookupChain protector. By calling Promise.prototype.finally, the attacker bypasses the vm2 wrappers, hijacks the promise reaction using a custom constructor, triggers a calibrated stack overflow to capture a host-realm RangeError, and executes arbitrary shell commands on the host.
A critical sandbox escape vulnerability in the vm2 library allows sandboxed JavaScript code to bypass containment and execute arbitrary native code on the host process. This occurs when the host's builtin crypto module is exposed to the NodeVM environment. Although vm2 implements a read-only proxy layer to restrict direct modifications to host properties, it does not prevent invocation of host-level functions. By calling the crypto.setEngine API with a path to a malicious native library on disk, an attacker can trigger OpenSSL's dynamic module loader. The host's operating system loader immediately runs the dynamic library's initializers/constructors before verifying engine compatibility, leading to remote code execution in the context of the host process.