Skip to main content

Twenty Case Study

Verified baseline scan — CVE Lite CLI v1.20.0 · 2026-06-09

Twenty logo

Summary

  • Project: Twenty — open-source CRM alternative (48k+ GitHub stars) built as a TypeScript Nx + Yarn Berry monorepo with NestJS backend, React frontend, and PostgreSQL
  • Revision: fc90b4ba8bb0a5d7c12c846fe9b2305527a0f7a8
  • Lockfile: yarn.lock (5,451 resolved packages, Yarn Berry 4.13.0)
  • Baseline findings: 105 unique vulnerable packages (6 critical · 40 high · 54 medium · 5 low)
  • OSV advisory matches: 167 CVE/advisory entries deduplicated into 105 packages
  • Direct vs transitive: 0 direct / 28 transitive / 77 unknown (Yarn Berry path classification limited in this MVP)
  • Validated fix command groups generated: 4
  • First-pass coverage: 24 of 105 findings have confident copy-and-run commands
  • yarn npm audit (same lockfile): no audit suggestions (lockfile-only snapshot — catalog/workspace protocols require full monorepo install)
  • Remediation applied in this study: none — baseline scan and generated fix plan only

What this case study demonstrates

Twenty is the largest lockfile snapshot in the CVE Lite CLI case study portfolio at 5,451 resolved packages — exceeding Mastra (4,555) and Ghost (4,447). It adds open-source CRM / business-application coverage alongside AI SDK monorepos and CMS platforms.

The direct/transitive split (0 direct, 28 transitive, 77 unknown) is the defining pattern. Twenty's root package.json is a private Nx workspace manifest — devDependencies only (@nx/jest, @nx/js, nx, verdaccio). Every vulnerable package in the scan lives in the resolved lockfile graph, not in anything a CRM deployer would recognize from the root manifest alone.

All six critical findings are unknown relationship — Yarn Berry path reconstruction could not classify them as direct or transitive in this lockfile-only MVP. That is operationally significant: on a graph this size, 73% of findings (77/105) fall into the unknown bucket, meaning triage requires maintainer path inspection rather than copy-and-run commands.

The six critical packages cluster around test environments and legacy HTTP helpers:

@nyariv/[email protected] — critical sandbox escape / RCE. JavaScript sandbox used in tooling chains — validated target 0.9.6 but no auto-generated parent upgrade.

[email protected] and [email protected] / @vitest/[email protected] — critical test-stack RCE. Vitest 4.x browser mode and happy-dom VM escape advisories — deeply embedded in the Nx test toolchain, not in Twenty's production CRM runtime.

[email protected] and [email protected] — critical unsafe random boundary generation. Two majors of the same package in different toolchain chains — deduplicated as two rows in CVE Lite's package view.

High-severity Nx orchestration surface: @nx/js, @nx/react, @nx/jest, and root nx appear in generated fix commands — partial parent upgrades with path-specific coverage notes (e.g. @nx/[email protected] covers one of five paths to picomatch).

Four command groups, 24/105 first-pass coverage — a higher absolute coverage count than Storybook (1/92) but a lower percentage (23% vs 1%). The generated plan mixes Nx parent bumps, within-range refreshes (axios, lodash, minimatch/picomatch/brace-expansion chains), and [email protected] for a nested lodash path.


Comparison Note: CVE Lite CLI vs yarn npm audit

Both tools were attempted against the same yarn.lock on the same machine on 2026-06-09.

Metricyarn npm audit (4.13.0)CVE Lite CLI v1.20.0
Total reported findings0 (no audit suggestions)105
Critical6
High40
Moderate / Medium54
Low5
Direct vs transitive breakdown✓ (0 / 28 / 77 unknown)
Full lockfile package parse✗ (requires install)✓ (5,451 packages)
Deduplicated package view
Specific copy-and-run commands✓ (4 groups)
Skipped findings with reason✓ (81 entries)

Why yarn npm audit reports nothing on this fixture:

Running yarn npm audit and yarn npm audit -A on this lockfile-only snapshot returns No audit suggestions. Twenty uses Yarn Berry catalog and workspace protocols across dozens of packages (twenty-front, twenty-server, twenty-ui, etc.) that require a full monorepo install context — not present in a committed package.json + yarn.lock snapshot.

