From 794267b967d4d8a9e1ada336dce6346e13f25bf2 Mon Sep 17 00:00:00 2001 From: Landry Trebon Date: Wed, 28 Jan 2026 15:18:22 +0100 Subject: [PATCH 1/2] [frontend] Improve graph labels --- .../components/graph/utils/useGraphParser.ts | 6 +- .../opencti-front/src/utils/String.jsx | 26 +++++- .../opencti-front/src/utils/String.test.ts | 87 +++++++++++++++++++ .../opencti-front/src/utils/String.test.tsx | 25 ------ 4 files changed, 115 insertions(+), 29 deletions(-) create mode 100644 opencti-platform/opencti-front/src/utils/String.test.ts delete mode 100644 opencti-platform/opencti-front/src/utils/String.test.tsx diff --git a/opencti-platform/opencti-front/src/components/graph/utils/useGraphParser.ts b/opencti-platform/opencti-front/src/components/graph/utils/useGraphParser.ts index 2d381afc3135..a193bb0e3802 100644 --- a/opencti-platform/opencti-front/src/components/graph/utils/useGraphParser.ts +++ b/opencti-platform/opencti-front/src/components/graph/utils/useGraphParser.ts @@ -3,7 +3,7 @@ import { dateFormat, jsDate } from '../../../utils/Time'; import { isNone, useFormatter } from '../../i18n'; import { defaultDate, getMainRepresentative } from '../../../utils/defaultRepresentatives'; import type { OctiGraphPositions, GraphLink, GraphNode } from '../graph.types'; -import { EMPTY_VALUE, truncate } from '../../../utils/String'; +import { EMPTY_VALUE, truncate, sanitize } from '../../../utils/String'; import GRAPH_IMAGES from './graphImages'; import { itemColor } from '../../../utils/Colors'; @@ -175,7 +175,7 @@ const useGraphParser = () => { isObservable: !!data.observable_value, numberOfConnectedElement: numberOfConnectedElement ?? data.numberOfConnectedElement, ...getNodeImg(data), - name: getNodeName(data), + name: sanitize(getNodeName(data), true), label: getNodeLabel(data), markedBy: getMarkings(data), createdBy: getCreatedBy(data), @@ -198,7 +198,7 @@ const useGraphParser = () => { relationship_type: data.relationship_type, label: t_i18n(`relationship_${data.entity_type}`), markedBy: getMarkings(data), - name: getRelationshipName(data), + name: sanitize(getRelationshipName(data), true), createdBy: getCreatedBy(data), defaultDate: jsDate(defaultDate(data)), isNestedInferred: getIsNestedInferred(data), diff --git a/opencti-platform/opencti-front/src/utils/String.jsx b/opencti-platform/opencti-front/src/utils/String.jsx index 412e9c2a9dd0..04c28ef22867 100644 --- a/opencti-platform/opencti-front/src/utils/String.jsx +++ b/opencti-platform/opencti-front/src/utils/String.jsx @@ -1,5 +1,5 @@ import * as R from 'ramda'; -import React from 'react'; +import purify from 'dompurify'; import { Base64 } from 'js-base64'; import Tooltip from '@mui/material/Tooltip'; import { last } from 'ramda'; @@ -284,3 +284,27 @@ export const extractUrlsFromText = (text) => { return parts; }; + +/** + * Transform a potential unsecure html string into a secure one. + * + * @param data String to sanitize. + * @param escapeHtml If we want to keep html tags without removing them. + * @returns Sanitized string. + */ +export const sanitize = (data, escapeHtml = false) => { + const toSanitize = escapeHtml + ? data.replace(/&/g, '&').replace(//g, '>') + : data; + return purify.sanitize(toSanitize); +}; + +/** + * Check if a string is xss safe. + * + * @param data Determine is a string is xss safe or not. + * @returns True if the string is safe. + */ +export const isStringSafe = (data) => { + return data === purify.sanitize(data); +}; diff --git a/opencti-platform/opencti-front/src/utils/String.test.ts b/opencti-platform/opencti-front/src/utils/String.test.ts new file mode 100644 index 000000000000..bbe3f24791c9 --- /dev/null +++ b/opencti-platform/opencti-front/src/utils/String.test.ts @@ -0,0 +1,87 @@ +import { describe, it, expect } from 'vitest'; +import { displayEntityTypeForTranslation, translateDateInterval, isStringSafe, sanitize } from './String'; + +describe('String utils', () => { + describe('translateDateInterval', () => { + it('should translate a string interval in relative date phrase if possible', () => { + const t = (s: string) => s; + expect(() => translateDateInterval(['test'], t)).toThrowError(); + expect(() => translateDateInterval(['test', 'now'], t)).toThrowError(); + expect(() => translateDateInterval(['now', 'now'], t)).toThrowError(); + expect(() => translateDateInterval(['now-1d', 'now+1d'], t)).toThrowError(); + expect(() => translateDateInterval(['now-1d/d', 'now'], t)).toThrowError(); + expect(translateDateInterval(['now-1d', 'now'], t)).toEqual('Last 1 day'); + expect(translateDateInterval(['now-2H', 'now'], t)).toEqual('Last 2 hours'); + expect(translateDateInterval(['now-10y', 'now'], t)).toEqual('Last 10 years'); + }); + }); + + describe('displayEntityTypeForTranslation', () => { + it('should translate an entity type in a translatable string', () => { + expect(displayEntityTypeForTranslation(undefined)).toEqual(undefined); + expect(displayEntityTypeForTranslation('Malware')).toEqual('entity_Malware'); + expect(displayEntityTypeForTranslation('targets')).toEqual('relationship_targets'); + }); + }); + + describe('Function: isHtmlSafe()', () => { + it('should return true if safe', () => { + expect(isStringSafe('')).toEqual(true); + expect(isStringSafe('string without any html')).toEqual(true); + expect(isStringSafe('

simple safe html

')).toEqual(true); + }); + + it('should return false if not safe', () => { + expect(isStringSafe('')).toEqual(false); + expect(isStringSafe('')).toEqual(false); + expect(isStringSafe('

abc