From 295cbc6e5fcbe1d907d29fcf37740e144f0c451a Mon Sep 17 00:00:00 2001 From: Thomas Bouffard <27200110+tbouffard@users.noreply.github.com> Date: Thu, 27 Mar 2025 13:26:20 +0100 Subject: [PATCH] feat!: limit usage of the `eval` function `StylesheetCodec` no longer call the function by default. The eslint configuration now includes a new rule that prevents new calls to the function. Introduce an internal function for indirection of the `eval` function. This change generates fewer warnings when using bundlers such as Rollup. Be aware that this is a temporary workaround, not a final solution. BREAKING CHANGES: `StylesheetCodec.allowEval` is now set to `false` by default to prevent unwanted use of the eval function, as it carries a possible security risk. --- .eslintrc.json | 3 ++- CHANGELOG.md | 3 +++ packages/core/src/editor/EditorPopupMenu.ts | 5 ++-- packages/core/src/internal/utils.ts | 23 +++++++++++++++++++ .../core/src/serialization/ObjectCodec.ts | 3 ++- .../serialization/codecs/StylesheetCodec.ts | 7 +++--- .../codecs/editor/EditorToolbarCodec.ts | 5 ++-- packages/core/src/view/GraphView.ts | 5 ++-- .../src/view/geometry/node/StencilShape.ts | 3 ++- 9 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 packages/core/src/internal/utils.ts diff --git a/.eslintrc.json b/.eslintrc.json index 277aead815..852e8fcec6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -33,6 +33,7 @@ "message": "Const enums are forbidden to increase interoperability. Use regular enums instead." } ], - "no-console": "error" + "no-console": "error", + "no-eval": "error" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6514f2df3d..ec50c98a65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ For more details on the contents of a release, see [the GitHub release page] (ht _**Note:** Yet to be released breaking changes appear here._ +**Breaking Changes**: +- `StylesheetCodec.allowEval` is now set to `false` by default to prevent unwanted use of the eval function, as it carries a possible security risk. + ## 0.16.0 Release date: `2025-03-02` diff --git a/packages/core/src/editor/EditorPopupMenu.ts b/packages/core/src/editor/EditorPopupMenu.ts index c503285970..552e62b93e 100644 --- a/packages/core/src/editor/EditorPopupMenu.ts +++ b/packages/core/src/editor/EditorPopupMenu.ts @@ -24,6 +24,7 @@ import Editor from './Editor'; import { PopupMenuItem } from '../types'; import { isNullish } from '../util/Utils'; +import { doEval } from '../internal/utils'; /** * Creates popupmenus for mouse events. @@ -194,7 +195,7 @@ export class EditorPopupMenu { if (isNullish(condition) || conditions[condition]) { let as = item.getAttribute('as')!; as = Translations.get(as) || as; - const funct = eval(getTextContent((item))); + const funct = doEval(getTextContent((item))); const action = item.getAttribute('action'); let icon = item.getAttribute('icon'); const iconCls = item.getAttribute('iconCls'); @@ -317,7 +318,7 @@ export class EditorPopupMenu { const condNodes = this.config!.getElementsByTagName('condition'); for (const condNode of Array.from(condNodes)) { - const funct = eval(getTextContent((condNode))); + const funct = doEval(getTextContent((condNode))); const name = condNode.getAttribute('name'); if (!isNullish(name) && typeof funct === 'function') { diff --git a/packages/core/src/internal/utils.ts b/packages/core/src/internal/utils.ts new file mode 100644 index 0000000000..31115dd5f3 --- /dev/null +++ b/packages/core/src/internal/utils.ts @@ -0,0 +1,23 @@ +/* +Copyright 2025-present The maxGraph project Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** + * @internal + */ +export const doEval = (expression: string): any => { + // eslint-disable-next-line no-eval -- valid here as we want this function to be the only place in the codebase that uses eval + return eval(expression); +}; diff --git a/packages/core/src/serialization/ObjectCodec.ts b/packages/core/src/serialization/ObjectCodec.ts index c7b298d7d2..ad99913eff 100644 --- a/packages/core/src/serialization/ObjectCodec.ts +++ b/packages/core/src/serialization/ObjectCodec.ts @@ -24,6 +24,7 @@ import { isInteger, isNumeric } from '../util/mathUtils'; import { getTextContent, isElement } from '../util/domUtils'; import { load } from '../util/MaxXmlRequest'; import type Codec from './Codec'; +import { doEval } from '../internal/utils'; /** * Generic codec for JavaScript objects that implements a mapping between @@ -820,7 +821,7 @@ class ObjectCodec { value = child.getAttribute('value'); if (value == null && ObjectCodec.allowEval) { - value = eval(getTextContent((child))); + value = doEval(getTextContent((child))); } } else { value = dec.decode(child, template); diff --git a/packages/core/src/serialization/codecs/StylesheetCodec.ts b/packages/core/src/serialization/codecs/StylesheetCodec.ts index 6b0e45711e..1c9e3b9522 100644 --- a/packages/core/src/serialization/codecs/StylesheetCodec.ts +++ b/packages/core/src/serialization/codecs/StylesheetCodec.ts @@ -22,6 +22,7 @@ import { clone } from '../../util/cloneUtils'; import { GlobalConfig } from '../../util/config'; import { isNumeric } from '../../util/mathUtils'; import { getTextContent, isElement } from '../../util/domUtils'; +import { doEval } from '../../internal/utils'; /** * Codec for {@link Stylesheet}s. @@ -41,9 +42,9 @@ export class StylesheetCodec extends ObjectCodec { * * **WARNING**: Enabling this switch carries a possible security risk. * - * @default true + * @default false */ - static allowEval = true; + static allowEval = false; /** * Encodes a stylesheet. See {@link decode} for a description of the format. @@ -162,7 +163,7 @@ export class StylesheetCodec extends ObjectCodec { let value = null; if (text && StylesheetCodec.allowEval) { - value = eval(text); + value = doEval(text); } else { value = entry.getAttribute('value'); diff --git a/packages/core/src/serialization/codecs/editor/EditorToolbarCodec.ts b/packages/core/src/serialization/codecs/editor/EditorToolbarCodec.ts index f9366b96ee..c519a368a9 100644 --- a/packages/core/src/serialization/codecs/editor/EditorToolbarCodec.ts +++ b/packages/core/src/serialization/codecs/editor/EditorToolbarCodec.ts @@ -24,6 +24,7 @@ import { getClientX, getClientY } from '../../../util/EventUtils'; import InternalEvent from '../../../view/event/InternalEvent'; import { getChildNodes, getTextContent, isElement } from '../../../util/domUtils'; import Translations from '../../../i18n/Translations'; +import { doEval } from '../../../internal/utils'; /** * Custom codec for configuring {@link EditorToolbar}s. @@ -157,7 +158,7 @@ export class EditorToolbarCodec extends ObjectCodec { if (action != null) { elt = into.addItem(as, icon, action, pressedIcon); } else if (mode != null) { - funct = EditorToolbarCodec.allowEval ? eval(text) : null; + funct = EditorToolbarCodec.allowEval ? doEval(text) : null; elt = into.addMode(as, icon, mode, pressedIcon, funct); } else if (template != null || (text != null && text.length > 0)) { let cell = template ? editor.templates[template] : null; @@ -171,7 +172,7 @@ export class EditorToolbarCodec extends ObjectCodec { let insertFunction = null; if (text != null && text.length > 0 && EditorToolbarCodec.allowEval) { - insertFunction = eval(text); + insertFunction = doEval(text); } elt = into.addPrototype( diff --git a/packages/core/src/view/GraphView.ts b/packages/core/src/view/GraphView.ts index 99043d75df..0971dec20c 100644 --- a/packages/core/src/view/GraphView.ts +++ b/packages/core/src/view/GraphView.ts @@ -46,6 +46,7 @@ import StyleRegistry from './style/StyleRegistry'; import type TooltipHandler from './plugins/TooltipHandler'; import type { EdgeStyleFunction, MouseEventListener } from '../types'; import { TranslationsConfig } from '../i18n/config'; +import { doEval } from '../internal/utils'; /** * @class GraphView @@ -1327,7 +1328,7 @@ export class GraphView extends EventSource { let tmp = StyleRegistry.getValue(edgeStyle); if (!tmp && this.isAllowEval()) { - tmp = eval(edgeStyle); + tmp = doEval(edgeStyle); } edgeStyle = tmp; @@ -1594,7 +1595,7 @@ export class GraphView extends EventSource { if (typeof perimeter === 'string') { let tmp = StyleRegistry.getValue(perimeter); if (tmp == null && this.isAllowEval()) { - tmp = eval(perimeter); + tmp = doEval(perimeter); } perimeter = tmp; } diff --git a/packages/core/src/view/geometry/node/StencilShape.ts b/packages/core/src/view/geometry/node/StencilShape.ts index fbe0d46ac4..34181c95c0 100644 --- a/packages/core/src/view/geometry/node/StencilShape.ts +++ b/packages/core/src/view/geometry/node/StencilShape.ts @@ -33,6 +33,7 @@ import { getChildNodes, getTextContent, isElement } from '../../../util/domUtils import Point from '../Point'; import AbstractCanvas2D from '../../canvas/AbstractCanvas2D'; import { AlignValue, ColorValue, VAlignValue } from '../../../types'; +import { doEval } from '../../../internal/utils'; /** * Configure global settings for stencil shapes. @@ -193,7 +194,7 @@ class StencilShape extends Shape { const text = getTextContent((node)); if (text && StencilShapeConfig.allowEval) { - const funct = eval(text); + const funct = doEval(text); if (typeof funct === 'function') { result = funct(shape);