Files
twenty/packages/twenty-server
martmull cc21160d83 fix(server): scope server-route target dispatch to the resolver's application (#22101)
## Summary

Security follow-up to #22002 (server-exposed logic functions). That PR's
`ServerRouteTriggerService` resolved the **target** logic function by
`(universalIdentifier, workspaceId)` alone, with no application scoping:

```ts
// before
const logicFunction = await this.logicFunctionRepository.findOne({
  where: { universalIdentifier, workspaceId },
});
```

Both values come straight from the resolver's return value. Because the
only gate was "a function with that UID exists in that workspace", a
resolver (owner-workspace code) could dispatch to a logic function
belonging to a **different application**, or to a workspace where its
own application is **not installed**, and read the target's return value
back in the HTTP response
(`buildRouteTriggerResponse(targetResult.data)`) — a cross-tenant /
cross-application isolation break.

The implementation this replaced (the deleted
`server-webhook-trigger.service.ts`) enforced both checks: the app had
to be installed in the target workspace, and the target function was
scoped by `applicationId`. This PR restores that guarantee.

## Changes

- **Scope the target dispatch to the resolver's
`applicationRegistration`.** `handle()` captures
`resolver.application.applicationRegistration.id` and threads it into
the target `findOne` as `application: { applicationRegistrationId }`
(joining the `application` relation). The target must belong to the same
registration — which also guarantees the application is installed in the
resolved workspace (no installed copy → no matching row). The resolver
lookup itself is unchanged.
- **Stop leaking raw internal error messages.** The `runFunction` catch
block logged the raw executor/`Error.message` *and* returned it to the
(unauthenticated) caller. It now logs the detail server-side and returns
a generic, per-code message.
- **Tests**: fixtures carry an `applicationRegistration.id`; new cases
assert the target lookup is scoped to the resolver's registration, that
a resolver not linked to a registration is rejected, and that a platform
error returns the generic message instead of the raw internal text.

Feature remains gated behind `IS_SERVER_LOGIC_FUNCTION_ENABLED` (default
off).

## Test plan
- [ ] `npx jest server-route-trigger` (verifying locally; environment
dependency install was flaky)
- [ ] `npx nx typecheck twenty-server`
- [ ] `npx nx lint:diff-with-main twenty-server`

https://claude.ai/code/session_014TNdRvQjjR8wN6MLTJ7rTE

---
_Generated by [Claude
Code](https://claude.ai/code/session_014TNdRvQjjR8wN6MLTJ7rTE)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22101?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-24 16:22:01 +00:00
..