Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[frontend] Improve graph labels
  • Loading branch information
lndrtrbn committed Feb 26, 2026
commit 794267b967d4d8a9e1ada336dce6346e13f25bf2
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down
26 changes: 25 additions & 1 deletion opencti-platform/opencti-front/src/utils/String.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
: 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);
};
87 changes: 87 additions & 0 deletions opencti-platform/opencti-front/src/utils/String.test.ts
Original file line number Diff line number Diff line change
@@ -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('<p>simple <strong>safe</strong> html</p>')).toEqual(true);
});

it('should return false if not safe', () => {
expect(isStringSafe('<img src=x onerror=alert(1)//>')).toEqual(false);
expect(isStringSafe('<svg><g/onload=alert(2)//<p>')).toEqual(false);
expect(isStringSafe('<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>')).toEqual(false);
});
});

describe('Function: sanitize()', () => {
it('should return same string if safe', () => {
const testA = '';
const testB = 'string without any html';
const testC = '<p>simple <strong>safe</strong> html</p>';

expect(sanitize(testA)).toEqual(testA);
expect(isStringSafe(sanitize(testA))).toEqual(true);
expect(sanitize(testB)).toEqual(testB);
expect(isStringSafe(sanitize(testB))).toEqual(true);
expect(sanitize(testC)).toEqual(testC);
expect(isStringSafe(sanitize(testC))).toEqual(true);
});

it('should return sanitized string if not safe (without escape)', () => {
const testA = '<img src=x onerror=alert(1)//>';
const testB = '<svg><g/onload=alert(2)//<p>';
const testC = '<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>';

expect(sanitize(testA)).toEqual('<img src="x">');
expect(isStringSafe(testA)).toEqual(false);
expect(isStringSafe(sanitize(testA))).toEqual(true);
expect(sanitize(testB)).toEqual('<svg><g></g></svg>');
expect(isStringSafe(testB)).toEqual(false);
expect(isStringSafe(sanitize(testB))).toEqual(true);
expect(sanitize(testC)).toEqual('<p>abc</p>');
expect(isStringSafe(testC)).toEqual(false);
expect(isStringSafe(sanitize(testC))).toEqual(true);
});

it('should return sanitized string if not safe (with escape)', () => {
const testA = '<img src=x onerror=alert(1)//>';
const testB = '<svg><g/onload=alert(2)//<p>';
const testC = '<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>';

expect(sanitize(testA, true)).toEqual('&lt;img src=x onerror=alert(1)//&gt;');
expect(isStringSafe(testA)).toEqual(false);
expect(isStringSafe(sanitize(testA, true))).toEqual(true);
expect(sanitize(testB, true)).toEqual('&lt;svg&gt;&lt;g/onload=alert(2)//&lt;p&gt;');
expect(isStringSafe(testB)).toEqual(false);
expect(isStringSafe(sanitize(testB, true))).toEqual(true);
expect(sanitize(testC, true)).toEqual('&lt;p&gt;abc&lt;iframe//src=jAva&amp;Tab;script:alert(3)&gt;def&lt;/p&gt;');
expect(isStringSafe(testC)).toEqual(false);
expect(isStringSafe(sanitize(testC, true))).toEqual(true);
});
});
});
25 changes: 0 additions & 25 deletions opencti-platform/opencti-front/src/utils/String.test.tsx

This file was deleted.