This is the same class of limitation documented in the Storybook case study. CVE Lite's value here is parsing the entire 5,451-package lockfile without installing the monorepo — surfacing 105 vulnerable packages including six critical findings that native audit cannot see on this fixture.

Why CVE Lite counts matter for triage:

A flat audit row count (when audit works at all) multiplies advisory × path entries. CVE Lite's 105 is the deduplicated package surface: 105 distinct package versions needing a decision, not hundreds of repeated path rows.


Before vs After

No remediation pass was performed for this study. This table records the verified baseline scan only.

StageFindingsCriticalHighMediumLowDirectTransitiveUnknownCommand groups
Baseline (verified)105640545028774

The first-pass plan covers 24 of 105 findings across four command groups. The remaining 81 appear in the skipped section — overwhelmingly unknown-relationship packages where Yarn Berry path reconstruction is limited, or transitive packages awaiting Nx/plugin parent releases.


Fix Journey

These commands were generated by the scanner but not run against the upstream Twenty repository.

On a 5,451-package Nx monorepo, the instinct is to run yarn npm audit fix. That path fails silently on this fixture (No audit suggestions). CVE Lite generates four grouped command sets instead:

Step 1 — Nx parent upgrades (high, partial coverage):

Covers one path each to picomatch, koa (via module federation), and lodash (via verdaccio/local-storage). Rescan required — multiple remaining paths noted in output.

Step 2 — within-range high refreshes:

yarn upgrade @babel/plugin-transform-modules-systemjs && yarn upgrade axios && yarn upgrade fast-uri && yarn upgrade lodash && yarn upgrade path-to-regexp && yarn upgrade picomatch && yarn upgrade tmp

Step 3 — within-range medium refreshes:

yarn upgrade ajv && yarn upgrade follow-redirects && yarn upgrade ip-address && yarn upgrade postcss && yarn upgrade qs && yarn upgrade yaml

Step 4 — Nx/jest parent upgrades (medium, partial):

Partial coverage on brace-expansion chains through @nx/jest and nx → ejs → jake → minimatch.

Not auto-fixable without maintainer triage:

  • 6 critical: @nyariv/sandboxjs, happy-dom, vitest, @vitest/browser, two form-data versions — all unknown relationship
  • Multiple minimatch majors (6 versions) — mixed fixability across Nx, webpack, and tooling chains
  • simplemde and vue-template-compiler — medium, no fix available

Running all four generated command groups should address 24 of 105 findings. The six critical findings require upstream Nx/Vitest/test-environment decisions, not direct installs of transitive packages.


Why this matters

Twenty is one of the fastest-growing open-source CRM projects — a full-stack TypeScript monorepo that teams deploy as production business infrastructure. A developer running default yarn npm audit on this lockfile-only snapshot sees nothing. A lockfile scan reveals 105 vulnerable packages, including six critical findings in test sandboxes and legacy HTTP helpers.

That gap matters for any team using Twenty as a reference for dependency hygiene. The risk is not only in packages Twenty ships to CRM users — it is in the Nx orchestration layer, Vitest browser testing stack, module federation tooling, and Electron companion builds locked in yarn.lock.

CVE Lite answers the useful pre-release question in one pass: four copy-and-run command groups for what the toolchain permits today, six critical findings routed to test/sandbox chains, and 81 skipped entries explaining why the rest are not auto-fixable.


Scan command

Run from the Twenty repository root or from the examples/twenty directory in this repository:

cve-lite . --verbose --all

The example lockfile reflects Twenty at revision fc90b4ba8bb0a5d7c12c846fe9b2305527a0f7a8. Twenty releases frequently — and OSV advisory data changes over time — so re-scanning may show a different finding count even on the same lockfile revision.


Scan verification

Every number in this case study comes from a live scan of the committed fixture at examples/twenty/ in the CVE Lite CLI repository.

