security: upgrade typeorm to 0.3.26 (CVE-2025-60542) (#21456)

## Context

Retry of the typeorm upgrade that was pulled out of #21448 after CI
showed "intermittently lossy metadata sync". **The investigation
exonerated typeorm**: the postcard/seed failures were a pre-existing bug
in `@ptc-org/nestjs-query-typeorm`'s batched relation paging (global
LIMIT across parents) that scan-order luck had been hiding — reproduced
byte-for-byte on typeorm **0.3.20** against a frozen repro DB. That bug
is fixed in #21455, which this PR is stacked on (base branch =
`charles/fix-nestjs-query-batch-relation-paging`; will retarget to main
when it merges).

## Changes

- typeorm `0.3.20` → `0.3.26`
([CVE-2025-60542](https://github.com/advisories/GHSA-q2pj-6v73-8rgj),
MEDIUM). The CVE lives in TypeORM's MySQL path
(`sqlstring`/`stringifyObjects`); Postgres-only Twenty never exercises
it — this is scanner hygiene + staying current.
- The local yarn patch (`PickKeysByType` + `DeleteResult.generatedMaps`)
applies **verbatim** to 0.3.26 (verified against the pristine tarball) —
renamed to `typeorm+0.3.26.patch`.
- `WorkspaceRepository.query` restricted override adapted to the generic
`query<T = any>()` base signature introduced in 0.3.24 (one-line change,
still throws `RAW_SQL_NOT_ALLOWED`).
- 0.3.26 ships `uuid ^11` natively → the scoped `typeorm/uuid`
resolution from #21441 and its `//resolutions` comment clause (including
the now-disproven "lossy sync" warning) are removed.

## Why we're confident this time

The original failure signature was fully understood, not just retried:
- On a frozen failing DB, **all fieldMetadata rows + workspace columns
were intact** — only the batched metadata API read was truncated (`LIMIT
501` over 558 rows, no ORDER BY).
- Same DB, typeorm 0.3.20: identical truncation, identical SQL → not a
typeorm regression.
- With #21455 applied: postcard install/uninstall stress loop **12/12
green on typeorm 0.3.26** (previously failed within 1–2 iterations), API
returns 558/558 fields.

## Verification

