Files
twenty/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx
T
Félix Malfait cebcf4f1f5 Prevent csv export injections (#14347)
**Small Security Issue:** CSV exports were vulnerable to formula
injection attacks when users entered values starting with =, +, -, or @.
(only happens if a logged-in user injects corrupted data)

Solution:
- Added ZWJ (Zero-Width Joiner) protection that prefixes dangerous
values with invisible Unicode character
- This is the best way to preserve original data while preventing Excel
from executing formulas
- Added import cleanup to restore original values when re-importing
 
Changes:
- New sanitizeValueForCSVExport() function for security
- Updated all CSV export paths to use both security + formatting
functions
- Added comprehensive tests covering attack vectors and international
characters
- Also added cursor rules for better code consistency

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2025-09-08 17:57:46 +02:00

645 lines
18 KiB
TypeScript

import { lazy, Suspense } from 'react';
import { Route, Routes } from 'react-router-dom';
import { SettingsProtectedRouteWrapper } from '@/settings/components/SettingsProtectedRouteWrapper';
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
import { SettingsPath } from '@/types/SettingsPath';
import { PermissionFlagType } from '~/generated/graphql';
const SettingsGraphQLPlayground = lazy(() =>
import(
'~/pages/settings/developers/playground/SettingsGraphQLPlayground'
).then((module) => ({
default: module.SettingsGraphQLPlayground,
})),
);
const SettingsRestPlayground = lazy(() =>
import('~/pages/settings/developers/playground/SettingsRestPlayground').then(
(module) => ({
default: module.SettingsRestPlayground,
}),
),
);
const SettingsAccountsCalendars = lazy(() =>
import('~/pages/settings/accounts/SettingsAccountsCalendars').then(
(module) => ({
default: module.SettingsAccountsCalendars,
}),
),
);
const SettingsAccountsEmails = lazy(() =>
import('~/pages/settings/accounts/SettingsAccountsEmails').then((module) => ({
default: module.SettingsAccountsEmails,
})),
);
const SettingsNewAccount = lazy(() =>
import('~/pages/settings/accounts/SettingsNewAccount').then((module) => ({
default: module.SettingsNewAccount,
})),
);
const SettingsNewObject = lazy(() =>
import('~/pages/settings/data-model/SettingsNewObject').then((module) => ({
default: module.SettingsNewObject,
})),
);
const SettingsNewImapSmtpCaldavConnection = lazy(() =>
import(
'@/settings/accounts/components/SettingsAccountsNewImapSmtpCaldavConnection'
).then((module) => ({
default: module.SettingsAccountsNewImapSmtpCaldavConnection,
})),
);
const SettingsEditImapSmtpCaldavConnection = lazy(() =>
import(
'@/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection'
).then((module) => ({
default: module.SettingsAccountsEditImapSmtpCaldavConnection,
})),
);
const SettingsObjectDetailPage = lazy(() =>
import('~/pages/settings/data-model/SettingsObjectDetailPage').then(
(module) => ({
default: module.SettingsObjectDetailPage,
}),
),
);
const SettingsObjectOverview = lazy(() =>
import('~/pages/settings/data-model/SettingsObjectOverview').then(
(module) => ({
default: module.SettingsObjectOverview,
}),
),
);
const SettingsDevelopersApiKeyDetail = lazy(() =>
import(
'~/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail'
).then((module) => ({
default: module.SettingsDevelopersApiKeyDetail,
})),
);
const SettingsDevelopersApiKeysNew = lazy(() =>
import(
'~/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew'
).then((module) => ({
default: module.SettingsDevelopersApiKeysNew,
})),
);
const SettingsServerlessFunctions = lazy(() =>
import(
'~/pages/settings/serverless-functions/SettingsServerlessFunctions'
).then((module) => ({ default: module.SettingsServerlessFunctions })),
);
const SettingsServerlessFunctionDetail = lazy(() =>
import(
'~/pages/settings/serverless-functions/SettingsServerlessFunctionDetail'
).then((module) => ({
default: module.SettingsServerlessFunctionDetail,
})),
);
const SettingsServerlessFunctionsNew = lazy(() =>
import(
'~/pages/settings/serverless-functions/SettingsServerlessFunctionsNew'
).then((module) => ({
default: module.SettingsServerlessFunctionsNew,
})),
);
const SettingsWorkspace = lazy(() =>
import('~/pages/settings/SettingsWorkspace').then((module) => ({
default: module.SettingsWorkspace,
})),
);
const SettingsDomains = lazy(() =>
import('~/pages/settings/domains/SettingsDomains').then((module) => ({
default: module.SettingsDomains,
})),
);
const SettingsDomain = lazy(() =>
import('~/pages/settings/domains/SettingsDomain').then((module) => ({
default: module.SettingsDomain,
})),
);
const SettingsApiWebhooks = lazy(() =>
import('~/pages/settings/workspace/SettingsApiWebhooks').then((module) => ({
default: module.SettingsApiWebhooks,
})),
);
const SettingsAI = lazy(() =>
import('~/pages/settings/ai/SettingsAI').then((module) => ({
default: module.SettingsAI,
})),
);
const SettingsAgentForm = lazy(() =>
import('~/pages/settings/ai/SettingsAgentForm').then((module) => ({
default: module.SettingsAgentForm,
})),
);
const SettingsWorkspaceMembers = lazy(() =>
import('~/pages/settings/SettingsWorkspaceMembers').then((module) => ({
default: module.SettingsWorkspaceMembers,
})),
);
const SettingsProfile = lazy(() =>
import('~/pages/settings/SettingsProfile').then((module) => ({
default: module.SettingsProfile,
})),
);
const SettingsTwoFactorAuthenticationMethod = lazy(() =>
import('~/pages/settings/SettingsTwoFactorAuthenticationMethod').then(
(module) => ({
default: module.SettingsTwoFactorAuthenticationMethod,
}),
),
);
const SettingsExperience = lazy(() =>
import(
'~/pages/settings/profile/appearance/components/SettingsExperience'
).then((module) => ({
default: module.SettingsExperience,
})),
);
const SettingsAccounts = lazy(() =>
import('~/pages/settings/accounts/SettingsAccounts').then((module) => ({
default: module.SettingsAccounts,
})),
);
const SettingsBilling = lazy(() =>
import('~/pages/settings/SettingsBilling').then((module) => ({
default: module.SettingsBilling,
})),
);
const SettingsIntegrations = lazy(() =>
import('~/pages/settings/integrations/SettingsIntegrations').then(
(module) => ({
default: module.SettingsIntegrations,
}),
),
);
const SettingsObjects = lazy(() =>
import('~/pages/settings/data-model/SettingsObjects').then((module) => ({
default: module.SettingsObjects,
})),
);
const SettingsDevelopersWebhookNew = lazy(() =>
import(
'~/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookNew'
).then((module) => ({
default: module.SettingsDevelopersWebhookNew,
})),
);
const SettingsDevelopersWebhookDetail = lazy(() =>
import(
'~/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail'
).then((module) => ({
default: module.SettingsDevelopersWebhookDetail,
})),
);
const SettingsIntegrationDatabase = lazy(() =>
import('~/pages/settings/integrations/SettingsIntegrationDatabase').then(
(module) => ({
default: module.SettingsIntegrationDatabase,
}),
),
);
const SettingsIntegrationMCP = lazy(() =>
import('~/pages/settings/integrations/SettingsIntegrationMCPPage').then(
(module) => ({
default: module.SettingsIntegrationMCPPage,
}),
),
);
const SettingsIntegrationNewDatabaseConnection = lazy(() =>
import(
'~/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection'
).then((module) => ({
default: module.SettingsIntegrationNewDatabaseConnection,
})),
);
const SettingsIntegrationEditDatabaseConnection = lazy(() =>
import(
'~/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection'
).then((module) => ({
default: module.SettingsIntegrationEditDatabaseConnection,
})),
);
const SettingsIntegrationShowDatabaseConnection = lazy(() =>
import(
'~/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection'
).then((module) => ({
default: module.SettingsIntegrationShowDatabaseConnection,
})),
);
const SettingsObjectNewFieldSelect = lazy(() =>
import(
'~/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect'
).then((module) => ({
default: module.SettingsObjectNewFieldSelect,
})),
);
const SettingsObjectNewFieldConfigure = lazy(() =>
import(
'~/pages/settings/data-model/new-field/SettingsObjectNewFieldConfigure'
).then((module) => ({
default: module.SettingsObjectNewFieldConfigure,
})),
);
const SettingsObjectFieldEdit = lazy(() =>
import('~/pages/settings/data-model/SettingsObjectFieldEdit').then(
(module) => ({
default: module.SettingsObjectFieldEdit,
}),
),
);
const SettingsPageLayouts = lazy(() =>
import('~/pages/settings/page-layout/SettingsPageLayouts').then((module) => ({
default: module.SettingsPageLayouts,
})),
);
const SettingsPageLayoutEdit = lazy(() =>
import('~/pages/settings/page-layout/SettingsPageLayoutEdit').then(
(module) => ({
default: module.SettingsPageLayoutEdit,
}),
),
);
const SettingsSecurity = lazy(() =>
import('~/pages/settings/security/SettingsSecurity').then((module) => ({
default: module.SettingsSecurity,
})),
);
const SettingsSecuritySSOIdentifyProvider = lazy(() =>
import('~/pages/settings/security/SettingsSecuritySSOIdentifyProvider').then(
(module) => ({
default: module.SettingsSecuritySSOIdentifyProvider,
}),
),
);
const SettingsSecurityApprovedAccessDomain = lazy(() =>
import('~/pages/settings/security/SettingsSecurityApprovedAccessDomain').then(
(module) => ({
default: module.SettingsSecurityApprovedAccessDomain,
}),
),
);
const SettingsAdmin = lazy(() =>
import('~/pages/settings/admin-panel/SettingsAdmin').then((module) => ({
default: module.SettingsAdmin,
})),
);
const SettingsAdminIndicatorHealthStatus = lazy(() =>
import(
'~/pages/settings/admin-panel/SettingsAdminIndicatorHealthStatus'
).then((module) => ({
default: module.SettingsAdminIndicatorHealthStatus,
})),
);
const SettingsAdminConfigVariableDetails = lazy(() =>
import(
'~/pages/settings/admin-panel/SettingsAdminConfigVariableDetails'
).then((module) => ({
default: module.SettingsAdminConfigVariableDetails,
})),
);
const SettingsReleases = lazy(() =>
import('~/pages/settings/releases/SettingsReleases').then((module) => ({
default: module.SettingsReleases,
})),
);
const SettingsRoles = lazy(() =>
import('~/pages/settings/roles/SettingsRoles').then((module) => ({
default: module.SettingsRoles,
})),
);
const SettingsRoleCreate = lazy(() =>
import('~/pages/settings/roles/SettingsRoleCreate').then((module) => ({
default: module.SettingsRoleCreate,
})),
);
const SettingsRoleEdit = lazy(() =>
import('~/pages/settings/roles/SettingsRoleEdit').then((module) => ({
default: module.SettingsRoleEdit,
})),
);
const SettingsRoleObjectLevel = lazy(() =>
import('~/pages/settings/roles/SettingsRoleObjectLevel').then((module) => ({
default: module.SettingsRoleObjectLevel,
})),
);
const SettingsRoleAddObjectLevel = lazy(() =>
import('~/pages/settings/roles/SettingsRoleAddObjectLevel').then(
(module) => ({
default: module.SettingsRoleAddObjectLevel,
}),
),
);
type SettingsRoutesProps = {
isFunctionSettingsEnabled?: boolean;
isAdminPageEnabled?: boolean;
isPageLayoutFeatureFlagEnabled?: boolean;
};
export const SettingsRoutes = ({
isFunctionSettingsEnabled,
isAdminPageEnabled,
isPageLayoutFeatureFlagEnabled,
}: SettingsRoutesProps) => (
<Suspense fallback={<SettingsSkeletonLoader />}>
<Routes>
<Route path={SettingsPath.ProfilePage} element={<SettingsProfile />} />
<Route
path={SettingsPath.TwoFactorAuthenticationStrategyConfig}
element={<SettingsTwoFactorAuthenticationMethod />}
/>
<Route path={SettingsPath.Experience} element={<SettingsExperience />} />
<Route path={SettingsPath.Accounts} element={<SettingsAccounts />} />
<Route path={SettingsPath.NewAccount} element={<SettingsNewAccount />} />
<Route
path={SettingsPath.AccountsCalendars}
element={<SettingsAccountsCalendars />}
/>
<Route
path={SettingsPath.AccountsEmails}
element={<SettingsAccountsEmails />}
/>
<Route
path={SettingsPath.NewImapSmtpCaldavConnection}
element={<SettingsNewImapSmtpCaldavConnection />}
/>
<Route
path={SettingsPath.EditImapSmtpCaldavConnection}
element={<SettingsEditImapSmtpCaldavConnection />}
/>
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={PermissionFlagType.WORKSPACE}
/>
}
>
<Route path={SettingsPath.Workspace} element={<SettingsWorkspace />} />
<Route path={SettingsPath.Domains} element={<SettingsDomains />} />
<Route
path={SettingsPath.ApiWebhooks}
element={<SettingsApiWebhooks />}
/>
<Route path={SettingsPath.AI} element={<SettingsAI />} />
<Route
path={SettingsPath.AINewAgent}
element={<SettingsAgentForm mode="create" />}
/>
<Route
path={SettingsPath.AIAgentDetail}
element={<SettingsAgentForm mode="edit" />}
/>
<Route path={SettingsPath.Billing} element={<SettingsBilling />} />
<Route path={SettingsPath.Domain} element={<SettingsDomain />} />
</Route>
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={PermissionFlagType.WORKSPACE_MEMBERS}
/>
}
>
<Route
path={SettingsPath.WorkspaceMembersPage}
element={<SettingsWorkspaceMembers />}
/>
</Route>
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={PermissionFlagType.DATA_MODEL}
/>
}
>
<Route path={SettingsPath.Objects} element={<SettingsObjects />} />
<Route
path={SettingsPath.ObjectOverview}
element={<SettingsObjectOverview />}
/>
<Route
path={SettingsPath.ObjectDetail}
element={<SettingsObjectDetailPage />}
/>
<Route path={SettingsPath.NewObject} element={<SettingsNewObject />} />
<Route
path={SettingsPath.ObjectNewFieldSelect}
element={<SettingsObjectNewFieldSelect />}
/>
<Route
path={SettingsPath.ObjectNewFieldConfigure}
element={<SettingsObjectNewFieldConfigure />}
/>
<Route
path={SettingsPath.ObjectFieldEdit}
element={<SettingsObjectFieldEdit />}
/>
</Route>
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={PermissionFlagType.ROLES}
/>
}
>
<Route path={SettingsPath.Roles} element={<SettingsRoles />} />
<Route path={SettingsPath.RoleDetail} element={<SettingsRoleEdit />} />
<Route
path={SettingsPath.RoleCreate}
element={<SettingsRoleCreate />}
/>
<Route
path={SettingsPath.RoleObjectLevel}
element={<SettingsRoleObjectLevel />}
/>
<Route
path={SettingsPath.RoleAddObjectLevel}
element={<SettingsRoleAddObjectLevel />}
/>
</Route>
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={PermissionFlagType.API_KEYS_AND_WEBHOOKS}
/>
}
>
<Route
path={`${SettingsPath.GraphQLPlayground}`}
element={<SettingsGraphQLPlayground />}
/>
<Route
path={`${SettingsPath.RestPlayground}/*`}
element={<SettingsRestPlayground />}
/>
<Route
path={SettingsPath.NewApiKey}
element={<SettingsDevelopersApiKeysNew />}
/>
<Route
path={SettingsPath.ApiKeyDetail}
element={<SettingsDevelopersApiKeyDetail />}
/>
<Route
path={SettingsPath.NewWebhook}
element={<SettingsDevelopersWebhookNew />}
/>
<Route
path={SettingsPath.WebhookDetail}
element={<SettingsDevelopersWebhookDetail />}
/>
<Route
path={SettingsPath.Integrations}
element={<SettingsIntegrations />}
/>
<Route
path={SettingsPath.IntegrationDatabase}
element={<SettingsIntegrationDatabase />}
/>
<Route
path={SettingsPath.IntegrationNewDatabaseConnection}
element={<SettingsIntegrationNewDatabaseConnection />}
/>
<Route
path={SettingsPath.IntegrationEditDatabaseConnection}
element={<SettingsIntegrationEditDatabaseConnection />}
/>
<Route
path={SettingsPath.IntegrationDatabaseConnection}
element={<SettingsIntegrationShowDatabaseConnection />}
/>
<Route
path={SettingsPath.IntegrationMCP}
element={<SettingsIntegrationMCP />}
/>
</Route>
{isFunctionSettingsEnabled && (
<>
<Route
path={SettingsPath.ServerlessFunctions}
element={<SettingsServerlessFunctions />}
/>
<Route
path={SettingsPath.NewServerlessFunction}
element={<SettingsServerlessFunctionsNew />}
/>
<Route
path={SettingsPath.ServerlessFunctionDetail}
element={<SettingsServerlessFunctionDetail />}
/>
</>
)}
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={PermissionFlagType.SECURITY}
/>
}
>
<Route path={SettingsPath.Security} element={<SettingsSecurity />} />
<Route
path={SettingsPath.NewSSOIdentityProvider}
element={<SettingsSecuritySSOIdentifyProvider />}
/>
<Route
path={SettingsPath.NewApprovedAccessDomain}
element={<SettingsSecurityApprovedAccessDomain />}
/>
</Route>
{isAdminPageEnabled && (
<>
<Route path={SettingsPath.AdminPanel} element={<SettingsAdmin />} />
<Route
path={SettingsPath.AdminPanelIndicatorHealthStatus}
element={<SettingsAdminIndicatorHealthStatus />}
/>
<Route
path={SettingsPath.AdminPanelConfigVariableDetails}
element={<SettingsAdminConfigVariableDetails />}
/>
</>
)}
{isPageLayoutFeatureFlagEnabled && (
<>
<Route
path={SettingsPath.PageLayout}
element={<SettingsPageLayouts />}
/>
<Route
path={SettingsPath.PageLayoutNew}
element={<SettingsPageLayoutEdit />}
/>
<Route
path={SettingsPath.PageLayoutEdit}
element={<SettingsPageLayoutEdit />}
/>
</>
)}
<Route
element={
<SettingsProtectedRouteWrapper
settingsPermission={PermissionFlagType.WORKSPACE}
/>
}
>
<Route path={SettingsPath.Releases} element={<SettingsReleases />} />
</Route>
</Routes>
</Suspense>
);