CVE-2026-114081
Trigger.dev before 4.5.6 Cross-Tenant SQL Injection via TSQL Window-Function Name
Trigger.dev before 4.5.6 contains an SQL injection vulnerability in the TSQL query compiler, which concatenates window-function names into generated ClickHouse SQL without allowlisting or escaping. Authenticated customers can submit a backtick-quoted identifier to POST /api/v1/query that injects a raw subquery outside the per-tenant WHERE guard, exfiltrating other organizations' analytics data.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
8.3
Trigger.dev before 4.5.6 Cross-Tenant SQL Injection via TSQL Window-Function Name
Trigger.dev before 4.5.6 contains an SQL injection vulnerability in the TSQL query compiler, which concatenates window-function names into generated ClickHouse SQL without allowlisting or escaping. Authenticated customers can submit a backtick-quoted identifier to POST /api/v1/query that injects a raw subquery outside the per-tenant WHERE guard, exfiltrating other organizations' analytics data.
- Record
CVE-2026-114081- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N - CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-89
- Updated
- 2026-11-02
EUVDEUVD-2026-106884
8.3
Trigger.dev before 4.5.6 contains an SQL injection vulnerability in the TSQL query compiler, which concatenates window-function names into generated ClickHouse SQL without allowlisting or escaping. Authenticated customers can submit a backtick-quoted identifier to POST /api/v1/query that injects a raw subquery outside the per-tenant WHERE guard, exfiltrating other organizations' analytics data.
- Record
EUVD-2026-106884- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-9q4r-4842-93vw
7.7
Trigger.dev: Cross-tenant SQL injection in the TSQL query compiler (POST /api/v1/query) via unsanitized window-function name
Summary
A cross-tenant SQL injection in the TSQL query compiler lets any authenticated trigger.dev customer read every other tenant's analytics data. The customer-facing query endpoint POST /api/v1/query accepts a TSQL/TRQL query that is compiled to ClickHouse SQL by internal-packages/tsql. The compiler parameterizes or escapes all user input and injects a per-tenant WHERE guard — except the window-function name, which is concatenated into the SQL string with no allowlist and no escaping. By smuggling a backtick-quoted identifier into that position, an attacker injects a raw subquery (e.g. (SELECT ... FROM task_runs_v2 WHERE organization_id = 'org_VICTIM')) that sits outside the tenant guard, exfiltrating another organization's rows. Verified end-to-end against the real compiler and a live ClickHouse.
Details
Vulnerable sink — internal-packages/tsql/src/query/printer.ts:3073-3075, ClickHousePrinter.visitWindowFunction:
private visitWindowFunction(node: WindowFunction): string {
const args = node.args ? node.args.map((a) => this.visit(a)) : [];
const funcCall = `${node.name}(${args.join(", ")})`; // <-- node.name concatenated RAW
...
}
node.name is emitted directly into the SQL with no allowlist check and no identifier escaping. This is the only place in the compiler where an attacker-influenced identifier reaches the output unguarded:
- The normal function-call path
visitCall(printer.ts:2951) throwsUnknown functionfor any name outside the hardcodedTSQL_CLICKHOUSE_FUNCTIONS/TSQL_AGGREGATIONSallowlists — this gate is absent on the window-function path. - String constants are bound as ClickHouse
query_params(parameterized). - Other identifiers go through
escapeClickHouseIdentifier. - Table functions (
url()/file()/remote()/s3()) are rejected.
A backtick-quoted identifier is accepted by the lexer and unescaped into node.name by visitIdentifier (the backticks are stripped and the inner text is unescaped), so arbitrary characters — spaces, (, ), ,, ', a full subquery — become the "function name" and are printed verbatim.
How it bypasses tenant isolation. Multi-tenancy is enforced only by enforcedWhereClause, which is attached to the outer table's WHERE (organization_id/project_id/environment_id taken from the caller's API key). An injected subquery has no such guard, so it reads across all tenants.
Reachability — apps/webapp/app/routes/api.v1.query.ts:
body.queryis a rawz.string().- Auth is any environment-scoped credential — a private API key or a public JWT — i.e. any signed-up customer.
- The route's authorization (
detectTables(body.query)+everyResource) only authorizes the outerFROMtable the caller is legitimately allowed to read. The injection rides in the SELECT/window position, so it is invisible to that check. executeQuerypasses the caller'sorganizationId/projectId/environmentIdinto the enforcedWHERE. The compiledsqlstring is then sent to ClickHouse (internal-packages/clickhouse/src/client/tsql.ts) with the injected subquery embedded in the SQL string itself (not in bound params), so ClickHouse executes it.
PoC
Reproduced in two stages: (1) the project's real compileTSQL emits the injection; (2) a live ClickHouse executes it and returns another tenant's data.
1. Attacker TSQL input (sent as the query field to POST /api/v1/query with any valid API key / JWT, authenticated here as tenant1):
SELECT `count() OVER (), (SELECT groupArray(payload) FROM trigger_dev.task_runs_v2 WHERE organization_id = 'org_OTHER_TENANT') AS stolen, dummy(`() OVER () AS x FROM task_runs
2. Compiled ClickHouse SQL emitted by compileTSQL (verbatim):
SELECT count() OVER (),
(SELECT groupArray(payload) FROM trigger_dev.task_runs_v2
WHERE organization_id = 'org_OTHER_TENANT') AS stolen, …
- Record
GHSA-9q4r-4842-93vw- CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-89, CWE-639
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-vmhj-4cqp-m5cj
8.3
Trigger.dev before 4.5.6 contains an SQL injection vulnerability in the TSQL query compiler,...
Trigger.dev before 4.5.6 contains an SQL injection vulnerability in the TSQL query compiler, which concatenates window-function names into generated ClickHouse SQL without allowlisting or escaping. Authenticated customers can submit a backtick-quoted identifier to POST /api/v1/query that injects a raw subquery outside the per-tenant WHERE guard, exfiltrating other organizations' analytics data.
- Record
GHSA-vmhj-4cqp-m5cj- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-89
- Updated
- 2026-11-02
NVD
8.3
Trigger.dev before 4.5.6 contains an SQL injection vulnerability in the TSQL query compiler, which concatenates window-function names into generated ClickHouse SQL without allowlisting or escaping. Authenticated customers can submit a backtick-quoted identifier to POST /api/v1/query that injects a raw subquery outside the per-tenant WHERE guard, exfiltrating other organizations' analytics data.
- Record
CVE-2026-114081- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-89
- Updated
- 2026-11-02
OSV
8.3
Trigger.dev before 4.5.6 Cross-Tenant SQL Injection via TSQL Window-Function Name
Trigger.dev before 4.5.6 contains an SQL injection vulnerability in the TSQL query compiler, which concatenates window-function names into generated ClickHouse SQL without allowlisting or escaping. Authenticated customers can submit a backtick-quoted identifier to POST /api/v1/query that injects a raw subquery outside the per-tenant WHERE guard, exfiltrating other organizations' analytics data.
- Record
CVE-2026-114081- CVSS 4.0
- 8.3 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N - Weakness
- CWE-89
- Updated
- 2026-11-02
OSVGHSA-9q4r-4842-93vw
7.7
Trigger.dev: Cross-tenant SQL injection in the TSQL query compiler (POST /api/v1/query) via unsanitized window-function name
Summary
A cross-tenant SQL injection in the TSQL query compiler lets any authenticated trigger.dev customer read every other tenant's analytics data. The customer-facing query endpoint POST /api/v1/query accepts a TSQL/TRQL query that is compiled to ClickHouse SQL by internal-packages/tsql. The compiler parameterizes or escapes all user input and injects a per-tenant WHERE guard — except the window-function name, which is concatenated into the SQL string with no allowlist and no escaping. By smuggling a backtick-quoted identifier into that position, an attacker injects a raw subquery (e.g. (SELECT ... FROM task_runs_v2 WHERE organization_id = 'org_VICTIM')) that sits outside the tenant guard, exfiltrating another organization's rows. Verified end-to-end against the real compiler and a live ClickHouse.
Details
Vulnerable sink — internal-packages/tsql/src/query/printer.ts:3073-3075, ClickHousePrinter.visitWindowFunction:
private visitWindowFunction(node: WindowFunction): string {
const args = node.args ? node.args.map((a) => this.visit(a)) : [];
const funcCall = `${node.name}(${args.join(", ")})`; // <-- node.name concatenated RAW
...
}
node.name is emitted directly into the SQL with no allowlist check and no identifier escaping. This is the only place in the compiler where an attacker-influenced identifier reaches the output unguarded:
- The normal function-call path
visitCall(printer.ts:2951) throwsUnknown functionfor any name outside the hardcodedTSQL_CLICKHOUSE_FUNCTIONS/TSQL_AGGREGATIONSallowlists — this gate is absent on the window-function path. - String constants are bound as ClickHouse
query_params(parameterized). - Other identifiers go through
escapeClickHouseIdentifier. - Table functions (
url()/file()/remote()/s3()) are rejected.
A backtick-quoted identifier is accepted by the lexer and unescaped into node.name by visitIdentifier (the backticks are stripped and the inner text is unescaped), so arbitrary characters — spaces, (, ), ,, ', a full subquery — become the "function name" and are printed verbatim.
How it bypasses tenant isolation. Multi-tenancy is enforced only by enforcedWhereClause, which is attached to the outer table's WHERE (organization_id/project_id/environment_id taken from the caller's API key). An injected subquery has no such guard, so it reads across all tenants.
Reachability — apps/webapp/app/routes/api.v1.query.ts:
body.queryis a rawz.string().- Auth is any environment-scoped credential — a private API key or a public JWT — i.e. any signed-up customer.
- The route's authorization (
detectTables(body.query)+everyResource) only authorizes the outerFROMtable the caller is legitimately allowed to read. The injection rides in the SELECT/window position, so it is invisible to that check. executeQuerypasses the caller'sorganizationId/projectId/environmentIdinto the enforcedWHERE. The compiledsqlstring is then sent to ClickHouse (internal-packages/clickhouse/src/client/tsql.ts) with the injected subquery embedded in the SQL string itself (not in bound params), so ClickHouse executes it.
PoC
Reproduced in two stages: (1) the project's real compileTSQL emits the injection; (2) a live ClickHouse executes it and returns another tenant's data.
1. Attacker TSQL input (sent as the query field to POST /api/v1/query with any valid API key / JWT, authenticated here as tenant1):
SELECT `count() OVER (), (SELECT groupArray(payload) FROM trigger_dev.task_runs_v2 WHERE organization_id = 'org_OTHER_TENANT') AS stolen, dummy(`() OVER () AS x FROM task_runs
2. Compiled ClickHouse SQL emitted by compileTSQL (verbatim):
SELECT count() OVER (),
(SELECT groupArray(payload) FROM trigger_dev.task_runs_v2
WHERE organization_id = 'org_OTHER_TENANT') AS stolen, …
- Record
GHSA-9q4r-4842-93vw- CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-639, CWE-89
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
trigger.devnpm |
through 4.5.5 | 4.5.6 |
trigger.devnpm |
before 4.5.6 | 4.5.6 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-114081 is a high vulnerability (CVSS 8.3) in Triggerdotdev Trigger.dev, published October 2, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.40%, the 33rd percentile. Fixed in trigger.dev 4.5.6.
References
github.com/triggerdotdev/trigger.dev/security/advisories/GHSA-9q4r-4842-93vw
vulncheck.com/advisories/trigger-dev-before-4.5.6-cross-tenant-sql-injection-via-tsql-window-function-name
github.com/triggerdotdev/trigger.dev/pull/4316
github.com/triggerdotdev/trigger.dev/commit/6997aeb05e27d2db47f9eda01fdc8a17c81a1ae0
github.com/triggerdotdev/trigger.dev/releases/tag/v4.5.6
github.com/advisories/GHSA-9q4r-4842-93vw
Left out here: the rest of 2 source descriptions. The JSON record has them.