mirror of
https://github.com/suitenumerique/docs.git
synced 2026-08-17 21:25:43 +02:00
🔒️(frontend) sanitize color during collaboration
To improve security we sanitize the color used for collaboration presence to ensure it's a valid hex color. If the color is not valid, we generate a random color instead. This prevents potential issues with invalid color values being used in the UI.
This commit is contained in:
@@ -18,6 +18,10 @@ and this project adheres to
|
||||
- 🐛(frontend) Block menu doesn't stay open on 1st line #2229
|
||||
- 🐛(frontend) The "+" on the first line of a new doc doesn't work #2229
|
||||
|
||||
### Security
|
||||
|
||||
- 🔒️(frontend) sanitize color during collaboration#2270
|
||||
|
||||
|
||||
## [v5.0.0] - 2026-04-08
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { sanitizeColor } from '../utils';
|
||||
|
||||
const HEX_COLOR_RE = /^#[0-9a-fA-F]{6}$/;
|
||||
|
||||
describe('sanitizeColor', () => {
|
||||
it('accepts valid 6-digit hex colors', () => {
|
||||
expect(sanitizeColor('#1a2b3c')).toBe('#1a2b3c');
|
||||
expect(sanitizeColor('#AABBCC')).toBe('#AABBCC');
|
||||
expect(sanitizeColor('#000000')).toBe('#000000');
|
||||
expect(sanitizeColor('#ffffff')).toBe('#ffffff');
|
||||
});
|
||||
|
||||
it('rejects 3-digit hex colors and returns a valid random hex color', () => {
|
||||
expect(sanitizeColor('#abc')).toMatch(HEX_COLOR_RE);
|
||||
});
|
||||
|
||||
it('rejects named colors and returns a valid random hex color', () => {
|
||||
expect(sanitizeColor('red')).toMatch(HEX_COLOR_RE);
|
||||
expect(sanitizeColor('blue')).toMatch(HEX_COLOR_RE);
|
||||
});
|
||||
|
||||
it('rejects CSS injection attempts and returns a valid random hex color', () => {
|
||||
expect(sanitizeColor('red; behavior: expression(alert(1))')).toMatch(
|
||||
HEX_COLOR_RE,
|
||||
);
|
||||
expect(sanitizeColor('#fff; color: red')).toMatch(HEX_COLOR_RE);
|
||||
expect(sanitizeColor('javascript:alert(1)')).toMatch(HEX_COLOR_RE);
|
||||
});
|
||||
|
||||
it('rejects empty string and returns a valid random hex color', () => {
|
||||
expect(sanitizeColor('')).toMatch(HEX_COLOR_RE);
|
||||
});
|
||||
});
|
||||
+4
-3
@@ -36,7 +36,7 @@ import {
|
||||
import { useEditorStore } from '../stores';
|
||||
import { DocsEditorStyle } from '../styles';
|
||||
import { DocsBlockNoteEditor } from '../types';
|
||||
import { randomColor } from '../utils';
|
||||
import { randomColor, sanitizeColor } from '../utils';
|
||||
|
||||
import BlockNoteAI from './AI';
|
||||
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
|
||||
@@ -152,12 +152,13 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
*/
|
||||
renderCursor: (user: { color: string; name: string }) => {
|
||||
const cursorElement = document.createElement('span');
|
||||
const safeColor = sanitizeColor(user.color);
|
||||
|
||||
cursorElement.classList.add('collaboration-cursor-custom__base');
|
||||
const caretElement = document.createElement('span');
|
||||
caretElement.classList.add('collaboration-cursor-custom__caret');
|
||||
caretElement.setAttribute('spellcheck', `false`);
|
||||
caretElement.setAttribute('style', `background-color: ${user.color}`);
|
||||
caretElement.setAttribute('style', `background-color: ${safeColor}`);
|
||||
|
||||
if (showCursorLabels === 'always') {
|
||||
cursorElement.setAttribute('data-active', '');
|
||||
@@ -169,7 +170,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
labelElement.setAttribute('spellcheck', `false`);
|
||||
labelElement.setAttribute(
|
||||
'style',
|
||||
`background-color: ${user.color};border: 1px solid ${user.color};`,
|
||||
`background-color: ${safeColor};border: 1px solid ${safeColor};`,
|
||||
);
|
||||
labelElement.insertBefore(document.createTextNode(user.name), null);
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
const HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
|
||||
|
||||
export const sanitizeColor = (color: string): string => {
|
||||
return HEX_COLOR_REGEX.test(color) ? color : randomColor();
|
||||
};
|
||||
|
||||
export const randomColor = () => {
|
||||
const randomInt = (min: number, max: number) => {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
|
||||
Reference in New Issue
Block a user