- `npx nx typecheck twenty-server` — clean
- Full `twenty-server` unit suite — green (5651 passed)
- `group-by-resolver` integration suite — 19/19 on a fresh 0.3.26-seeded
test DB
- Postcard app-sync stress loop — 12/12 on this exact stack
- Lockfile: typeorm 0.3.26 + new `sql-highlight` dep, `esbuild`/uuid
entries untouched
This commit is contained in:
Charles Bochet
2026-06-11 16:41:22 +02:00
committed by GitHub
parent d75685b8dc
commit 503c689f37
5 changed files with 60 additions and 69 deletions
+1 -2
View File
@@ -44,11 +44,10 @@
"sockjs/uuid": "11.1.1",
"@cypress/request/uuid": "11.1.1",
"@ptc-org/nestjs-query-typeorm/uuid": "11.1.1",
"typeorm/uuid": "11.1.1",
"googleapis-common/uuid": "11.1.1",
"@cyntler/react-doc-viewer/ajv": "8.20.0"
},
"//resolutions": "Each entry is load-bearing: it forces a version OUTSIDE some parent's declared range where no fixed upstream release exists; remove each once its blocker ships. graphql 16.8.1 -> singleton pin held below msw's ^16.12.0 dep and @nestjs/graphql's ^16.11.0 peer; drop after a validated repo-wide bump to latest 16.x; graphql-redis-subscriptions/ioredis 5.10.1 -> TS type-identity dedup: twenty-server passes its ioredis client into RedisPubSub, so this must equal the exact ioredis version pinned by twenty-server and bullmq (bump in lockstep); @types/qs 6.9.16 -> holdback below the 6.9.17 ParsedQs typing break (node-saml wants ^6.9.18); @opentelemetry/api 1.9.1 -> singleton guard for the NoopMeterProvider bug (#20231): ai 6.0.x pins 1.9.0 exact vs @sentry/node ^1.9.1, drop when workspace ai >=6.0.178 AND @scalar/agent-chat moves off ai 6.0.33; chokidar ^3 -> NestJS CLI watch needs fsevents on macOS, removed in chokidar 4/5 (#20316); tmp ^0.2.7 -> CVE, zapier-platform-cli 19 (latest) pins 0.2.5 and inquirer 7/8's external-editor wants ^0.0.33; make-fetch-happen ^15 + @electron/{rebuild,node-gyp}/tar ^7.5.16 -> tar CVE eviction for the @electron/rebuild 3.x toolchain (rebuild 3.x pins tar ^6, its node-gyp fork pins tar ^6.2.1 + mfh ^10), drop when electron-forge declares @electron/rebuild >=4; @angular-devkit/core 19.2.24 -> picomatch CVE, blocked on @nestjs/cli >11.0.23 fixing the dist/src output regression (repo held at 11.0.16); yeoman-environment 6.0.1 -> CVE, zapier-platform-cli 19 (latest) pins 4.4.3; webpack-dev-server 5.2.4 -> CVE, @electron-forge/plugin-webpack (incl. 8.x alphas) still declares ^4; express/qs + @cypress/request/qs 6.15.2 -> qs CVE for old express 4.22.0/4.22.1 pinned by @mintlify/previewing and verdaccio (verdaccio also pins @cypress/request 3.0.10; all other qs parents resolve safe naturally); next/postcss 8.5.15 -> postcss CVE, every stable next pins 8.4.31 exact (fix only in 16.3.0 canaries; @react-email/ui also pins next 16.2.6); <pkg>/uuid 11.1.1 -> uuid CVE for parents pinning uuid <11 with no fixed release (sockjs dormant since 2021; @cypress/request 3.0.10 via verdaccio; @ptc-org/nestjs-query-typeorm at latest; typeorm 0.3.20 patch:dep -- droppable by bumping to 0.3.26+ and re-rolling the patch, but 0.3.26 made workspace metadata sync intermittently lossy (see #21448), debug before retrying; googleapis 105 -> common 8 drops uuid but needs the googleapis >=152 migration). Preserves the intentional uuid 13.x; @cyntler/react-doc-viewer/ajv 8.20.0 -> CVE, upstream (latest 1.17.1) pins ajv ^7 but never imports it, forcing v8 is safe",
"//resolutions": "Each entry is load-bearing: it forces a version OUTSIDE some parent's declared range where no fixed upstream release exists; remove each once its blocker ships. graphql 16.8.1 -> singleton pin held below msw's ^16.12.0 dep and @nestjs/graphql's ^16.11.0 peer; drop after a validated repo-wide bump to latest 16.x; graphql-redis-subscriptions/ioredis 5.10.1 -> TS type-identity dedup: twenty-server passes its ioredis client into RedisPubSub, so this must equal the exact ioredis version pinned by twenty-server and bullmq (bump in lockstep); @types/qs 6.9.16 -> holdback below the 6.9.17 ParsedQs typing break (node-saml wants ^6.9.18); @opentelemetry/api 1.9.1 -> singleton guard for the NoopMeterProvider bug (#20231): ai 6.0.x pins 1.9.0 exact vs @sentry/node ^1.9.1, drop when workspace ai >=6.0.178 AND @scalar/agent-chat moves off ai 6.0.33; chokidar ^3 -> NestJS CLI watch needs fsevents on macOS, removed in chokidar 4/5 (#20316); tmp ^0.2.7 -> CVE, zapier-platform-cli 19 (latest) pins 0.2.5 and inquirer 7/8's external-editor wants ^0.0.33; make-fetch-happen ^15 + @electron/{rebuild,node-gyp}/tar ^7.5.16 -> tar CVE eviction for the @electron/rebuild 3.x toolchain (rebuild 3.x pins tar ^6, its node-gyp fork pins tar ^6.2.1 + mfh ^10), drop when electron-forge declares @electron/rebuild >=4; @angular-devkit/core 19.2.24 -> picomatch CVE, blocked on @nestjs/cli >11.0.23 fixing the dist/src output regression (repo held at 11.0.16); yeoman-environment 6.0.1 -> CVE, zapier-platform-cli 19 (latest) pins 4.4.3; webpack-dev-server 5.2.4 -> CVE, @electron-forge/plugin-webpack (incl. 8.x alphas) still declares ^4; express/qs + @cypress/request/qs 6.15.2 -> qs CVE for old express 4.22.0/4.22.1 pinned by @mintlify/previewing and verdaccio (verdaccio also pins @cypress/request 3.0.10; all other qs parents resolve safe naturally); next/postcss 8.5.15 -> postcss CVE, every stable next pins 8.4.31 exact (fix only in 16.3.0 canaries; @react-email/ui also pins next 16.2.6); <pkg>/uuid 11.1.1 -> uuid CVE for parents pinning uuid <11 with no fixed release (sockjs dormant since 2021; @cypress/request 3.0.10 via verdaccio; @ptc-org/nestjs-query-typeorm at latest; googleapis 105 -> common 8 drops uuid but needs the googleapis >=152 migration). Preserves the intentional uuid 13.x; @cyntler/react-doc-viewer/ajv 8.20.0 -> CVE, upstream (latest 1.17.1) pins ajv ^7 but never imports it, forcing v8 is safe",
"version": "0.2.1",
"nx": {},
"scripts": {
+1 -1
View File
@@ -166,7 +166,7 @@
"tsdav": "^2.2.0",
"tslib": "2.8.1",
"type-fest": "4.10.1",
"typeorm": "patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch",
"typeorm": "patch:typeorm@0.3.26#./patches/typeorm+0.3.26.patch",
"unzipper": "^0.12.3",
"uuid": "^11.1.1",
"zod": "^4.1.11"
@@ -918,7 +918,7 @@ export class WorkspaceRepository<
/**
* DEPRECATED AND RESTRICTED METHODS
*/
override async query(): Promise<unknown> {
override async query<TResult = unknown>(): Promise<TResult> {
throw new PermissionsException(
'Method not allowed.',
PermissionsExceptionCode.RAW_SQL_NOT_ALLOWED,
+57 -65
View File
@@ -31760,7 +31760,7 @@ __metadata:
languageName: node
linkType: hard
"dayjs@npm:^1.10.0, dayjs@npm:^1.11.9":
"dayjs@npm:^1.10.0, dayjs@npm:^1.11.13":
version: 1.11.21
resolution: "dayjs@npm:1.11.21"
checksum: 10c0/bd97dfdc4bfea3c66268635690313828b386faa040fbc1f829ff42a2bd748b72c9d9b3c8f9616ce9e61fcb78923f1461a462c969c54b1084458ae1b715898fb0
@@ -32592,7 +32592,7 @@ __metadata:
languageName: node
linkType: hard
"dotenv@npm:^16.0.3, dotenv@npm:^16.3.0, dotenv@npm:^16.4.5, dotenv@npm:^16.5.0":
"dotenv@npm:^16.3.0, dotenv@npm:^16.4.5, dotenv@npm:^16.4.7, dotenv@npm:^16.5.0":
version: 16.6.1
resolution: "dotenv@npm:16.6.1"
checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc
@@ -36197,7 +36197,7 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^10.0.0, glob@npm:^10.3.10, glob@npm:^10.3.7":
"glob@npm:^10.0.0, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.5":
version: 10.5.0
resolution: "glob@npm:10.5.0"
dependencies:
@@ -43879,15 +43879,6 @@ __metadata:
languageName: node
linkType: hard
"mkdirp@npm:^2.1.3":
version: 2.1.6
resolution: "mkdirp@npm:2.1.6"
bin:
mkdirp: dist/cjs/src/bin.js
checksum: 10c0/96f551c651dd8f5f9435d53df1a7b9bfc553be769ee6da5192c37c1f303a376ef1c6996f96913d4a8d357060451d4526a346031d1919f92c58806a5fa3cd8dfe
languageName: node
linkType: hard
"mlly@npm:^1.7.4":
version: 1.8.0
resolution: "mlly@npm:1.8.0"
@@ -49578,7 +49569,7 @@ __metadata:
languageName: node
linkType: hard
"reflect-metadata@npm:0.2.2, reflect-metadata@npm:^0.2.1, reflect-metadata@npm:^0.2.2":
"reflect-metadata@npm:0.2.2, reflect-metadata@npm:^0.2.2":
version: 0.2.2
resolution: "reflect-metadata@npm:0.2.2"
checksum: 10c0/1cd93a15ea291e420204955544637c264c216e7aac527470e393d54b4bb075f10a17e60d8168ec96600c7e0b9fcc0cb0bb6e91c3fbf5b0d8c9056f04e6ac1ec2
@@ -52500,6 +52491,13 @@ __metadata:
languageName: node
linkType: hard
"sql-highlight@npm:^6.0.0":
version: 6.1.0
resolution: "sql-highlight@npm:6.1.0"
checksum: 10c0/9614f4608bfde8ea7bf9b2fe9233dcc99a619c91cbc3f5cd85a6fb5ad4b2177f4ac8ca4a0191f4243ff8aea3b6f2a1229efc88635298269e0049b2ac08bde263
languageName: node
linkType: hard
"sshpk@npm:^1.18.0":
version: 1.18.0
resolution: "sshpk@npm:1.18.0"
@@ -55245,7 +55243,7 @@ __metadata:
twenty-emails: "workspace:*"
twenty-shared: "workspace:*"
type-fest: "npm:4.10.1"
typeorm: "patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch"
typeorm: "patch:typeorm@0.3.26#./patches/typeorm+0.3.26.patch"
unzipper: "npm:^0.12.3"
uuid: "npm:^11.1.1"
zod: "npm:^4.1.11"
@@ -55714,43 +55712,42 @@ __metadata:
languageName: node
linkType: hard
"typeorm@npm:0.3.20":
version: 0.3.20
resolution: "typeorm@npm:0.3.20"
"typeorm@npm:0.3.26":
version: 0.3.26
resolution: "typeorm@npm:0.3.26"
dependencies:
"@sqltools/formatter": "npm:^1.2.5"
ansis: "npm:^3.17.0"
app-root-path: "npm:^3.1.0"
buffer: "npm:^6.0.3"
chalk: "npm:^4.1.2"
cli-highlight: "npm:^2.1.11"
dayjs: "npm:^1.11.9"
debug: "npm:^4.3.4"
dotenv: "npm:^16.0.3"
glob: "npm:^10.3.10"
mkdirp: "npm:^2.1.3"
reflect-metadata: "npm:^0.2.1"
dayjs: "npm:^1.11.13"
debug: "npm:^4.4.0"
dedent: "npm:^1.6.0"
dotenv: "npm:^16.4.7"
glob: "npm:^10.4.5"
sha.js: "npm:^2.4.11"
tslib: "npm:^2.5.0"
uuid: "npm:^9.0.0"
yargs: "npm:^17.6.2"
sql-highlight: "npm:^6.0.0"
tslib: "npm:^2.8.1"
uuid: "npm:^11.1.0"
yargs: "npm:^17.7.2"
peerDependencies:
"@google-cloud/spanner": ^5.18.0
"@sap/hana-client": ^2.12.25
better-sqlite3: ^7.1.2 || ^8.0.0 || ^9.0.0
hdb-pool: ^0.1.6
"@google-cloud/spanner": ^5.18.0 || ^6.0.0 || ^7.0.0
"@sap/hana-client": ^2.14.22
better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0
ioredis: ^5.0.4
mongodb: ^5.8.0
mssql: ^9.1.1 || ^10.0.1
mongodb: ^5.8.0 || ^6.0.0
mssql: ^9.1.1 || ^10.0.1 || ^11.0.1
mysql2: ^2.2.5 || ^3.0.1
oracledb: ^6.3.0
pg: ^8.5.1
pg-native: ^3.0.0
pg-query-stream: ^4.0.0
redis: ^3.1.1 || ^4.0.0
redis: ^3.1.1 || ^4.0.0 || ^5.0.14
reflect-metadata: ^0.1.14 || ^0.2.0
sql.js: ^1.4.0
sqlite3: ^5.0.3
ts-node: ^10.7.0
typeorm-aurora-data-api-driver: ^2.0.0
typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0
peerDependenciesMeta:
"@google-cloud/spanner":
optional: true
@@ -55758,8 +55755,6 @@ __metadata:
optional: true
better-sqlite3:
optional: true
hdb-pool:
optional: true
ioredis:
optional: true
mongodb:
@@ -55790,47 +55785,46 @@ __metadata:
typeorm: cli.js
typeorm-ts-node-commonjs: cli-ts-node-commonjs.js
typeorm-ts-node-esm: cli-ts-node-esm.js
checksum: 10c0/7e4be724641beef86ae36289c87b6e66bfaf19a4313f089926d36d2d6f0d67f9314d942711c9d83ab8a174b8622148c2f7e83e6c1448d638ee3ab24469257814
checksum: 10c0/a09ed0609a5799718bc837450b84593f6a64e6698e0662162eee2f7f12591ad9cb6f9173b42ae1d16f784d1ea09712c6e886481f4a3bdc7f851402cfde54e65a
languageName: node
linkType: hard
"typeorm@patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch::locator=twenty-server%40workspace%3Apackages%2Ftwenty-server":
version: 0.3.20
resolution: "typeorm@patch:typeorm@npm%3A0.3.20#./patches/typeorm+0.3.20.patch::version=0.3.20&hash=e61204&locator=twenty-server%40workspace%3Apackages%2Ftwenty-server"
"typeorm@patch:typeorm@0.3.26#./patches/typeorm+0.3.26.patch::locator=twenty-server%40workspace%3Apackages%2Ftwenty-server":
version: 0.3.26
resolution: "typeorm@patch:typeorm@npm%3A0.3.26#./patches/typeorm+0.3.26.patch::version=0.3.26&hash=e61204&locator=twenty-server%40workspace%3Apackages%2Ftwenty-server"
dependencies:
"@sqltools/formatter": "npm:^1.2.5"
ansis: "npm:^3.17.0"
app-root-path: "npm:^3.1.0"
buffer: "npm:^6.0.3"
chalk: "npm:^4.1.2"
cli-highlight: "npm:^2.1.11"
dayjs: "npm:^1.11.9"
debug: "npm:^4.3.4"
dotenv: "npm:^16.0.3"
glob: "npm:^10.3.10"
mkdirp: "npm:^2.1.3"
reflect-metadata: "npm:^0.2.1"
dayjs: "npm:^1.11.13"
debug: "npm:^4.4.0"
dedent: "npm:^1.6.0"
dotenv: "npm:^16.4.7"
glob: "npm:^10.4.5"
sha.js: "npm:^2.4.11"
tslib: "npm:^2.5.0"
uuid: "npm:^9.0.0"
yargs: "npm:^17.6.2"
sql-highlight: "npm:^6.0.0"
tslib: "npm:^2.8.1"
uuid: "npm:^11.1.0"
yargs: "npm:^17.7.2"
peerDependencies:
"@google-cloud/spanner": ^5.18.0
"@sap/hana-client": ^2.12.25
better-sqlite3: ^7.1.2 || ^8.0.0 || ^9.0.0
hdb-pool: ^0.1.6
"@google-cloud/spanner": ^5.18.0 || ^6.0.0 || ^7.0.0
"@sap/hana-client": ^2.14.22
better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0
ioredis: ^5.0.4
mongodb: ^5.8.0
mssql: ^9.1.1 || ^10.0.1
mongodb: ^5.8.0 || ^6.0.0
mssql: ^9.1.1 || ^10.0.1 || ^11.0.1
mysql2: ^2.2.5 || ^3.0.1
oracledb: ^6.3.0
pg: ^8.5.1
pg-native: ^3.0.0
pg-query-stream: ^4.0.0
redis: ^3.1.1 || ^4.0.0
redis: ^3.1.1 || ^4.0.0 || ^5.0.14
reflect-metadata: ^0.1.14 || ^0.2.0
sql.js: ^1.4.0
sqlite3: ^5.0.3
ts-node: ^10.7.0
typeorm-aurora-data-api-driver: ^2.0.0
typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0
peerDependenciesMeta:
"@google-cloud/spanner":
optional: true
@@ -55838,8 +55832,6 @@ __metadata:
optional: true
better-sqlite3:
optional: true
hdb-pool:
optional: true
ioredis:
optional: true
mongodb:
@@ -55870,7 +55862,7 @@ __metadata:
typeorm: cli.js
typeorm-ts-node-commonjs: cli-ts-node-commonjs.js
typeorm-ts-node-esm: cli-ts-node-esm.js
checksum: 10c0/2593c3ddf6b243c6be13e3d9deebad1e9ecfcbdda8bb518b7fb633077e2e20b99c1873cdc0848df8ba44c9bfb22ecfe345937aa82702a9c857ecd73b32de8373
checksum: 10c0/66268d90c1019c0b1c39971985d0f3f673f0236ce9a21835c7de16d5db54e3dcee39cbfad1985b0e0feb603b4f440fb25c6c6222c55a17452ac1b746d3a9e0f5
languageName: node
linkType: hard
@@ -58568,7 +58560,7 @@ __metadata:
languageName: node
linkType: hard
"yargs@npm:17.7.2, yargs@npm:^17.0.0, yargs@npm:^17.0.1, yargs@npm:^17.3.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2":
"yargs@npm:17.7.2, yargs@npm:^17.0.0, yargs@npm:^17.0.1, yargs@npm:^17.3.1, yargs@npm:^17.5.1, yargs@npm:^17.7.2":
version: 17.7.2
resolution: "yargs@npm:17.7.2"
dependencies: