summaryrefslogtreecommitdiff
path: root/apps/web/src/utils/__tests__/math.test.ts
diff options
context:
space:
mode:
authorRayan Salhab <r.salhab@aiyexpertsolutions.com>2026-03-29 19:27:05 +0300
committerGitHub <noreply@github.com>2026-03-29 18:27:05 +0200
commiteb66e03f0e67c3b04c863c2b5540c65495bf621d (patch)
tree2e1efc1a63cd2504b9ecbb37895bf4f43933c5ca /apps/web/src/utils/__tests__/math.test.ts
parenta2418751e6e28dfd4e59a1bb4a0c8e14f0989b47 (diff)
downloadopencut-eb66e03f0e67c3b04c863c2b5540c65495bf621d.tar.gz
fix(security): replace new Function() with safe math expression parser (#753)
* fix(security): replace new Function() with safe math expression parser Replace the dangerous new Function() constructor in evaluateMathExpression() with a recursive descent parser that safely evaluates basic arithmetic expressions. The Function constructor is a security anti-pattern that could enable arbitrary code execution if the validation regex is ever bypassed or relaxed. The new parser: - Only supports numbers, +, -, *, /, parentheses, and whitespace - Has no eval-like functionality that could execute arbitrary code - Maintains backward compatibility with existing expressions - Handles operator precedence and parentheses correctly Fixes #725 * Apply suggestion from @coderabbitai[bot] Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: Maze <167211895+mazeincoding@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Diffstat (limited to 'apps/web/src/utils/__tests__/math.test.ts')
-rw-r--r--apps/web/src/utils/__tests__/math.test.ts120
1 files changed, 120 insertions, 0 deletions
diff --git a/apps/web/src/utils/__tests__/math.test.ts b/apps/web/src/utils/__tests__/math.test.ts
new file mode 100644
index 00000000..4bdb959f
--- /dev/null
+++ b/apps/web/src/utils/__tests__/math.test.ts
@@ -0,0 +1,120 @@
+import { describe, expect, it } from "bun:test";
+import { evaluateMathExpression } from "../math";
+
+describe("evaluateMathExpression", () => {
+ describe("basic arithmetic", () => {
+ it("should add numbers", () => {
+ expect(evaluateMathExpression({ input: "1+2" })).toBe(3);
+ expect(evaluateMathExpression({ input: "10 + 20" })).toBe(30);
+ });
+
+ it("should subtract numbers", () => {
+ expect(evaluateMathExpression({ input: "5-3" })).toBe(2);
+ expect(evaluateMathExpression({ input: "100 - 50" })).toBe(50);
+ });
+
+ it("should multiply numbers", () => {
+ expect(evaluateMathExpression({ input: "4*5" })).toBe(20);
+ expect(evaluateMathExpression({ input: "10 * 10" })).toBe(100);
+ });
+
+ it("should divide numbers", () => {
+ expect(evaluateMathExpression({ input: "20/4" })).toBe(5);
+ expect(evaluateMathExpression({ input: "100 / 10" })).toBe(10);
+ });
+ });
+
+ describe("operator precedence", () => {
+ it("should respect multiplication over addition", () => {
+ expect(evaluateMathExpression({ input: "2+3*4" })).toBe(14);
+ expect(evaluateMathExpression({ input: "10+5*2" })).toBe(20);
+ });
+
+ it("should respect division over subtraction", () => {
+ expect(evaluateMathExpression({ input: "10-6/2" })).toBe(7);
+ });
+ });
+
+ describe("parentheses", () => {
+ it("should evaluate expressions in parentheses first", () => {
+ expect(evaluateMathExpression({ input: "(2+3)*4" })).toBe(20);
+ expect(evaluateMathExpression({ input: "(10-5)*2" })).toBe(10);
+ });
+
+ it("should handle nested parentheses", () => {
+ expect(evaluateMathExpression({ input: "((2+3)*4)" })).toBe(20);
+ expect(evaluateMathExpression({ input: "(1+(2*3))" })).toBe(7);
+ });
+ });
+
+ describe("decimal numbers", () => {
+ it("should handle decimal numbers", () => {
+ expect(evaluateMathExpression({ input: "3.5+2.5" })).toBe(6);
+ expect(evaluateMathExpression({ input: "10.5 * 2" })).toBe(21);
+ });
+ });
+
+ describe("negative numbers", () => {
+ it("should handle negative numbers", () => {
+ expect(evaluateMathExpression({ input: "-5+3" })).toBe(-2);
+ expect(evaluateMathExpression({ input: "10+-5" })).toBe(5);
+ });
+ });
+
+ describe("whitespace handling", () => {
+ it("should handle various whitespace", () => {
+ expect(evaluateMathExpression({ input: " 1 + 2 " })).toBe(3);
+ expect(evaluateMathExpression({ input: "10* 5" })).toBe(50);
+ });
+ });
+
+ describe("edge cases", () => {
+ it("should handle single number", () => {
+ expect(evaluateMathExpression({ input: "42" })).toBe(42);
+ expect(evaluateMathExpression({ input: "3.14" })).toBe(3.14);
+ });
+
+ it("should return null for empty string", () => {
+ expect(evaluateMathExpression({ input: "" })).toBeNull();
+ });
+
+ it("should return null for invalid characters", () => {
+ expect(evaluateMathExpression({ input: "1+2a" })).toBeNull();
+ expect(evaluateMathExpression({ input: "alert(1)" })).toBeNull();
+ expect(evaluateMathExpression({ input: "1+2; doSomething()" })).toBeNull();
+ });
+
+ it("should return null for unbalanced parentheses", () => {
+ expect(evaluateMathExpression({ input: "(1+2" })).toBeNull();
+ expect(evaluateMathExpression({ input: "1+2)" })).toBeNull();
+ });
+
+ it("should return null for division by zero", () => {
+ expect(evaluateMathExpression({ input: "1/0" })).toBeNull();
+ });
+
+ it("should return null for incomplete expressions", () => {
+ expect(evaluateMathExpression({ input: "1+" })).toBeNull();
+ expect(evaluateMathExpression({ input: "*2" })).toBeNull();
+ });
+ });
+
+ describe("security - code injection prevention", () => {
+ it("should reject code injection attempts", () => {
+ // These would be dangerous with new Function() but are safe here
+ expect(evaluateMathExpression({ input: "alert('xss')" })).toBeNull();
+ expect(evaluateMathExpression({ input: "process.exit()" })).toBeNull();
+ expect(evaluateMathExpression({ input: "require('fs')" })).toBeNull();
+ expect(evaluateMathExpression({ input: "eval('1+1')" })).toBeNull();
+ expect(evaluateMathExpression({ input: "Function('return 1')()" })).toBeNull();
+ expect(evaluateMathExpression({ input: "constructor.constructor('return 1')()" })).toBeNull();
+ expect(evaluateMathExpression({ input: "this.toString" })).toBeNull();
+ expect(evaluateMathExpression({ input: "__proto__" })).toBeNull();
+ });
+
+ it("should reject prototype pollution attempts", () => {
+ expect(evaluateMathExpression({ input: "[].constructor" })).toBeNull();
+ expect(evaluateMathExpression({ input: "({}).constructor" })).toBeNull();
+ });
+ });
+});