FieldValue
Scan date2026-06-09
CLI versionv1.20.0
CVE Lite commandnode dist/index.js examples/twenty --verbose --all --json
yarn audit commandyarn npm audit / yarn npm audit -A (Yarn 4.13.0 — no audit suggestions on lockfile-only snapshot)
Advisory sourceOSV (https://api.osv.dev) — online mode
Lockfile sourceexamples/twenty/yarn.lock from twentyhq/twenty@fc90b4b
Packages parsed (CVE Lite)5,451
Unique vulnerable packages (CVE Lite)105
OSV advisory matches (CVE Lite)167
Fix command groups (CVE Lite)4
First-pass coverage (CVE Lite)24 / 105 findings
Skipped findings with reason (CVE Lite)81

Reproduce CVE Lite locally from the repository root:

npm install && npm run build
node dist/index.js examples/twenty --verbose --all

Remaining risk

All 105 baseline findings remain open at the time of this study. No remediation was applied.

  • 6 critical: @nyariv/sandboxjs, @vitest/browser, vitest, happy-dom, two form-data versions
  • 40 high: including six minimatch versions, two next versions, OpenTelemetry exporters, electron, typeorm, axios, lodash, path-to-regexp, ws, and Nx toolchain packages
  • 54 medium: including @nestjs/core, multiple ajv/postcss/qs/uuid/ws versions, dompurify, file-type, webpack-dev-server
  • 5 low: @tootallnate/once (2 versions), two diff versions, elliptic (no fix)

Baseline findings

Full vulnerable package list from the verified scan on 2026-06-09 (revision fc90b4b):

PackageVersionSeverityRelationshipFix hintAdvisory IDs
@nyariv/sandboxjs0.8.25criticalunknown0.9.6 ⊘GHSA-2gg9-6p7w-6cpj, GHSA-58jh-xv4v-pcx4
@vitest/browser4.0.18criticalunknown4.1.6 ⊘GHSA-2h32-95rg-cppp
form-data2.3.3criticalunknown2.5.4 ⊘GHSA-fjxv-7rqg-78g4
form-data4.0.0criticalunknown4.0.4 ⊘GHSA-fjxv-7rqg-78g4
happy-dom15.11.7criticalunknown20.8.9 ⊘GHSA-37j7-fg3j-429f, GHSA-6q6h-j7hj-3r64
vitest4.0.18criticalunknown4.1.0 ⊘GHSA-5xrq-8626-4rwp
@babel/plugin-transform-modules-systemjs7.25.9hightransitive7.29.4GHSA-fv7c-fp4j-7gwp
@opentelemetry/auto-instrumentations-node0.60.1highunknown0.75.0 ⊘GHSA-q7rr-3cgh-j5r3
@opentelemetry/exporter-prometheus0.202.0highunknown0.217.0 ⊘GHSA-q7rr-3cgh-j5r3
@opentelemetry/exporter-prometheus0.211.0highunknown0.217.0 ⊘GHSA-q7rr-3cgh-j5r3
@opentelemetry/sdk-node0.202.0highunknown0.217.0 ⊘GHSA-q7rr-3cgh-j5r3
axios1.13.6highunknown1.16.0 ⊘GHSA-35jp-ww65-95wh, GHSA-3g43-6gmg-66jw
axios1.13.5hightransitive1.16.0GHSA-35jp-ww65-95wh, GHSA-3g43-6gmg-66jw
defu6.1.4highunknown6.1.5 ⊘GHSA-737v-mqg7-c878
electron36.0.1highunknown39.8.5 ⊘GHSA-3c8v-cfp5-9885, GHSA-4p4r-m79c-wq3v
fast-uri3.0.1hightransitive3.1.2GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc
fast-xml-builder1.0.0highunknown1.1.7 ⊘GHSA-5wm8-gmm8-39j9
fast-xml-parser5.4.1highunknown5.7.0 ⊘GHSA-8gc5-j5rx-235r, GHSA-gh4j-gqv2-49f6
immutable3.7.6highunknown3.8.3 ⊘GHSA-wf6x-7x77-mvgw
koa3.0.3hightransitive3.1.2GHSA-7gcc-r8m5-44qm
lodash4.17.23hightransitive4.18.0GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc
lodash4.17.21hightransitive4.18.0GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc
minimatch9.0.3highunknown9.0.7 ⊘GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26
minimatch10.0.3highunknown10.2.3 ⊘GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26
minimatch3.1.2highunknown3.1.4 ⊘GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26
minimatch4.2.3highunknown4.2.5 ⊘GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26
minimatch7.4.6hightransitive7.4.8GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26
minimatch3.0.8highunknown3.1.4 ⊘GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26
next16.0.10highunknown16.2.6 ⊘GHSA-267c-6grr-h53f, GHSA-26hh-7cqf-hhc6
next16.1.7highunknown16.2.6 ⊘GHSA-267c-6grr-h53f, GHSA-26hh-7cqf-hhc6
node-forge1.3.2highunknown1.4.0 ⊘GHSA-2328-f5f3-gj25, GHSA-5m6q-g25r-mvwx
path-to-regexp8.3.0highunknown8.4.0 ⊘GHSA-27v5-c462-wpq7, GHSA-j3q9-mxjg-w52f
path-to-regexp0.1.12hightransitive0.1.13GHSA-37ch-88jc-xwx2
picomatch4.0.2hightransitive4.0.4GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj
picomatch2.3.1hightransitive2.3.2GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj
picomatch4.0.3hightransitive4.0.4GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj
serialize-javascript6.0.2hightransitive7.0.5GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v
tar6.2.1hightransitive7.5.11GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28cx
tmp0.2.1highunknown0.2.6 ⊘GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65
tmp0.0.33highunknown0.2.6 ⊘GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65
tmp0.2.5hightransitive0.2.6GHSA-ph9p-34f9-6g65
typeorm0.3.20highunknown0.3.26 ⊘GHSA-q2pj-6v73-8rgj
undici5.29.0highunknown6.24.0 ⊘GHSA-2mjp-6q6p-2qxm, GHSA-4992-7rv2-5pvq
ws8.13.0highunknown8.20.1 ⊘GHSA-3h5v-q93c-6h6q, GHSA-58qx-3vcg-4xpx
ws8.16.0highunknown8.20.1 ⊘GHSA-3h5v-q93c-6h6q, GHSA-58qx-3vcg-4xpx
yeoman-environment3.3.0highunknown6.0.1 ⊘GHSA-vv9j-gjw2-j8wp
@nestjs/core11.1.16mediumunknown11.1.18 ⊘GHSA-36xv-jgw5-4q75
@octokit/plugin-paginate-rest2.21.3mediumunknown9.2.2 ⊘GHSA-h5c3-5r3r-rr8q
@octokit/request5.6.3mediumunknown8.4.1 ⊘GHSA-rmvr-2pp2-xj38
@octokit/request-error2.1.0mediumunknown5.1.1 ⊘GHSA-xx4v-prfh-6cgc
@protobufjs/utf81.1.0mediumunknown1.1.1 ⊘GHSA-q6x5-8v7m-xcrf
ajv8.13.0mediumunknown8.18.0 ⊘GHSA-2g4f-4pwh-qvx6
ajv8.17.1mediumtransitive8.18.0GHSA-2g4f-4pwh-qvx6
ajv6.12.6mediumunknown6.14.0 ⊘GHSA-2g4f-4pwh-qvx6
ajv7.2.4mediumunknown8.18.0 ⊘GHSA-2g4f-4pwh-qvx6
ajv8.12.0mediumunknown8.18.0 ⊘GHSA-2g4f-4pwh-qvx6
apollo-server-core3.13.0mediumunknown5.5.0 ⊘GHSA-9q82-xgwf-vj6h
bn.js4.12.0mediumunknown4.12.3 ⊘GHSA-378v-28hj-76wf
bn.js5.2.1mediumunknown5.2.3 ⊘GHSA-378v-28hj-76wf
brace-expansion5.0.5mediumunknown5.0.6 ⊘GHSA-jxxr-4gwj-5jf2
brace-expansion1.1.12mediumtransitive1.1.13GHSA-f886-m6hf-6m8v
brace-expansion2.0.2mediumtransitive2.0.3GHSA-f886-m6hf-6m8v
brace-expansion5.0.3mediumtransitive5.0.6GHSA-f886-m6hf-6m8v, GHSA-jxxr-4gwj-5jf2
dompurify3.3.3mediumunknown3.4.0 ⊘GHSA-39q2-94rc-95cp, GHSA-crv5-9vww-q3g8
esbuild0.21.5mediumunknown0.25.0 ⊘GHSA-67mh-4wv8-2f99
file-type20.5.0mediumunknown21.3.2 ⊘GHSA-5v7r-6r5c-r473, GHSA-j47w-4g3g-c36v
file-type21.3.0mediumunknown21.3.2 ⊘GHSA-5v7r-6r5c-r473, GHSA-j47w-4g3g-c36v
file-type21.3.1mediumunknown21.3.2 ⊘GHSA-j47w-4g3g-c36v
follow-redirects1.15.6mediumtransitive1.16.0GHSA-r4q5-vmmm-2653
follow-redirects1.15.11mediumtransitive1.16.0GHSA-r4q5-vmmm-2653
got9.6.0mediumunknown11.8.5 ⊘GHSA-pfrx-2q88-qq97
ip-address10.0.1mediumunknown10.1.1 ⊘GHSA-v2v4-37r5-5v8g
ip-address9.0.5mediumtransitive10.1.1GHSA-v2v4-37r5-5v8g
nodemailer8.0.4mediumunknown8.0.5 ⊘GHSA-vvjj-xcjg-gr5g
postcss8.4.31mediumunknown8.5.10 ⊘GHSA-qx2v-qp2m-jg93
postcss8.4.49mediumunknown8.5.10 ⊘GHSA-qx2v-qp2m-jg93
postcss8.5.8mediumunknown8.5.10 ⊘GHSA-qx2v-qp2m-jg93
postcss8.5.6mediumtransitive8.5.10GHSA-qx2v-qp2m-jg93
qs6.15.0mediumtransitive6.15.2GHSA-q8mj-m7cp-5q26
qs6.14.2mediumtransitive6.15.2GHSA-q8mj-m7cp-5q26
qs6.5.5mediumunknown6.14.1 ⊘GHSA-6rw7-vpxm-498p
react-router6.30.3mediumunknown6.30.4 ⊘GHSA-2j2x-hqr9-3h42
request2.88.2mediumunknown3.0.0 ⊘GHSA-p8p7-x288-28g6
simplemde1.11.2mediumunknown⚠ no fixGHSA-wg85-p6j7-gp3w
tough-cookie2.5.0mediumunknown4.1.3 ⊘GHSA-72xf-g2v4-qvf3
unhead1.11.20mediumunknown2.1.13 ⊘GHSA-5339-hvwr-7582, GHSA-95h2-gj7x-gx9w
uuid9.0.1mediumunknown11.1.1 ⊘GHSA-w5hq-g745-h8pq
uuid3.4.0mediumunknown11.1.1 ⊘GHSA-w5hq-g745-h8pq
uuid8.3.2mediumtransitive11.1.1GHSA-w5hq-g745-h8pq
uuid10.0.0mediumunknown11.1.1 ⊘GHSA-w5hq-g745-h8pq
uuid11.1.0mediumunknown11.1.1 ⊘GHSA-w5hq-g745-h8pq
uuid13.0.0mediumunknown13.0.1 ⊘GHSA-w5hq-g745-h8pq
vue-template-compiler2.7.16mediumunknown⚠ no fixGHSA-g3ch-rx76-35fx
webpack-dev-server4.15.2mediumunknown5.2.4 ⊘GHSA-4v9v-hfq4-rm2v, GHSA-79cf-xcqc-c78w
ws8.17.1mediumunknown8.20.1 ⊘GHSA-58qx-3vcg-4xpx
ws8.18.0mediumtransitive8.20.1GHSA-58qx-3vcg-4xpx
ws8.18.2mediumunknown8.20.1 ⊘GHSA-58qx-3vcg-4xpx
ws8.19.0mediumunknown8.20.1 ⊘GHSA-58qx-3vcg-4xpx
yaml1.10.2mediumtransitive1.10.3GHSA-48c2-rrv3-qjmp
yaml2.8.1mediumtransitive2.8.3GHSA-48c2-rrv3-qjmp
@tootallnate/once1.1.2lowunknown2.0.1 ⊘GHSA-vpq2-c234-7xj6
@tootallnate/once2.0.0lowunknown2.0.1 ⊘GHSA-vpq2-c234-7xj6
diff4.0.2lowunknown4.0.4 ⊘GHSA-73rr-hh4g-fpgx
diff5.2.0lowunknown5.2.2 ⊘GHSA-73rr-hh4g-fpgx
elliptic6.6.1lowunknown⚠ no fixGHSA-848j-6mx2-7j84

Want your project reviewed?

If you maintain an interesting JavaScript or TypeScript project and want CVE Lite CLI considered for a public case study, open an issue in the CVE Lite CLI repository.

Please include:

  • the repository link
  • why the project would make a useful case study
  • whether the dependency graph is publicly reproducible

Not every project will be selected. Preference will go to projects that are publicly useful, technically interesting, and strong examples of realistic dependency remediation workflows.