## What
The standalone apps under `packages/twenty-apps/*` each ship **their own
`yarn.lock`** (they're not part of the root workspace). Three of them
still pinned the vulnerable transitive `vite@7.3.1`:
- `examples/hello-world`
- `examples/postcard`
- `internal/call-recording`
`vite <= 7.3.1` is affected by three advisories, all first patched in
**7.3.2**:
| Advisory | Summary | Open Dependabot alerts |
|----------|---------|------------------------|
|
[GHSA-v2wj-q39q-566r](https://github.com/advisories/GHSA-v2wj-q39q-566r)
(CVE-2026-39364) | `server.fs.deny` bypassed with queries | #894, #892,
#891 |
|
[GHSA-4w7w-66w2-5vf9](https://github.com/advisories/GHSA-4w7w-66w2-5vf9)
| Path traversal in optimized-deps `.map` handling | #901, #899, #898 |
|
[GHSA-p9ff-h696-f583](https://github.com/advisories/GHSA-p9ff-h696-f583)
| Arbitrary file read via dev-server WebSocket | #908, #906, #905 |
The root `yarn.lock` was already remediated separately (vite 7.3.2 /
8.0.16); these three sub-package lockfiles were the only ones still
flagged open.
## How
Ran `yarn up -R vite` per app to re-resolve vite within the existing
range; it lands on **7.3.5**.
## Scope
- **Lockfile-only**, 3 apps. No `package.json` changes.
- Each lockfile diff is 3 lines (version / resolution / checksum).
- Verified no vite resolution below the patched thresholds remains
anywhere in the repo.
## Context
The view **Hide empty groups** setting (`shouldHideEmptyGroups`) can be
toggled in the UI, is persisted on the `View` entity, exposed in the
`CreateView`/`UpdateView` GraphQL inputs, and tracked by the flat-view
sync machinery — but it could **not** be set from an app's view
manifest.
Root cause: the field postdates the manifest plumbing (added in #16385,
Dec 2025). Two spots were never updated to thread it through:
- `ViewManifest` didn't declare the field.
- `fromViewManifestToUniversalFlatView` hardcoded
`shouldHideEmptyGroups: false`.
Ref: twentyhq/core-team-issues#414
## Changes
- Add optional `shouldHideEmptyGroups?: boolean` to `ViewManifest`.
- Read it in the converter (`?? false`), mirroring the existing
`isCompact` handling.
- Cover it in the converter unit test (default + explicit value).
No migration or schema change — the column already exists, and
downstream sync (`FLAT_VIEW_EDITABLE_PROPERTIES` + the universal-flat
compare type) already handles it.
## Test
- `npx jest from-view-manifest-to-universal-flat-view` → 5 passed
- `tsgo -p tsconfig.json` (twenty-server) → no new errors
- oxlint + oxfmt clean
## What
Clears two **High** Dependabot alerts
(https://github.com/twentyhq/twenty/security/dependabot) from the root
tree **without resolutions/overrides** — by refreshing the lockfile so
the existing semver ranges pick up the already-patched releases.
| Package | From → To | Requested by | Advisory |
|---|---|---|---|
| path-to-regexp | 8.3.0 → 8.4.2 | `router` (`^8.0.0`) |
GHSA-j3q9-mxjg-w52f |
| defu | 6.1.4 → 6.1.7 | `radix-vue` (`^6.1.4`) | GHSA-737v-mqg7-c878 |
Only `yarn.lock` changes — no `package.json` edits, no `resolutions`.
## Why only these two
I traced every vulnerable transitive back to its parent. Only `defu` and
`path-to-regexp` were stuck purely on a stale lockfile (their parents'
ranges already allow the patched version). The remaining root High
alerts can **not** be fixed by a parent update:
- **next** — latest `@react-email/preview-server` (5.2.10) still ships
`next@16.1.7`, itself vulnerable
- **immutable** — `@ardatan/relay-compiler@12.0.0` is terminal and pins
`~3.7.6`
- **minimatch / lodash / ws** — exact-pinned deep in dev tooling
(api-extractor, spectral, NestJS, graphql-tools) with no fixed upstream
release
Those will be handled separately.
## Verification
- `nx typecheck` passes for twenty-server and twenty-front
## Summary
`isValidReturnToPath` validates the post-login `returnTo` path and
already rejects protocol-relative `//` paths — but not the backslash
variant. Browsers normalize `\` to `/`, so `/\evil.com` resolves like
`//evil.com` (a protocol-relative, external URL) while still passing the
existing `//` check:
```ts
isValidReturnToPath("/\\evil.com"); // returns true today; should be false
```
This hardens the open-redirect guard by rejecting any path containing a
backslash, so a `returnTo` can only ever be a same-site absolute path.
## Changes
- `isValidReturnToPath`: reject paths containing `\`.
- Added tests for backslash-tricked paths.
Framed as defense-in-depth — the validator should reject this class
regardless of how each consumer performs the redirect.
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Context
`isCustom` was a legacy denormalized boolean on `ObjectMetadataEntity`
and `FieldMetadataEntity`.
Now that every metadata row carries `applicationId` (via
`SyncableEntity`), "is this custom" is fully derivable, and the stored
boolean was a redundant second source of truth that could drift.
The real meaning of `isCustom` is **"the owning application is not the
twenty-standard application"** — i.e. `!belongsToTwentyStandardApp`.
Note this is *not* "belongs to the workspace custom app" as I initially
thought: third-party-application
objects/fields are custom too.
The standard application has a globally stable `universalIdentifier`, so
the value derives with no per-workspace lookup.
## Changed
## `isCustom` checks — before → after
`isCustom` is no longer a stored column. The table below lists every
site that branched on it and how it resolves now. The unifying rule:
`isCustom ≡
!isTwentyStandardApplicationUniversalIdentifier(applicationUniversalIdentifier)`.
### Server — behavioural checks
| Location | Purpose | Before | Now |
|---|---|---|---|
| `utils/compute-object-target-table.util.ts` | Physical table name `_`
prefix | `computeTableName(nameSingular, objectMetadata.isCustom)` |
derives from `applicationUniversalIdentifier` (single source for all
table-name callers) |
| `twenty-orm/factories/entity-schema.factory.ts` +
`…/entity-schema-metadata.type.ts` | ORM table name (hot path) |
`object.isCustom` | `object.applicationId !== standardApplicationId`
(computed in `buildEntitySchemaMetadataMaps`) |
|
`twenty-orm/repository/workspace-{delete,soft-delete,update}-query-builder.ts`
| Table name for mutations | `computeTableName(nameSingular,
objectMetadata.isCustom)` | `computeObjectTargetTable(objectMetadata)` |
| `index-metadata/utils/generate-deterministic-index-name-v2.ts` | Index
name hash (must stay bit-identical) | `flatObjectMetadata.isCustom` |
derives from `applicationUniversalIdentifier` |
| `object-metadata/object-record-count.service.ts` | Table name for
record count | `computeTableName(nameSingular, isCustom)` |
`computeObjectTargetTable(flatObjectMetadata)` |
|
`workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts`
| Match seed config by table name | `computeTableName(item.nameSingular,
item.isCustom)` | `computeObjectTargetTable(item)` |
| `commands/workspace-export/workspace-export.service.ts` +
`…/utils/generate-workspace-schema-ddl.util.ts` | Export table name (raw
entity) | `objectMetadata.isCustom` |
`!isTwentyStandard…(objectMetadata.application?.universalIdentifier)` |
|
`flat-field-metadata/services/flat-field-metadata-type-validator.service.ts`
| Block users creating reserved field types |
`args.flatEntityToValidate.isCustom` |
`!args.flatEntityToValidate.isSystem` |
| `api/common/.../common-create-many-query-runner.service.ts` | Don't
let client overwrite system `createdBy` |
`createdByFieldMetadata.isCustom === false` |
`createdByFieldMetadata.isSystem === true` |
|
`field-metadata/utils/resolve-field-metadata-standard-override.util.ts`
| Skip i18n/overrides for custom fields | `if (fieldMetadata.isCustom)
return raw` | **removed** — falls through on
`isDefined(standardOverrides)` |
|
`object-metadata/utils/resolve-object-metadata-standard-override.util.ts`
| Skip i18n/overrides for custom objects | `if (objectMetadata.isCustom)
return raw` | **removed** — same fall-through |
|
`command-menu-item/utils/build-navigation-interpolation-context.util.ts`
| Override context for nav labels | passed `isCustom` into resolver |
dropped (resolver no longer needs it) |
| `api/common/.../data-arg-processor.service.ts` | `isCustom` for
record-position table name | `flatObjectMetadata.isCustom` | derives
from `applicationUniversalIdentifier` |
| `metadata-modules/minimal-metadata/minimal-metadata.service.ts` |
Minimal DTO + override context | `flatObjectMetadata.isCustom` | derives
from `applicationUniversalIdentifier` |
|
`commands/upgrade-version-command/1-23/…backfill-record-page-layouts.command.ts`
| Filter to custom objects | `objectMetadata.isCustom` |
`!isTwentyStandard…(applicationUniversalIdentifier)` |
### Server — DTO / API population
| Location | Before | Now |
|---|---|---|
|
`flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util.ts`
| passthrough `isCustom` | derives from `applicationUniversalIdentifier`
|
|
`flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util.ts`
| passthrough `isCustom` | derives from `applicationUniversalIdentifier`
|
|
`object-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util.ts`
(REST) | `entity.isCustom` | `entity.applicationId !==
standardApplicationId` |
|
`field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util.ts`
(REST) | `entity.isCustom` | `entity.applicationId !==
standardApplicationId` |
| `dataloaders/dataloader.service.ts` | passed
`flatFieldMetadata.isCustom` into override resolver | dropped (resolver
no longer needs it) |
> REST controllers (`object-metadata.controller.ts`,
`field-metadata.controller.ts`) resolve `standardApplicationId` once per
request from the cached `flatApplicationMaps`.
### Frontend
| Location | Purpose | Before | Now |
|---|---|---|---|
| `settings/.../SettingsObjectFieldDisabledActionDropdown.tsx` | Whether
an inactive field is deletable | `isDeletable = isCustomField` |
`isDeletable = isCustomField && !isSystemField` |
### Unchanged (out of scope)
`isCustom` on `IndexMetadata` / `View` / `Skill` / `Agent` and their
guards still read the persisted column.
Breaking change is on the isCustom filter on field and object APIs, this
is never used in the FE and unlikely used by external consumers
Fast-follows for the new layout / flat redesign (master:
twentyhq/core-team-issues#2478).
## Changes
- **Main navbar 48px** (twentyhq/core-team-issues#2479) —
`SIDE_PANEL_TOP_BAR_HEIGHT` 40 → 48, so `PageCardHeader` matches the
Figma target. The side panel top bar shares this constant and stays
aligned.
- **Content panel 12px radius** (twentyhq/core-team-issues#2480) —
`PageCardLayout` card gets a full border + 12px radius and an 8px inset
(`spacing[2]`) so it floats on the shell instead of square full-bleed.
- **Square three-dots button** (twentyhq/core-team-issues#2481) — added
a `square` option to `AnimatedButton`; the page-header side-panel toggle
now renders a 24×24 square icon button instead of a 32×24 pill.
- **Table checkbox sizing** (twentyhq/core-team-issues#2482) — restored
`box-sizing: content-box` on the checkbox box. Its border is declared
outside the label size, so the global `border-box` reset (#21349) was
shrinking it (14px → 12px). Same fix pattern as #21349.
- **Tertiary navbar background** (twentyhq/core-team-issues#2483) — left
navbar / app shell use `background/tertiary` instead of the noisy
surface (`DefaultLayout`, `UserOrMetadataLoader`).
- **Skeleton loading** (twentyhq/core-team-issues#2484) — metadata +
content loading now match the new layout: tertiary shell, 12px rounded
content panel, 48px navbar, sparse bars, empty body (removed the dense
full-width rows). Left-panel skeleton bars use `quaternary` so they stay
visible on the tertiary shell.
## Verification
- typecheck (tsgo) + oxlint + oxfmt pass for all changed files.
- Verified live against a running workspace: measured navbar = 48px,
three-dots = 24×24, checkbox box-sizing = content-box (14px), card
radius = 12px, shell background = tertiary (no noisy image).
Content-loading skeleton matches the target.
Some bugs fixed in this PR
1. From UI any field could be chosen to group the query by it, while for
instance, RAW_JSON type (eg workflowRun.state) is not supported by
PostgreSQL to group a query by. Fix: removed it from the "group by"
fields options in FE + in BE -->
2. The BE check existed (isFlatFieldMetadataSupportedInGroupBy) but the
signature was malformed: it expected`{ fieldMetadataType,
fieldMetadataName, fieldMetadataIsSystem }` while every caller passes a
flat field metadata object with type/name/isSystem. So the check is
mis-wired — at runtime the destructured props are undefined, making it
always return true (validation bypassed). Fixed this.
3. Group by does not work with Morph relations if their direction is
ONE_TO_MANY. Added that constraint.
4. Group by with morph relations were broken even for MANY_TO_ONE,
because a morph is stored as one field per target
(polymorphicOwnerRocket, polymorphicOwnerSurveyResult…), each with its
own join column, but the frontend collapsed them into a single
polymorphicOwner field — so the backend tried to resolve a non-existent
polymorphicOwnerId. Fix: Frontend: added a target picker so you choose
the specific morph target (then its sub-field), storing the real
per-target field id. Backend: fixed validate-relation-subfield to use
the per-target field's own relationTargetObjectMetadataId instead of the
multi-target resolver that returned null.
5. (improvement) When an error occured in the query, the graph showed
"No data". Updated it to "error". (screenshot 1)
6. When a field used as a filter on a graph is deleted, it is not
deleted as a graph filter (which is ok because it would involve parsing
all the graph's configuration json to find whether a field is
referenced; there is no foreign key), which prevented from further
modifying the graph's filters. Fixed this + add an indicator that the
filter is can/should be removed (see screenshot 2)
7. "Ambiguous column name" PG error occurs when ordering by "creation
date" of a related field, because both objects have createdAt field.
Fixed it by adding table alias as prefix.
8. (improvement) While working on #5 I did not understand why we could
directly do `"objectMetadataNameSingular"."columnName" `while I expected
that for custom objects it would have to be
`_objectMetadataNameSingular`. that's simply because we use an alias
from the beginning. To add clarity, within groupBy code I replaced
`objectMetadataNameSingular` with `objectAlias` everywhere it is indeed
inherited from us using objectAlias.
<img width="685" height="391" alt="Screenshot 2026-06-08 at 12 01 45"
src="https://github.com/user-attachments/assets/f2b15ca5-da39-4114-8188-69f58f3c4cbf"
/>
<img width="598" height="341" alt="Screenshot 2026-06-08 at 11 53 55"
src="https://github.com/user-attachments/assets/66372811-4a37-40d9-b43a-4af51f89b6e6"
/>
## What
After a partner submits the application wizard, the success screen now
offers an inline Cal.com booking widget so they can book an intro call
on the spot — keeping the partner process high-touch.
- Inline Cal.com embed on the application success step, prefilled with
the applicant's name/email (company in the notes)
- Wide `month_view` layout; the success modal widens to ~960px (the
4-step form and mobile are unchanged)
- Event-type-details panel hidden via `cal('ui', { hideEventTypeDetails:
true })` so it's just calendar + times, on its own `partner-intro` Cal
namespace (isolated from the ContactCal embed)
- "I'll book later" escape hatch; backend / submission path untouched
## Why
The warmest moment is right after someone opts in. Today the success
screen only shows a Close button — this turns that moment into a
scheduled conversation.
## How
- `PartnerIntroCalEmbed` — thin wrapper over `@calcom/embed-react`
(already a dependency), reusing the existing `ContactCal` embed pattern
- `buildPartnerIntroPrefill` — pure mapping of applicant fields → Cal
prefill
- `PartnerApplicationSuccess` — presentational success view (heading +
subtitle + embed + dismiss)
- The wizard reports submitted-state up (`onSubmittedChange`) so the
modal widens only on the booking step
- New Lingui copy + regenerated catalogs (en/es/fr)
## Test plan
- `npx jest PartnerApplication` — green (prefill mapping, embed
link/layout/prefill, success view)
- `npx nx typecheck twenty-website` — clean
- `npx oxlint -c .oxlintrc.json` / `npx oxfmt --check` — clean
- Manual: submit the wizard → success step shows the wide booking
calendar, prefilled, dark theme, no event-details panel; "I'll book
later" closes the modal
## Notes
- Frontend only — no backend, schema, or submission-path change
- Cal link `rashad-twenty/partner-intro` lives as a constant in
`config.ts`
- Branch is currently behind `main`; happy to rebase before review
## Problem
The merge queue is broken. Every queued PR (#21357, #21361, #21364,
#21366, …) fails on the same E2E assertion in
`workflow-creation.spec.ts:36`:
```
Locator: getByTestId('top-bar-title').getByPlaceholder('Name')
Error: element(s) not found
```
All other E2E tests pass, which pointed to a regression already on
`main` rather than any individual PR.
## Root cause
#21308 ("generalize the page primary/secondary bars (flat redesign)")
switched `RecordShowPageHeader` from `PageHeader` to the new
`PageCardHeader`.
- The old `PageHeader` wrapped its title in `<StyledTitleContainer
data-testid="top-bar-title">`.
- The new `PageCardHeader` renders the breadcrumb slot **without** that
`data-testid`.
The editable record title cell (the `Name` input the test fills in)
still renders fine inside `ObjectRecordShowPageBreadcrumb` — it just
lost the `top-bar-title` wrapper that the E2E suite locates it by. The
testid is also used by the `blank-workflow` fixture.
## Fix
Restore `data-testid="top-bar-title"` on the record-show breadcrumb
container, which wraps exactly what `PageHeader` previously did (the
editable `Name` input and, after save, the record name text). Minimal
and behavior-preserving; record-index and standalone pages use different
header slots and were unaffected (their E2E tests passed throughout).
## Why
App installs on cloud intermittently fail with a 504, surfacing in
Sentry as `Migration action 'update' for 'logicFunction' failed` +
`Failed to rollback transaction: Query runner already released`. This is
**temporary instrumentation** to pin down where the time goes — to be
reverted once the bottleneck is fixed. Everything is greppable via
`[install-perf]` and marked `// TODO(install-perf)`.
## What the local repro already told us
I instrumented the manifest-sync/migration path and ran a local harness
(new skipped spec) installing **1 / 8 / 30 logic functions**, for both
create and the checksum-bump **update** (the incident path):
| stage (N=30, update) | ms |
|---|---|
| flat-maps recompute | ~1 |
| build migration | ~11 |
| transaction (all actions + commit) | ~79 |
| post-commit cache invalidate | ~6 |
| **full sync** | **~135** |
Nothing approached 1s, let alone 10s; no slow queries logged. So the
migration/cache code is **not** the algorithmic cause. Given the
in-transaction `UPDATE ... WHERE id=?` is intrinsically fast, a >10s in
prod almost certainly means it was **blocked on a lock**, and the 10s
node-pg `query_timeout` (`core.datasource.ts`) then killed the
connection → the observed errors + 504. Local can't reproduce prod lock
contention / table sizes, hence this instrumentation.
## What this adds (all `TODO`-marked)
- **hrtime per-stage timing** — flat-maps recompute, build vs run,
per-action (`>50ms`), transaction summary, post-commit cache
invalidation. Uses `process.hrtime` because the integration harness
enables fake timers (so `Date.now()` is useless there).
- **`maxQueryExecutionTime`** slow-query logging on the core datasource
(logs the offending SQL).
- **Scoped `SET LOCAL lock_timeout = '8s'`** on the migration
transaction (below the 10s `query_timeout`) → a blocked action fails
fast with a clear *"canceling statement due to lock timeout"* instead of
the opaque connection kill.
- **Best-effort `pg_stat_activity` snapshot on failure** (on a fresh
pooled connection) to identify the blocking session, plus a **guarded
rollback** so a released connection stops masking the real error.
- **Skipped local perf harness**
(`logic-function-install-performance.integration-spec.ts`) — run
manually with `nx test:integration:with-db-reset -- --testPathPattern
"logic-function-install-performance"`.
## How we'll use it
Deploy, reproduce the failing install, and read the `[install-perf]`
logs: the per-action timing names the action, the `lock_timeout` message
+ `pg_stat_activity` snapshot name the **blocking** query/PID. Then
revert this PR and fix the actual contention.
Typecheck (`nx typecheck twenty-server`) is clean.
## Cause
PR #21308 ("generalize the page primary/secondary bars") swapped the old
`PageHeader` for the new `PageCardHeader` on the record-index,
record-show, and standalone pages. The old header set
`data-click-outside-id="page-action-container"` on its action container
— an id that the record table/board/calendar click-outside listeners
exclude so header clicks don't clear the current selection. The new
`PageCardHeader` dropped that attribute.
## Implications
With the attribute gone, clicking a pinned command-menu item registered
as a click *outside* the table/board, which reset the selected records
before the action read them. As a result, pinned actions and workflows
triggered from the top bar ran with an empty selection.
## Fix
Re-add `data-click-outside-id={PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID}`
to `PageCardHeader`'s action container. Since all three migrated headers
route their buttons through this shared component, the single change
covers every affected page.
## Summary
- Increase BullMQ worker concurrency for `logicFunctionQueue` from 1
(default) to 10
- Logic function executions are I/O-bound Lambda calls — the worker just
holds an HTTP connection open, so higher concurrency doesn't add
CPU/memory pressure
- With 13 worker pods, this goes from 13 to ~130 concurrent slots, which
should resolve the ~3h average queue latency observed in Grafana
## Test plan
- [ ] Monitor `avg_latency_ms` for `logic-function-queue` in the Grafana
job queue dashboard after deploy
- [ ] Verify worker pod CPU/memory remains stable
## Summary
- Moves current version to previous versions array
- Sets TWENTY_CURRENT_VERSION to the new version
- Updates TWENTY_NEXT_VERSIONS with the next minor version
- Bumps twenty-client-sdk, twenty-sdk, and create-twenty-app to the same
version
## Checklist
- [ ] Verify version constants are correct
- [ ] Verify npm package versions match
Co-authored-by: Github Action Deploy <github-action-deploy@twenty.com>
## What
Makes workspace resolution consistent across the SSO, OTP and
login-token sign-in flows — the target workspace is derived from the
authenticated principal rather than from separate request-supplied
values.
- **SSO callback** (`sso-auth.controller.ts`): validates the resolved
workspace against the authenticating identity provider's own workspace,
so every SSO session is scoped to the provider that issued it. The
`workspaceInviteHash` is request-controlled and shouldn't select a
different workspace than the provider.
- **`getAuthTokensFromOTP`** (`auth.resolver.ts`): reuses the shared
`validateWorkspaceAccess` helper already used by
`getAuthTokensFromLoginToken`, so the login token and the
origin-resolved workspace are checked the same way in both flows.
- **SSO enablement** (`auth.service.ts`, `workspace.validate.ts`): SSO
sign-in now checks the workspace operates an active SSO identity
provider, matching how the other providers gate on their per-workspace
settings, instead of treating SSO as unconditionally enabled.
## Tests
- `auth.service.spec.ts`: SSO sign-in throws when the workspace has no
active SSO identity provider; proceeds when it does.
The controller and resolver spec additions were dropped from this PR;
the behaviours below cover them via manual testing on `main`.
`tsgo`, `oxlint` and `oxfmt` all clean on the changed files.
## How to test on main
Check out this branch on top of `main` and exercise each flow against a
multi-workspace setup (workspace **A** and workspace **B**, each on its
own domain).
**1. SSO callback is scoped to the issuing provider**
(`sso-auth.controller.ts`)
- Configure an SSO identity provider (SAML or OIDC) on workspace **A**
and set it to **Active**.
- Start an SSO sign-in for workspace **A**, but tamper with the callback
so the resolved workspace points at **B** (e.g. supply a
`workspaceInviteHash` belonging to **B**).
- Expected: the callback is rejected with `OAUTH_ACCESS_DENIED`
("Identity provider does not belong to this workspace"). A clean
callback that resolves to **A** still completes sign-in.
**2. Inactive SSO provider blocks sign-in** (`auth.service.ts` /
`workspace.validate.ts`)
- Take workspace **A**'s SSO identity provider and set its status to
something other than `Active` (e.g. inactive/draft).
- Attempt SSO sign-in for **A**.
- Expected: sign-in is denied with `OAUTH_ACCESS_DENIED` ("Identity
provider not found"). Flipping the provider back to `Active` lets
sign-in proceed.
**3. OTP login token must match the origin workspace**
(`getAuthTokensFromOTP` in `auth.resolver.ts`)
- Enable two-factor authentication for a user who belongs to workspace
**A**.
- Sign in to obtain a login token scoped to **A**, then call
`getAuthTokensFromOTP` (submit the OTP) from workspace **B**'s
origin/domain.
- Expected: the request is rejected with `FORBIDDEN_EXCEPTION` ("Token
is not valid for this workspace") and no tokens are issued. Submitting
the OTP from **A**'s origin issues tokens as before.
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Problem
On workspaces with a sizeable AI chat history, the whole app was
freezing during navigation, including Settings (one navigation click
measured ~6.5s).
## Root cause
`AgentChatProvider` is mounted app-wide in `AppRouterProviders`, so its
effects run on every page.
On every render it would:
1. auto-select the most recently active thread
(`AgentChatThreadInitializationEffect`),
2. fetch that thread's **full message history**
(`AgentChatMessagesFetchEffect`),
3. run `AgentChatStreamingPartsDiffSyncEffect` →
`updateStreamingPartsWithDiff`, which loops over every message doing
`isDeeplyEqual(existing, incoming)` + `structuredClone`.
A large thread would produce multi-second freeze on every interaction,
app-wide. (Confirmed via a Chrome CPU profile)
## Fix
Don't run the agent-chat **message runtime** until the chat is actually
opened.
## Note
There is still room for improvement, opening AI chats would still be
very slow.
## Problem
Syncing a `twenty-sdk` app against a server (`twenty dev`) **crashes the
server process**. The metadata migration completes, then the server-side
`GqlTypeGenerator` regenerates typed clients via the vendored genql
codegen in `twenty-client-sdk`, which throws and exits node:
```
ConfigError: Couldn't find plugin for AST format "estree".
Plugins must be explicitly added to the standalone bundle.
at .../packages/twenty-client-sdk/dist/generate.cjs
Node.js v24.5.0 ← process exits
```
The CLI sees `ECONNRESET`; the app row still persists because the crash
happens after the metadata commit. Any app sync takes the server down.
## Root cause
The genql codegen formatter
[`prettify.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-client-sdk/src/generate/genql/helpers/prettify.ts)
was vendored (in #21339) targeting **prettier 2.8**: a synchronous
`format()` and `prettier/parser-typescript`, which in 2.8 bundles the
estree printer. `package.json` pins `prettier ^2.8.8`, but the monorepo
actually resolves/bundles **prettier 3.8.3** (`^2.8.8` is silently
unsatisfied — no 2.8.x is nested for this package, and the subpath
imports are bundled from the hoisted 3.8.3). Under prettier 3 the estree
printer must be added explicitly (`prettier/plugins/estree`) and
`format()` is async — so the codegen throws.
`prettier/parser-typescript` / `parser-graphql` don't even exist in
prettier 3 (only `prettier/plugins/*`), so the declared `^2.8.8` was
already inconsistent with what runs.
## Fix
- `prettify`: switch to the prettier-3 entrypoints
`prettier/plugins/{graphql,typescript,estree}`, `await` the async
`format()`, and fall back to the unformatted (still valid) code on any
failure so cosmetic formatting can never crash codegen again.
- `RenderContext.toCode` + `clientTasks`: propagate the now-async
`prettify` (await the four `toCode` call sites).
- Bump the declared `prettier` dependency `^2.8.8 → ^3.8.3` to match
what is actually used (only consumer; minimal lockfile diff).
## Verification (local, source server on :3000)
- `twenty dev --once` now completes: `Registering application → Syncing
manifest → Generating API client → ✓ Synced` with the **server staying
up**.
- The app integration test passes (full re-sync of 6 metadata objects +
`MetadataApiClient`/`CoreApiClient` CRUD through the generated genql
runtime).
- `nx build twenty-client-sdk` (incl. `tsgo` typecheck) passes.
Release note: this is a v2.11 blocker — without it, installing/syncing
any app crashes the server.
Automated daily sync of `ai-providers.json` from
[models.dev](https://models.dev).
This PR updates pricing, context windows, and model availability based
on the latest data.
New models meeting inclusion criteria (tool calling, pricing data,
context limits) are added automatically.
Deprecated models are detected based on cost-efficiency within the same
model family.
**Please review before merging** — verify no critical models were
incorrectly deprecated.
Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com>
## Problem
Since the `twenty-ui` → `twenty-ui-deprecated` / `twenty-new-ui` →
`twenty-ui` rename (#21315), many deprecated components render with
**compacted height** — e.g. dropdown menu items collapse from 32px to
16px, and chips from ~24px to 16px.
## Root cause
The new `twenty-ui` (formerly `twenty-new-ui`) ships a global reset in
`packages/twenty-ui/src/styles/base/reset.scss`:
```css
*, *::before, *::after {
box-sizing: border-box;
}
```
This is bundled into `twenty-ui/style.css`, which the app imports in
`index.tsx`. #21315 did not change the `import 'twenty-ui/style.css'`
line, but it changed what `twenty-ui` resolves to (old → new), so this
**global `border-box` reset now applies app-wide**.
Several deprecated components were authored against the **content box**,
e.g. `StyledMenuItemBase`:
```css
height: calc(32px - 2 * var(--vertical-padding));
padding: var(--vertical-padding) var(--horizontal-padding);
```
With `content-box` the padding sits *outside* the declared height → 32px
total. Under the new `border-box` reset the padding is folded *inside* →
16px total. (`Chip` uses `height: spacing[4]` + outside padding — same
failure mode.)
Verified in the running app: the collapsed menu item computes
`box-sizing: border-box`, matched by the rule `*, ::before, ::after {
box-sizing: border-box }`; `height` resolves to `calc(32px - 2 * 8px) =
16px`.
## Fix
Add `box-sizing: content-box` to the affected deprecated components. A
class selector outranks the universal `*` reset, so this restores their
intended sizing **without touching the global reset** (which the new
`twenty-ui` components rely on).
Affected: `StyledMenuItemBase` (and its hoverable variant),
`MenuItemSelect`, `MenuItemSuggestion`, `Chip`.
Replaces #21279 and #21282 with one clean PR from `main`.
Generalizes the settings primary-bar / secondary-bar card chrome to the
record index, record show and standalone pages via a shared
`PageCardLayout` + `PageCardHeader` (the side panel sits as a sibling of
the content card), and applies the new flat design direction: square
corners on the card, side panel and loading skeletons.
Iterating toward the new design (Figma node 102282-221623); the
confirmed direction and the explicit "remove rounded corners" change are
in, remaining designer specifics to follow.
## Summary
Propagates the just-published **`twenty-sdk@2.10.1`** security patch
into the `twenty-apps/*` mini-apps, clearing the bulk of the
nested-lockfile Dependabot alerts (the `tmp` + `undici` clusters).
Each app carries its own `yarn.lock`, so the fix only reaches them once
they bump the SDK. `2.10.1` drops the two vulnerable transitive deps
every app inherited:
| Vuln dep | Source | Fixed by |
|---|---|---|
| `tmp@0.0.33` (GHSA-ph9p / GHSA-52f5) | `inquirer ^10 →
external-editor` | `inquirer ^14` → `@inquirer/editor@5` (no
external-editor) |
| `undici@<6.24` (5 GHSAs) | `@genql/cli` | vendored genql codegen
(`@genql/cli` removed) |
## Changes
Bumps `twenty-sdk` **and** `twenty-client-sdk` (whichever each app pins
— several pin both) to `2.10.1` and regenerates each lockfile.
**10 apps updated** (all on the v2 line — minor bump, low risk):
`twenty-slack`, `twenty-discord`, `twenty-linear`, `twenty-partners`,
`twenty-fireflies`, `people-data-labs`, `twenty-for-twenty`, `exa`,
`github-connector`, `postcard`.
Verified per-app after regen: **`tmp@0.0.33` = 0** and **`undici@5` =
0** in every updated lockfile.
## Deliberately excluded
Three apps pin a **pre-2.0** SDK, where `→ 2.10.1` is a major jump that
risks breaking the app and needs per-app validation:
- `examples/hello-world` (`0.9.0`)
- `internal/call-recording` (`0.6.3-alpha`)
- `internal/self-hosting` (`1.22.0-canary.6`)
These still carry one `tmp`/`undici` alert each and should be handled in
a follow-up.
## Related
- `twenty-sdk@2.10.1` release (tag `sdk/v2.10.1`) — backport of #21339
(undici) + #21340 (tmp) from `main`.
## Summary
Removes all transitive **`tar@6.2.1`** from the dependency tree,
resolving [Dependabot alert
#400](https://github.com/twentyhq/twenty/security/dependabot/400)
([GHSA-34x7-hfp2-rc4v](https://github.com/isaacs/node-tar/security/advisories/GHSA-34x7-hfp2-rc4v)
/ CVE-2026-24842 — node-tar hardlink path traversal, high/8.2).
The alert had been dismissed as `no_bandwidth`, but `tar@6.2.1` was
still in the lockfile. I confirmed **6.2.1 is genuinely exploitable** by
running the advisory's PoC (the hardlink escaped the extraction dir to a
parent-directory file); `7.5.16` blocks it. There is **no patched 6.x
release** — the fix only exists in `7.5.7+`.
## Approach
Upgrade the build tooling that pulled tar v6 to the majors that depend
on tar v7, rather than forcing tar onto v6-era consumers:
| Package | Change | Mechanism |
|---|---|---|
| `node-gyp` | 10.2.0 / 7.1.2 / 9.4.1 → **12.4.0** | resolution |
| `cacache` | 18 → **20.0.4** | resolution |
| `make-fetch-happen` | → **15.0.6** | resolution |
| `mintlify` (twenty-docs) | `latest` → **^4.2.594**
(`@mintlify/previewing` → tar 7.5.15) | direct dep bump |
| `@electron/rebuild`, `@electron/node-gyp`, `pacote` → `tar` | →
**^7.5.16** | scoped resolution |
The last row covers the two subtrees with **no upstream tar-v7
release**: `@electron/rebuild` (+ electron's `node-gyp` fork) in
`twenty-companion`, and `pacote@11/15` via `zapier-platform-cli` in
`twenty-zapier`.
All `tar` now resolves to **7.5.13 / 7.5.15 / 7.5.16**; `node_modules`
verified free of tar v6.
## Validation done
- `yarn install` completes cleanly (constraints pass, only pre-existing
`enableScripts: false` + peer-dep warnings).
- Installed `node_modules` contains zero tar v6.
## Validation still needed before merge ⚠️
- The scoped overrides force tar v7 onto packages written for the v6
API. Resolution is consistent, but **runtime not exercised**
(`enableScripts: false` skips native builds at install). Please
validate:
- `twenty-companion` electron `make` / native rebuild
- `twenty-zapier` build/push
- If either breaks, drop the scoped overrides and accept those two
**dev/build-only** clusters as residual — they extract only trusted
archives at build time, so the CVE (which needs attacker-controlled
input) isn't reachable there.
- `mintlify` is pinned (not `latest`) because `.yarnrc.yml`'s
`npmMinimalAgeGate: 3d` quarantines the true latest. Pinning is arguably
healthier, but it's a deliberate behavior change.
## Note
twenty-server's own runtime tarball extraction
(`extract-tarball-securely.util.ts`) was already on patched tar **and**
rejects all hardlink/symlink entries — so this PR addresses the
remaining build-tooling exposure, not a live runtime hole.
Large `yarn.lock` churn is expected: the node-gyp/cacache major bumps
refresh npm-internals tree-wide.
## What
Vendors a narrowed copy of
[`@genql/cli@3.0.5`](https://github.com/remorses/genql) (MIT) into
`packages/twenty-client-sdk/src/generate/genql/` and repoints the two
client generators at it, then removes `@genql/cli` from
`twenty-client-sdk`, `twenty-sdk` and `create-twenty-app`.
## Why
`@genql/cli` was used **only** to generate the typed GraphQL client from
an SDL string. It is unmaintained and pulls in vulnerable/abandoned
transitives — `undici@5` (**30 Dependabot alerts**), `native-fetch`,
`listr`, `yargs`, etc. None of these were ever executed by Twenty: the
sole consumer of `undici`/`native-fetch` is `@genql/cli`'s live-endpoint
schema-introspection path, and Twenty always passes a schema string,
never an endpoint.
Removing the package eliminates the dependency at the source — for
Twenty and for scaffolded end-user apps.
## What changed vs upstream
The vendored copy (`genql/README.md` + `genql/LICENSE`) keeps the
`render/` and `runtime/` trees verbatim and narrows the orchestration:
- **Dropped the endpoint/introspection path** (`schema/fetchSchema.ts`)
— the only `undici`/`native-fetch`/`qs` consumer.
- **Dropped `listr`** — generation tasks run as plain sequential `async`
functions (file contents unchanged).
- **Replaced `fs-extra`/`mkdirp`/`rimraf`** with `node:fs`.
- **Runtime templates are imported as `?raw`** and bundled, instead of
read from `node_modules` at generation time.
- **Kept `prettier@^2.8` and `@graphql-tools/*`** so the generated
output is byte-for-byte identical.
## Verification
- **Byte-identical output**: regenerating the metadata client from its
committed schema produces a recursive-diff-clean result vs the previous
`@genql/cli` output (including the copied `runtime/` folder). The core
client generates and esbuild-bundles cleanly.
- The public `twenty-client-sdk/generate` barrel API is unchanged
(twenty-server / twenty-sdk consumers unaffected).
- `undici@^5`, `native-fetch`, `@genql/cli`, `listr`, `yargs@^15` and
`subscriptions-transport-ws@0.9` are gone from `yarn.lock` (net −364
lines).
- `twenty-client-sdk` and `twenty-sdk` typecheck, lint and build;
`twenty-client-sdk` tests pass (9/9).
## Notes
- The vendored folder is excluded from `oxlint`/`oxfmt` (it is
third-party code, with `@ts-nocheck` on the verbatim renderers,
mirroring the generated output).
- Stacks conceptually on #21334 (drops `@genql/runtime`); the two are
independent and only overlap trivially in `yarn.lock`. `@genql/runtime`
is intentionally left for that PR.
The **central fix** for the `tmp` Dependabot alerts in
`packages/twenty-apps/*` — so apps don't each need a per-app
`resolutions` entry.
### Root cause
Every app depends on `twenty-sdk`, whose inquirer chain pulls the
vulnerable `tmp`:
```
twenty-sdk → inquirer ^10 → @inquirer/prompts 7.x → @inquirer/editor 4.x → external-editor → tmp@0.0.33
```
`@inquirer/editor 5.x` dropped `external-editor` (and thus old `tmp`),
and it's only reached via `@inquirer/prompts 8.x`, which requires
**inquirer ≥ 13.4.3**. So `^12` isn't enough — bump to **`^14`**
(latest):
```
inquirer 14 → @inquirer/prompts 8.5.2 → @inquirer/editor 5.2.2 (no external-editor)
```
### Verified
- The SDK uses the classic `inquirer.prompt([...])` API (uninstall / add
/ remote commands) — **typechecks cleanly under inquirer 14**.
- After the bump, the SDK's subtree resolves `@inquirer/editor@5.2.2`
(the lingering `external-editor` in this repo's lockfile is from *other*
consumers — `nx`/`zapier` — handled separately).
### Propagation
Fixes it **once** for every app using the SDK, with no per-app
`package.json` additions. Existing `twenty-apps/*` clear their `tmp`
alert once a new `twenty-sdk` is published and they bump to it;
newly-scaffolded apps are clean immediately.
(The root-workspace `tmp` from `nx`/`zapier` is handled by
twenty#21338.)
Adds an `on-partner-application-created` logic function triggered on the
`partner.created` database event. When the website application form
creates a new Partner, it posts a rich embed to a Discord channel
(applicant, company, country, languages, partner scope, skills) with a
deep link to the record.
## How it works
- Fires only on genuine form submissions — discriminates via
`createdBy.source === 'APPLICATION'`, which excludes seed/import (`API`)
and manual UI (`MANUAL`) creation.
- Runs out-of-band on the worker (database event trigger), so it adds
**no latency** to the applicant's submission, and the linked Person
already exists by the time it runs.
- Best-effort: a Discord failure never fails the trigger (wrapped in
`try/catch`, 8s timeout).
## Configuration (per workspace — Settings → Apps → Twenty Partners →
Variables)
- `DISCORD_WEBHOOK_URL` (secret) — the incoming webhook URL. **The
feature is a no-op when unset.**
- `PARTNER_APP_FRONTEND_URL` — workspace front-end base URL for the
record deep link (e.g. `https://partners.twenty.com`).
## Notes
- New logic function + two application variables; version bumped to
**0.4.0** (minor).
- Unit tests cover the source-guard branches, the on/off switch, the
embed contents/ordering, and best-effort failure handling.
- The website and the existing `submit-partner-application` handler are
untouched.
Resolves the **root** `tmp` Dependabot alert (`tmp < 0.2.6`, #1308).
In the root workspace, `tmp` is a transitive dep of `nx` (`~0.2.1`) and
`zapier-platform-cli` (exact `0.2.1`) — dev/CLI tooling that
**exact-pins old tmp with no fixed parent to upgrade to** (verified:
even latest `zapier-platform-cli@15.19.0` still pins `0.2.1`). So it's
pinned to the patched **0.2.7** via a root `resolutions` entry — the
correct tool for un-dedupe-able transitive pins. Not in the prod image.
### Why the `twenty-apps/*` alerts are not fixed here
Those come from a different source — `twenty-sdk → inquirer ^10 →
@inquirer/editor 4.x → external-editor → tmp@0.0.33`. Rather than add a
`resolutions` block to every app's `package.json` (which doesn't scale —
every newly-scaffolded app would need it), they'll be fixed
**centrally** by bumping `inquirer` in `twenty-sdk` (`^10 → ^12`, which
reaches `@inquirer/editor 5.x` that dropped external-editor). Separate
PR — apps inherit the fix on the next SDK release with no manual
additions.
Resolves the **vitest Critical** Dependabot alerts
(`GHSA-5xrq-8626-4rwp`, vitest `< 3.2.6`) — #1422–#1433.
Each `packages/twenty-apps/*` project is an **independent yarn project**
with its own `package.json` + `yarn.lock` (not part of the root
workspace). 12 of them declared `vitest: ^3.1.1` and locked an older
3.2.x. This bumps the range to `^3.2.6` and refreshes each lockfile to
**3.2.6** (latest 3.x, published 2026-06-01).
Projects updated: `community/github-connector`,
`examples/{hello-world,postcard}`,
`internal/{exa,people-data-labs,self-hosting,twenty-discord,twenty-fireflies,twenty-for-twenty,twenty-linear,twenty-partners,twenty-slack}`.
- Dev-scope only (test runner); no runtime impact.
- The **root workspace already uses vitest 4.x** (≥ the fix) and is
intentionally untouched.
- Verified: no `vitest < 3.2.6` remains in any `twenty-apps` lockfile.
## What
Removes the `@genql/runtime` dependency from `twenty-client-sdk` and
`twenty-sdk`. It was declared but **never imported** in source.
## Why
The genql codegen (`@genql/cli` `generate()`) inlines a **fully
self-contained runtime** into every generated client — see the committed
`twenty-client-sdk/src/metadata/generated/runtime/` (all relative
imports) and the generated `index.ts` which imports from `./runtime`,
not `@genql/runtime`. So the `@genql/runtime` package was dead weight in
the dep graph.
Dropping it prunes its abandoned, vulnerable transitive deps **at the
source**:
- `ws@^6` (old)
- `subscriptions-transport-ws@0.9.x`
- `isomorphic-unfetch`
- `zen-observable-ts`
- `graphql-query-batcher`
- `lodash`
None are used by Twenty — the generated client makes plain `fetch`
GraphQL requests and has no `ws`-based subscriptions.
## Verification
- `@genql/runtime` is gone from `node_modules` and `yarn.lock` (103
lockfile lines removed); the remaining
`subscriptions-transport-ws@0.11.0` is a different, maintained version
pulled by an unrelated package.
- `twenty-client-sdk` and `twenty-sdk` typecheck.
- `twenty-client-sdk` unit tests pass (9/9).
- With `@genql/runtime` physically removed from `node_modules`,
`generate()` still emits a complete, self-contained client (`index.ts`
imports `./runtime`).
## Scope
`@genql/cli` (the codegen, which pulls `undici`) is intentionally
**not** touched here — it is still required for client generation and
will be addressed separately.
Bumps `@nestjs` packages to clear the scanner findings they pin on the
prod image. All within-major bumps, past the repo's `npmMinimalAgeGate:
3d`.
## Changes
| Package | From → To | Clears |
|---|---|---|
| `@nestjs/common` | 11.1.16 → **11.1.24** | `file-type@21.3.0` → 21.3.4
|
| `@nestjs/core` | ^11.1.18 → **^11.1.24** | (path-to-regexp 8.4.2) |
| `@nestjs/platform-express` | 11.1.16 → **11.1.24** |
`path-to-regexp@8.3.0` → 8.4.2 |
| `@nestjs/serve-static` | 5.0.4 → **5.0.5** | `path-to-regexp@8.3.0` →
8.4.2 |
| `@nestjs/testing` | 11.1.16 → **11.1.24** | — |
Verified in the regenerated lockfile: **`file-type@21.3.0` and
`path-to-regexp@8.3.0` are gone**. `twenty-server:typecheck` passes
locally.
## Not in scope
- **`lodash@4.17.21`** and **`ws@8.16.0`** are pinned by
**`@nestjs/graphql@12.1.1`** (and lodash also by
`@nestjs/config@3.3.0`). Bumping graphql 12→13 would clear them, but
it's blocked by a **316-line custom patch** implementing Twenty's
multi-schema scoping (`resolverSchemaScope`, `computeReachableTypes`)
welded to 12.1.1's compiled internals — a dedicated effort, not a
routine bump. (Twenty uses the Yoga driver, so it's *not* an Apollo
migration.)
- `@nestjs/config` 3→4 alone wouldn't clear `lodash` (graphql still pins
it), so deferred with the graphql work.
- `path-to-regexp@0.1.12` is express 4.x's own — separate from @nestjs.
I think these screenshots were accidentally committed at the repo root
in #21033. They are not referenced anywhere in the codebase, so removing
them to keep the repo clean.
Signed-off-by: Parship Chowdhury <parshipchowdhury@gmail.com>
This PR migrates the p-limit library to Native graph SDK batching fixing
the concurrency and rate limit issues in production seen for some larger
accounts
## Summary
- Adds a server-side guard that rejects deletion of standard fields
(`isCustom: false`) in the `deleteOneField` path
- The UI already prevents this, but the API had no enforcement, allowing
standard fields to be deleted via direct GraphQL calls
Without this guard, deleting a standard field like `jobTitle` cascades
to drop dependent generated columns (e.g. `searchVector`), leaving the
object in a broken state where all subsequent queries fail with "Data
validation error."
## Test plan
- [x] Call `deleteOneField` with a standard field ID → should return
`FIELD_MUTATION_NOT_ALLOWED` error
- [x] Call `deleteOneField` with a custom field ID → should succeed as
before
- [x] UI deactivation of standard fields still works (deactivate !=
delete)
## What
On the very first load (full browser refresh), the navigation skeleton
didn't match the real `NavigationDrawer` width: it rendered an 8px-wider
panel (an 8px wrapper padding on top of the 220px animated container)
and right-aligned 204/196px item rows, so the menu visibly shifted and
resized once the app finished loading.
This makes every navigation skeleton mirror the real drawer geometry: a
single `NAVIGATION_DRAWER_CONSTRAINTS.default`-wide (220px), border-box
panel with the drawer's own padding, left-aligned, and skeleton bars
that fill the content width like the real nav items (`width: 100%`). The
same fill-width fix is applied to the in-drawer section skeletons so
every navigation skeleton matches the real menu width.
## Verification
- `tsgo` typecheck, `oxlint`, and `oxfmt` all clean on the changed
files.
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
## Description
Promotes the next-gen UI library (formerly `twenty-new-ui`) to the name
**`twenty-ui`** (v0.1.0, publishable) and renames the old package to
**`twenty-ui-deprecated`**. Rewrites ~1,730 `twenty-ui` imports →
`twenty-ui-deprecated`, updates all configs/CI/Docker/deps, and migrates
twenty-front's `Toggle` to the new package (first consumer) as a
drop-in.
## Next steps
- Wire the `ui/v*` publish dispatch (`cd-deploy-tag.yaml` +
`.yarnrc.yml`), then tag `ui/v0.1.0` to publish.
- Continue migrating components from `twenty-ui-deprecated` →
`twenty-ui`.
Hardens the `prod-twenty` server image. Built `--target twenty-server`
and walked it to verify each change.
- **Node 24.15.0 → 24.16.0** (all stages + `.nvmrc`): 24.15.0 links
OpenSSL **3.5.5** (CVE-2026-31798), 24.16.0 links **3.5.6** — the proper
fix (deleting headers only hid it; the binary still linked the vuln
lib).
- **Remove the bundled npm CLI** (`ip-address`): app uses yarn via
corepack, never npm; npm still bundles `ip-address@10.1.0` and its
latest 10.2.0 is itself unfixed — no upgrade path.
- **Remove vendored `example/` apps** (`passport-microsoft/example`
ships a `package-lock.json` for an old Express demo, never
installed/run; not in our lockfile).
- **node-forge → 1.4.0** (Critical CVE-2026-33606) via `yarn dedupe` —
lockfile-only, no phantom dep, no root resolution.
Verified on the built image: node 24.16.0 / openssl 3.5.6, npm CLI +
example dirs absent, node-forge@1.4.0 only.
**Not included (need CI/QA):** real deps pinned inside
`@nestjs/*`/`express` (`lodash@4.17.21`, `file-type`, `path-to-regexp`,
`ws`, `qs`) need parent bumps or scoped resolutions; standalone
`undici@5.29.0` (5→7), `apollo-server-core@3` (EOL), `typeorm`, etc.
(`axios` already patched.)
## Summary
- The `twenty_queue_jobs_waiting_total` gauge was summing all queues
into a single value without a `queue` label, making the Grafana "Jobs
Waiting by Queue" panel show a single aggregated line instead of
per-queue breakdown.
- Uses `getMeter()` directly to call `observableResult.observe(count, {
queue: queueName })` per queue, matching the `by (queue)` grouping the
dashboard already expects.
## Test plan
- [x] Deploy and verify the Grafana "Jobs Waiting by Queue" panel
displays separate series per queue
- [x] Confirm Prometheus scrape returns
`twenty_queue_jobs_waiting_total{queue="..."}` with distinct queue
labels
Clears the `uuid` "missing buffer bounds check in v3/v5/v6" advisory —
patched in **11.1.1**. Bumps `twenty-server`, `twenty-shared`,
`twenty-front` from 9 → `^11.1.1`.
### Why 11 and not 13
uuid **11.1.x still ships a CommonJS build**, so jest loads it with **no
config changes**. uuid went **ESM-only at v12+**, which would otherwise
force `transformIgnorePatterns` workarounds across the jest projects
(and broke server/integration/storybook CI on the earlier 13 attempt).
11.1.1 is the actual patched version, so this is the minimal fix.
### Changes
- `uuid` → `^11.1.1` in the three workspaces (lockfile regenerated under
hardened mode)
- one test (`useCreateManyRecords.test.tsx`): pin the mocked `v4` to its
string-returning overload — uuid's types declare a `Uint8Array` overload
that `jest.mocked` resolves to (present in v11 too, unrelated to ESM).
All usages are named imports, so no source migration. typecheck passes
(server/shared/front); affected specs pass. **No jest config changes.**
Fixes the **high-severity** `serialize-javascript` RCE advisory
(RegExp.flags / Date.prototype.toISOString, patched in **7.0.5**).
- Bumps the direct dep in `twenty-website` `^6.0.2 → ^7.0.5`.
- Only consumer is `src/lib/seo/JsonLd.tsx` (default-export API,
unchanged in v7 — the major only drops old Node support).
- `twenty-website` typecheck passes; lockfile regenerated under hardened
mode (`--immutable --check-cache` clean).
Clears the Electron advisory batch (use-after-free, IPC scoping, origin
handling, ASAR integrity, …) — patched in **39.8.5+**, resolves to
**39.8.10**.
- `twenty-companion` is the standalone desktop companion app
(electron-forge; @electron-forge 7.8 supports Electron 39). Main-process
code only uses basic `require('electron')` APIs, stable across 36→39.
- Lockfile + manifest only; gate-safe; hardened install clean.
⚠️ **Verification caveat:** there's no CI job that builds/tests
twenty-companion, so this isn't exercised by CI, and I couldn't verify
runtime locally (electron-forge packaging downloads the ~100MB Electron
binary / needs a display, and the repo's `enableScripts: false` skips
the binary). **Recommend a manual smoke test** (`yarn make`/`start` in
twenty-companion) before relying on the bump.