ca4fc5615fdf47ebf96c689116a0b7856eb2436b
12590 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ca4fc5615f |
security: refresh lodash + picomatch in twenty-apps lockfiles (#21378)
## What Clears the 4 remaining High alerts in the standalone `twenty-apps` lockfiles (hello-world, call-recording). Both are transitive and already in-range, so a plain lockfile refresh picks up the patched releases — no resolutions. | Package | From → To | Requested by | Advisory | |---|---|---|---| | lodash | 4.17.x → 4.18.1 | `@genql/runtime` (`^4.17.20`), `twenty-client-sdk` (`^4.17.21`) | GHSA-r5fr-rjxr-66jc | | picomatch | 4.0.x → 4.0.4 | `tinyglobby` (`^4.0.3`) | GHSA-c2c7-rcm5-vvqj | Only the two app `yarn.lock` files change. These are isolated example/internal apps (not in the root workspace), in the same family as the already-merged #21371 / #21374. |
||
|
|
5f7638cdaf |
fix(front): surface widget render errors via ErrorBoundary onError (#21009)
## Summary
The page-layout widget `ErrorBoundary` in `WidgetCardShell` renders a
generic
"Invalid Configuration" fallback whenever a widget renderer throws.
Because
there is no `onError` handler, the underlying error is swallowed —
unrelated
widget types (fields, notes, front-component, etc.) all surface the same
chip
with no telemetry, which makes render failures hard to triage.
This adds an `onError` handler that forwards the caught error to
`console.error`
and to Sentry (when available) with the widget's `id`, `type`, and
`configurationType` as extra context. It reuses the same dynamic-import
Sentry
pattern already used by `AppErrorBoundary` and
`CommandMenuItemErrorBoundary`,
so it degrades gracefully to a console log when Sentry is not
configured. The
fallback UI is unchanged.
## Test plan
- [ ] `npx nx typecheck twenty-front` passes
- [ ] `npx nx lint:diff-with-main twenty-front` passes
- [ ] When a widget renderer throws, the "Invalid Configuration" chip
still
renders and the error now appears in the browser console / Sentry
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
|
||
|
|
0d8d463a44 |
security: clear all High minimatch Dependabot alerts via parent bumps (#21373)
## What Clears **all 14 High `minimatch` ReDoS alerts** (GHSA-7r86-cg39-jmmj, GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26) in the root tree — **by bumping the actual parent dev tools, with no `resolutions`/overrides**. Each parent that pinned a vulnerable minimatch is upgraded so the patched version resolves naturally. | Vulnerable minimatch | Pinned by | Fix | |---|---|---| | 10.0.3 | `@microsoft/api-extractor` 7.55.1 | → 7.58.7 (in-range refresh) → minimatch 10.2.3 | | 3.1.2 | `@stoplight/spectral-core` 1.20.0 | → 1.23.0 (in-range refresh) → minimatch ^3.1.4 | | 3.0.8 | `vite-plugin-dts` 3.8.1 → api-extractor 7.43.0 | bump to `^4.5.4` (already used elsewhere here) → minimatch 10.2.3 | | 4.2.3 | `graphql-config` 4.5.0 via `@graphql-codegen/cli` ^3.3.1 | bump cli to `^5.0.7` → graphql-config 5.1.6 → minimatch ^10 | | 9.0.3 | `zapier-platform-cli` ^15.4.1 | bump to `^19.0.0` | | 7.4.6 | `verdaccio` 6.5.2 → `@verdaccio/core` 8.0.0-next | refresh to 6.7.2 → core 8.1.1 → minimatch 7.4.9 | All six are **build/test tooling** — the ReDoS exposure is build-time, never shipped to users. ## Verification - ✅ Every resolved `minimatch` in `yarn.lock` is now ≥ its patched floor (3.1.5 / 7.4.9 / 9.0.9 / 10.2.3+). No `resolutions` added. - ✅ `nx build`: twenty-shared, twenty-ui, twenty-ui-deprecated, twenty-emails (validates vite-plugin-dts v4) - ✅ twenty-zapier: typecheck + build + `zapier validate` (35/35 checks pass; cli 19 + core 15.5.1) - ✅ twenty-front: typecheck; `graphql:generate` with codegen cli 5 produces **byte-identical** output (no generated-file changes in this PR) - ✅ `yarn install --immutable` clean ## Notes - The large `yarn.lock` diff is expected: major bumps to codegen (3→5), zapier-cli (15→19), and vite-plugin-dts (3→4) cascade through dev-tree transitives (net −1244 lines after dedup). - `zapier-platform-core` (runtime) intentionally left at 15.5.1 — only the CLI (dev tool) carried the vulnerable minimatch; `zapier validate` flags only a non-blocking "consider upgrading core" suggestion. - codegen plugins (`typescript`/`typescript-operations`) left at v3: they run fine under cli 5 and produce identical output, so the minimal change is just the cli bump. |
||
|
|
06d68f665d |
fix(auth): additional workspace and identity validation in auth flows (#21347)
## What Adds validation across three auth flows so that a session or reset link is consistently scoped to a workspace the authenticated principal belongs to, and to a verified identity. - **Access token** (`jwt.auth.strategy.ts`): when resolving the request's user context, the token's `userWorkspaceId` must belong to the token's `workspaceId` — the same check the application-token path already performs. - **OIDC** (`oidc.auth.strategy.ts`): reject sign-in when the identity provider explicitly reports `email_verified: false`. - **Password reset** (`reset-password.service.ts`): a supplied `workspaceId` is only used when the user is a member of it; otherwise it falls back to the user's own first password-auth-enabled workspace. ## Tests - `jwt.auth.strategy.spec.ts`: rejects an access token whose user workspace belongs to a different workspace than the token; existing mocks updated to carry the cached `workspaceId`. - `oidc.auth.strategy.spec.ts` (new): rejects unverified email; accepts verified and absent-claim cases. - `reset-password.service.spec.ts`: falls back when the supplied `workspaceId` is not one the user belongs to. `tsgo`, `oxlint` and `oxfmt` all clean on the changed files. |
||
|
|
24839d044a |
fix(server): repair server typecheck broken by isCustom deprecation (#21376)
## What Repairs `server-lint-typecheck`, which is **currently red on `main`**. After #21228 retyped `FlatObjectMetadata.isCustom` as `WasRemovedInUpgrade<boolean> | undefined`, the spec added in #21311 still passed `flatObjectMetadata.isCustom` to `computeTableName(nameSingular, isCustom: boolean)`: ``` graphql-query-order-group-by.parser.spec.ts(83,5): error TS2345: Argument of type 'WasRemovedInUpgrade<boolean> | undefined' is not assignable to parameter of type 'boolean'. ``` Both PRs merged via stale bases, and `server-lint-typecheck` only runs on PRs (not `main` pushes), so the regression landed undetected — the next PR to touch anything server-wide surfaces it. ## Fix Compute the expected physical table name with **`computeObjectTargetTable`** — the production helper that derives custom-ness from the application (`applicationUniversalIdentifier !== TWENTY_STANDARD_APPLICATION`), which is exactly the pattern the `isCustom` deprecation steers callers toward. This stops reading the deprecated field and won't break again when it's removed. One-line change in a single test file; behaviour is unchanged (custom object → `_`-prefixed physical table). ## Verification - `nx typecheck twenty-server` ✅ (was failing on `main`, now passes) - The spec runs green (3/3) - `nx lint:diff-with-main twenty-server` ✅ (lint + format) |
||
|
|
bd084afc11 |
security: force shell-quote >= 1.8.4 (GHSA-w7jw-789q-3m8p, critical) (#21372)
## What `shell-quote <= 1.8.3` is affected by [GHSA-w7jw-789q-3m8p](https://github.com/advisories/GHSA-w7jw-789q-3m8p) / CVE-2026-9277 (**critical**): `quote()` backslash-escapes `.op` characters with `/(.)/g`, which doesn't match line terminators (`\n`, `\r`, U+2028/2029). A line terminator in an object token's `.op` value passes through unescaped, and POSIX shells treat a literal `\n` as a command separator — enabling shell command injection in callers that pass attacker-influenced object tokens to `quote()`. First patched in **1.8.4**. This is Dependabot alert #1434 on the root `yarn.lock`. ## How The root lockfile resolved two vulnerable versions: - `1.8.1` — via the `^1.6.1` / `^1.7.3` / `^1.8.1` ranges - `1.8.3` — **hard-pinned** by `concurrently@9.2.1` (used in `twenty-companion`) `yarn up -R shell-quote` only re-resolves the ranged dependents; the exact `1.8.3` pin from `concurrently` stays. So I added a `shell-quote: "^1.8.4"` entry to root `resolutions`, matching the existing `tmp` / `chokidar` / `tar` security overrides. Every consumer now resolves to the patched `1.8.4`. ## Scope - `package.json`: +1 resolution line. - `yarn.lock`: two vulnerable entries collapse to a single `shell-quote@1.8.4`. - `1.8.4` is a semver-compatible patch over `1.8.3`; latest `concurrently` (10.x) already depends on `1.8.4`. - Verified no `shell-quote <= 1.8.3` remains in any lockfile across the repo. |
||
|
|
e51efef7c8 |
security(apps): bump twenty-sdk to 2.10.1 for the 3 remaining pre-2.0 apps (tmp, undici) (#21374)
## Summary Completes the follow-up flagged in #21344, which deliberately deferred the **three apps pinning a pre-2.0 `twenty-sdk`** (a major jump that needed per-app validation). These were the last `twenty-apps/*` lockfiles still carrying the `tmp` + `undici` Dependabot clusters: | App | SDK before | SDK after | |---|---|---| | `examples/hello-world` | `0.9.0` | `2.10.1` | | `internal/call-recording` | `0.6.3-alpha` | `2.10.1` | | `internal/self-hosting` | `1.22.0-canary.6` | `2.10.1` | Bumping to `twenty-sdk@2.10.1` drops the two vulnerable transitive deps these apps still inherited (via `inquirer ^10 → external-editor`, and `@genql/cli`): | Vuln dep | Advisory | Source | |---|---|---| | `tmp@0.0.33` | [GHSA-ph9p-34f9-6g65](https://github.com/advisories/GHSA-ph9p-34f9-6g65) / CVE-2026-44705 (path traversal) | `inquirer ^10 → external-editor` | | `undici@5.29.0` | [GHSA-vrm6-8vpv-qv8q](https://github.com/advisories/GHSA-vrm6-8vpv-qv8q) / CVE-2026-1526 (websocket OOM) | `@genql/cli` | ## Changes - Bump `twenty-sdk` (and `twenty-client-sdk` where pinned) to `2.10.1` in all 3 apps + regenerate each lockfile. - `hello-world` and `self-hosting` migrate transparently (typecheck clean). - `internal/call-recording` needed source changes for the 2.x API: - `twenty-sdk/clients` → `twenty-client-sdk/core` + `twenty-client-sdk/metadata` (5 files); added `twenty-client-sdk` dependency. - `defineRole` `permissionFlags` → `permissionFlagUniversalIdentifiers` (`SystemPermissionFlag`) — real runtime fix (old key is silently ignored in 2.x). ## Verification Per-app after regen: **`tmp@0.0.33` = 0**, **`undici@5` = 0** in every lockfile; `oxlint` passes with **0 errors**. Root `yarn.lock` untouched; all other undici in the repo is already ≥ patched (`6.26.0` / `7.24.8`). |
||
|
|
123db9e3be |
security: bump vite to 7.3.5 in twenty-apps lockfiles (GHSA-v2wj-q39q-566r) (#21371)
## 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. |
||
|
|
cf70565976 |
feat(twenty-server): allow shouldHideEmptyGroups in app view manifest (#21370)
## 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 |
||
|
|
834541da6d |
security: bump path-to-regexp and defu to patched versions (lockfile refresh) (#21369)
## 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 |
||
|
|
7894ae39f0 |
fix(front): reject backslash paths in isValidReturnToPath (open-redirect hardening) (#21287)
## 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>
|
||
|
|
c5e95c6649 |
Enrich app:add field relation and morph relations (#21368)
# Before <img width="833" height="358" alt="image" src="https://github.com/user-attachments/assets/e006bc5b-79e8-449f-beba-eb75393a9c60" /> # After <img width="1015" height="383" alt="image" src="https://github.com/user-attachments/assets/586f19df-e43b-45bc-9b26-16573108e750" /> <img width="1061" height="413" alt="image" src="https://github.com/user-attachments/assets/962635fa-6cdf-4aef-a1ea-cd129c5ff656" /> |
||
|
|
9c66975520 |
isCustom deprecation for Objects and Fields (#21228)
## 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
|
||
|
|
7530775dc1 |
fix(billing) - Suspend workspace at trial period end if cancelation is planned (#21363)
https://twenty-v7.sentry.io/issues/6900026484/events/e5a9e11fe0464df296d8beeabcf0f538/?end=2026-06-09T05%3A24%3A00&project=4507072499810304&query=%21twenty.workspace.id%3A%EF%80%8DContains%EF%80%8Dad6668da-9f4c-4618-9424-9092ea87db26%20%21twenty.workspace.id%3A%EF%80%8DContains%EF%80%8D86b352c5-ffff-4a4c-8660-b1b2bfb7f57d&referrer=next-event&start=2026-05-23T06%3A02%3A00 |
||
|
|
02aa086866 |
fix(twenty-front): new layout fast-follows (#21360)
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. |
||
|
|
c27c8c88b0 |
Fix various graphs bugs (#21311)
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"
/>
|
||
|
|
137fe45cf6 |
Deprecate dummy enterprise key 2/2 (#21328)
Following [1/2](https://github.com/twentyhq/twenty/pull/20890) Now that all usages of hasValidEnterpriseKey has been removed in prod and deployed, we can safely remove it altogether. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
bda6fcfec9 |
feat(website): book an intro call after partner application (#21343)
## 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
|
||
|
|
ba4ac6b70e |
fix(twenty-front): restore top-bar-title testid to unbreak merge queue (#21367)
## 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). |
||
|
|
fcaf2b4d9b |
chore(twenty-server): temporary instrumentation for app-install 504 (#21365)
## 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. |
||
|
|
7606dd75a8 |
Fix: pinned command-menu actions run with empty selection (#21366)
## 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. |
||
|
|
92502efacc |
Restore content-box sizing for components broken by the global border-box reset (#21361)
Since [#21315](https://github.com/twentyhq/twenty/pull/21315), the new twenty-ui's global border-box reset applies app-wide, shrinking legacy content-box components: most visibly, off-center checkboxes. [#21349](https://github.com/twentyhq/twenty/pull/21349) missed a few; this adds box-sizing: content-box to Checkbox, Radio, ColorSample, MenuItemHotKeys, Tag, ImageInput, and OnboardingModalCircularIcon. |
||
|
|
beaa4e636c |
Remove default command (#21357)
fix conflict with not existing commands |
||
|
|
e9086b49f6 |
Increase logicFunctionQueue worker concurrency to 10 (#21364)
## 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 |
||
|
|
ed7ff4a84b |
chore: bump version to 2.12.0 (#21358)
## 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> |
||
|
|
20ccc52424 |
fix(auth): align workspace resolution across SSO and OTP sign-in flows (#21346)
## 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>
|
||
|
|
55cbd3bfbf |
perf(ai): lazy-load agent chat runtime so it doesn't fetch/diff threads until opened (#21331)
## 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. |
||
|
|
4305a7dc84 |
fix(twenty-client-sdk): make genql codegen formatter prettier-3 compatible (fixes app-sync server crash) (#21354)
## 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.
|
||
|
|
1d81bdbb22 |
Fix breaking change with dry run input (#21356)
- fix dry-run input parameter breaking change - remove duplicated log |
||
|
|
441fe73be5 |
chore: sync AI model catalog from models.dev (#21353)
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> |
||
|
|
8a3e6e645a |
fix(ui): restore content-box sizing for components broken by the global border-box reset (#21349)
## 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`. |
||
|
|
bfefcd3755 |
feat(twenty-front): generalize the page primary/secondary bars (flat redesign) (#21308)
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. |
||
|
|
a48c158a66 |
security(apps): bump twenty-sdk to 2.10.1 across twenty-apps (tmp, undici) (#21344)
## 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`. |
||
|
|
7823216568 |
i18n - docs translations (#21342)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
1e309369bc |
chore(deps): upgrade tar to v7, evict vulnerable tar@6.2.1 (CVE-2026-24842) (#21341)
## 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. |
||
|
|
37b986aa4b |
security: vendor @genql/cli codegen to drop undici/native-fetch (#21339)
## 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. |
||
|
|
a0fb157899 |
fix(sdk): bump inquirer ^10 -> ^14 to drop external-editor/tmp from apps (#21340)
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.) |
||
|
|
bf75ab8982 |
feat(twenty-partners): notify Discord on new partner application (#21313)
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. |
||
|
|
822beb6a86 |
chore: force tmp >= 0.2.7 for root dev tooling (nx/zapier) (#21338)
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. |
||
|
|
5a2523f533 |
chore(apps): bump vitest to 3.2.6 in twenty-apps projects (GHSA-5xrq-8626-4rwp) (#21336)
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.
|
||
|
|
44c4c27c76 |
i18n - docs translations (#21337)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
356cec5f24 |
security: drop unused @genql/runtime dependency (#21334)
## 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. |
||
|
|
434f5cbcd2 |
chore(server): bump @nestjs to 11.1.24 + serve-static 5.0.5 to clear CVEs (#21333)
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. |
||
|
|
bc3036c5da |
chore: remove accidentally committed screenshots (#21332)
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> |
||
|
|
27aea728df |
2474 add a utils to perform route trigger logic function requests in front components (#21330)
- exports `RestApiClient` from 'twenty-client-sdk/rest';` - documents the rest client |
||
|
|
296c202be4 |
messaging: Microsoft driver migrate p-limit to native batching (#21132)
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 |
||
|
|
772b807490 |
fix: prevent deletion of standard fields via API (#21319)
## 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) |
||
|
|
cd13457a4a |
fix(twenty-front): match loading skeleton menu width to the nav drawer (#21278)
## 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> |
||
|
|
c596a5e342 |
Rename twenty-ui to twenty-ui-deprecated and twenty-new-ui to twenty-ui to prepare package release (#21315)
## 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`. |
||
|
|
a91e737e69 |
fix(docker): bump Node 24.16.0 (OpenSSL fix), strip unused cruft, dedupe node-forge (#21322)
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.) |