Files
twenty/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutTabCreate.ts
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

46 lines
1.4 KiB
TypeScript

import { useRecoilCallback } from 'recoil';
import { v4 as uuidv4 } from 'uuid';
import { pageLayoutCurrentLayoutsState } from '../states/pageLayoutCurrentLayoutsState';
import { pageLayoutDraftState } from '../states/pageLayoutDraftState';
import { type PageLayoutTab } from '../states/savedPageLayoutsState';
import { createEmptyTabLayout } from '../utils/createEmptyTabLayout';
export const usePageLayoutTabCreate = () => {
const handleCreateTab = useRecoilCallback(
({ snapshot, set }) =>
(title?: string): string => {
const pageLayoutDraft = snapshot
.getLoadable(pageLayoutDraftState)
.getValue();
const newTabId = `tab-${uuidv4()}`;
const newTab: PageLayoutTab = {
id: newTabId,
title: title || `Tab ${pageLayoutDraft.tabs.length + 1}`,
position: pageLayoutDraft.tabs.length,
pageLayoutId: '',
widgets: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
deletedAt: null,
};
const updatedTabs = [...pageLayoutDraft.tabs, newTab];
set(pageLayoutDraftState, (prev) => ({
...prev,
tabs: updatedTabs,
}));
set(pageLayoutCurrentLayoutsState, (prev) =>
createEmptyTabLayout(prev, newTabId),
);
return newTabId;
},
[],
);
return { handleCreateTab };
};