From 32f8efd072625e267aa7237d2daff35738c50d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Thu, 14 Feb 2019 17:27:11 -0300 Subject: [PATCH 01/35] Fix sandbox breakout vulnerability This fixes the vulnerability detailed in this advisory: https://www.npmjs.com/advisories/758 --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 3cfbdba..998c8b7 100644 --- a/index.js +++ b/index.js @@ -135,6 +135,7 @@ module.exports = function (ast, vars) { if(key.type == 'Identifier'){ vars[key.name] = null; } + else return FAIL; }); for(var i in bodies){ if(walk(bodies[i]) === FAIL){ From a56a139dd1290e958b9da44fd79c63bf2eabc23f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 15 Feb 2019 17:11:57 +0100 Subject: [PATCH 02/35] explicitly mention security caveats --- readme.markdown | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.markdown b/readme.markdown index 9bd50f9..dcb7bd6 100644 --- a/readme.markdown +++ b/readme.markdown @@ -6,6 +6,10 @@ evaluate statically-analyzable expressions [![build status](https://secure.travis-ci.org/substack/static-eval.png)](http://travis-ci.org/substack/static-eval) +# security + +static-eval is like `eval`. It is intended for use in build scripts and code transformations, doing some evaluation at build time—it is **NOT** suitable for handling arbitrary untrusted user input. Malicious user input _can_ execute arbitrary code. + # example ``` js From 5d5dc2d63de66a22f95f91615d7a56ed41f680f2 Mon Sep 17 00:00:00 2001 From: substack Date: Fri, 15 Feb 2019 10:07:10 -1000 Subject: [PATCH 03/35] 2.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d62b312..d241b80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "static-eval", - "version": "2.0.0", + "version": "2.0.1", "description": "evaluate statically-analyzable expressions", "main": "index.js", "dependencies": { From a5547bf00a3053e06c995fc3818289490cb4838b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Lang?= Date: Mon, 18 Feb 2019 15:16:29 -0300 Subject: [PATCH 04/35] Fix a bug in the sandbox breakout fix The `return FAIL` was making the anonymous function of the forEach call to return, that wasn't what I was expecting, so the last fix didn't have any effects. I changed the code so now the `return FAIL` makes the main function return. --- index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 998c8b7..ea16186 100644 --- a/index.js +++ b/index.js @@ -131,12 +131,13 @@ module.exports = function (ast, vars) { oldVars[element] = vars[element]; }) - node.params.forEach(function(key) { + for(var i=0; i Date: Tue, 26 Feb 2019 10:50:24 +0100 Subject: [PATCH 05/35] 2.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d241b80..3c3471e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "static-eval", - "version": "2.0.1", + "version": "2.0.2", "description": "evaluate statically-analyzable expressions", "main": "index.js", "dependencies": { From 50fefa596715ffda047b56ed7a3e5465fecf74dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 26 Feb 2019 10:54:27 +0100 Subject: [PATCH 06/35] ci: Add new node versions. --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index cc4dba2..4e300f4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,10 @@ language: node_js node_js: - "0.8" - "0.10" + - "0.12" + - "4" + - "6" + - "8" + - "9" + - "10" + - "11" From 425d029b0015ddb3688fcf30f3404cc6eb05260d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 26 Feb 2019 10:58:59 +0100 Subject: [PATCH 07/35] Update esprima. --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3c3471e..35854af 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,11 @@ "description": "evaluate statically-analyzable expressions", "main": "index.js", "dependencies": { - "escodegen": "^1.8.1" + "escodegen": "^1.11.1" }, "devDependencies": { - "esprima": "^2.7.3", - "tape": "^4.6.0" + "esprima": "^3.1.3", + "tape": "^4.10.1" }, "scripts": { "test": "tape test/*.js" From da0879e9c6c9ce74858fd05a71188c027a256597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 26 Feb 2019 11:05:24 +0100 Subject: [PATCH 08/35] ci: install latest available npm --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4e300f4..0eeabed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: node_js +before_install: + - "nvm install-latest-npm" node_js: - "0.8" - "0.10" From 2b9cc5da42f11119888fd288753ecb943b4f7245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sat, 28 Sep 2019 12:25:01 +0200 Subject: [PATCH 09/35] Update license text so Github recognises it. --- LICENSE | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index ee27ba4..f52ee8c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,6 @@ -This software is released under the MIT license: +MIT License + +Copyright (c) 2013 James Halliday Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From b0c80ab8d8cb6fc9b48f3605f6f240f7e94fc670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 24 Oct 2019 16:41:09 +0200 Subject: [PATCH 10/35] disallow accessing __proto__ or constructor Plugs another hole in the not-quite-sandbox. We'll never be 100% airtight but this is low hanging fruit that doesn't impact any legitimate use case. --- index.js | 6 ++++++ test/eval.js | 28 +++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index ea16186..13860f3 100644 --- a/index.js +++ b/index.js @@ -102,10 +102,12 @@ module.exports = function (ast, vars) { return FAIL; } if (node.property.type === 'Identifier') { + if (isUnsafeProperty(node.property.name)) return FAIL; return obj[node.property.name]; } var prop = walk(node.property); if (prop === FAIL) return FAIL; + if (isUnsafeProperty(prop)) return FAIL; return obj[prop]; } else if (node.type === 'ConditionalExpression') { @@ -176,3 +178,7 @@ module.exports = function (ast, vars) { return result === FAIL ? undefined : result; }; + +function isUnsafeProperty(name) { + return name === 'constructor' || name === '__proto__'; +} diff --git a/test/eval.js b/test/eval.js index c8d3d25..6dde4c4 100644 --- a/test/eval.js +++ b/test/eval.js @@ -79,4 +79,30 @@ test('MemberExpressions from Functions unresolved', function(t) { var ast = parse(src).body[0].expression; var res = evaluate(ast, {}); t.equal(res, undefined); -}); \ No newline at end of file +}); + +test('disallow accessing constructor or __proto__', function (t) { + t.plan(4) + + var someValue = {}; + + var src = 'object.constructor'; + var ast = parse(src).body[0].expression; + var res = evaluate(ast, { vars: { object: someValue } }); + t.equal(res, undefined); + + var src = 'object["constructor"]'; + var ast = parse(src).body[0].expression; + var res = evaluate(ast, { vars: { object: someValue } }); + t.equal(res, undefined); + + var src = 'object.__proto__'; + var ast = parse(src).body[0].expression; + var res = evaluate(ast, { vars: { object: someValue } }); + t.equal(res, undefined); + + var src = 'object["__pro"+"t\x6f__"]'; + var ast = parse(src).body[0].expression; + var res = evaluate(ast, { vars: { object: someValue } }); + t.equal(res, undefined); +}); From 551469918259875c5591e5110f7a91781dd30680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 24 Oct 2019 17:03:22 +0200 Subject: [PATCH 11/35] Bail on `null` properties (eg. uninitialized arguments) --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 13860f3..7620e5b 100644 --- a/index.js +++ b/index.js @@ -101,12 +101,12 @@ module.exports = function (ast, vars) { if((obj === FAIL) || (typeof obj == 'function')){ return FAIL; } - if (node.property.type === 'Identifier') { + if (node.property.type === 'Identifier' && !node.computed) { if (isUnsafeProperty(node.property.name)) return FAIL; return obj[node.property.name]; } var prop = walk(node.property); - if (prop === FAIL) return FAIL; + if (prop === null || prop === FAIL) return FAIL; if (isUnsafeProperty(prop)) return FAIL; return obj[prop]; } From 6b7b9609d770948ec9e8a8dedeee5e55891459a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Thu, 24 Oct 2019 17:05:39 +0200 Subject: [PATCH 12/35] add runtime-only test case --- test/eval.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/eval.js b/test/eval.js index 6dde4c4..3a0c034 100644 --- a/test/eval.js +++ b/test/eval.js @@ -106,3 +106,18 @@ test('disallow accessing constructor or __proto__', function (t) { var res = evaluate(ast, { vars: { object: someValue } }); t.equal(res, undefined); }); + + +test('constructor at runtime only', function(t) { + t.plan(1) + + var src = '(function myTag(y){return ""[!y?"__proto__":"constructor"][y]})("constructor")("console.log(process.env)")()' + var ast = parse(src).body[0].expression; + var res = evaluate(ast); + t.equal(res, undefined); + + var src = '(function(prop) { return {}[prop ? "benign" : "constructor"][prop] })("constructor")("alert(1)")()' + var ast = parse(src).body[0].expression; + var res = evaluate(ast); + t.equal(res, undefined); +}); From 7d7bdc5f449aba8b023637a018b640abead453c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Wed, 30 Oct 2019 15:45:28 +0100 Subject: [PATCH 13/35] Disable package-lock.json. --- .npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false From a18a308120ac7d5bc974292a8eefb3dfc0649f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Wed, 30 Oct 2019 16:12:04 +0100 Subject: [PATCH 14/35] plan fix --- test/eval.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/eval.js b/test/eval.js index 3a0c034..4055b35 100644 --- a/test/eval.js +++ b/test/eval.js @@ -109,7 +109,7 @@ test('disallow accessing constructor or __proto__', function (t) { test('constructor at runtime only', function(t) { - t.plan(1) + t.plan(2) var src = '(function myTag(y){return ""[!y?"__proto__":"constructor"][y]})("constructor")("console.log(process.env)")()' var ast = parse(src).body[0].expression; From b694d0f37288b8051b0740b990810ceb285349d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Tue, 19 Nov 2019 16:22:02 +0100 Subject: [PATCH 15/35] 2.0.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 35854af..925de2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "static-eval", - "version": "2.0.2", + "version": "2.0.3", "description": "evaluate statically-analyzable expressions", "main": "index.js", "dependencies": { From 62b90331ef3bf6d0392bc79fd99d9a3da8611f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Wed, 19 Feb 2020 16:04:09 +0100 Subject: [PATCH 16/35] Add security.md --- security.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 security.md diff --git a/security.md b/security.md new file mode 100644 index 0000000..a14ace6 --- /dev/null +++ b/security.md @@ -0,0 +1,10 @@ +# Security Policy + +## Supported Versions +Only the latest major version is supported at any given time. + +## Reporting a Vulnerability + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. From e06011a92712fa973f8d3ef7dc5782ea00337669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Wed, 19 Feb 2020 16:06:46 +0100 Subject: [PATCH 17/35] security caveat --- security.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/security.md b/security.md index a14ace6..a1273a0 100644 --- a/security.md +++ b/security.md @@ -8,3 +8,8 @@ Only the latest major version is supported at any given time. To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. + +Note that this package is intended for use in build-time +transformations. It is only intended to handle trusted code. If a +vulnerability requires a fix that would prevent important use cases, we +may decide not to address it. From 16f9c46b490759fdb006b8c6fb930dbed180cc92 Mon Sep 17 00:00:00 2001 From: RoboPhred Date: Thu, 20 Feb 2020 10:08:54 -0700 Subject: [PATCH 18/35] Test for short circuit evaluation --- test/eval.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/eval.js b/test/eval.js index 4055b35..7bc4ce4 100644 --- a/test/eval.js +++ b/test/eval.js @@ -121,3 +121,15 @@ test('constructor at runtime only', function(t) { var res = evaluate(ast); t.equal(res, undefined); }); + +test('short circuit evaluation', function(t) { + t.plan(1); + + var variables = { + value: null + }; + var src = 'value && value.func()'; + var ast = parse(src).body[0].expression; + var res = evaluate(ast, variables); + t.equals(res, null); +}) \ No newline at end of file From 3eb22078152690f4dc940acb7cff48dfc56f37cf Mon Sep 17 00:00:00 2001 From: RoboPhred Date: Thu, 20 Feb 2020 10:14:00 -0700 Subject: [PATCH 19/35] Implement short circuit evaluation --- index.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 7620e5b..55b8123 100644 --- a/index.js +++ b/index.js @@ -40,12 +40,22 @@ module.exports = function (ast, vars) { } else if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') { + var op = node.operator; + + if (op === '&&') { + var l = walk(node.left); + if (l === FAIL) return FAIL; + if (!l) return l; + var r = walk(node.right); + if (r === FAIL) return FAIL; + return r; + } + var l = walk(node.left); if (l === FAIL) return FAIL; var r = walk(node.right); if (r === FAIL) return FAIL; - var op = node.operator; if (op === '==') return l == r; if (op === '===') return l === r; if (op === '!=') return l != r; @@ -62,7 +72,6 @@ module.exports = function (ast, vars) { if (op === '|') return l | r; if (op === '&') return l & r; if (op === '^') return l ^ r; - if (op === '&&') return l && r; if (op === '||') return l || r; return FAIL; From c65e1f52edc538020f1965d5b05d2dc13753517f Mon Sep 17 00:00:00 2001 From: RoboPhred Date: Sat, 22 Feb 2020 12:22:38 -0700 Subject: [PATCH 20/35] Test short circuit eval for OR --- test/eval.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/eval.js b/test/eval.js index 7bc4ce4..87cb6d9 100644 --- a/test/eval.js +++ b/test/eval.js @@ -122,7 +122,7 @@ test('constructor at runtime only', function(t) { t.equal(res, undefined); }); -test('short circuit evaluation', function(t) { +test('short circuit evaluation AND', function(t) { t.plan(1); var variables = { @@ -132,4 +132,18 @@ test('short circuit evaluation', function(t) { var ast = parse(src).body[0].expression; var res = evaluate(ast, variables); t.equals(res, null); +}) + +test('short circuit evaluation OR', function(t) { + t.plan(1); + + var fnInvoked = false; + var variables = { + value: true, + fn: function() { fnInvoked = true} + }; + var src = 'value || fn()'; + var ast = parse(src).body[0].expression; + evaluate(ast, variables); + t.equals(fnInvoked, false); }) \ No newline at end of file From 72cc85db336e88145793a16bfec54d4b6a9470c4 Mon Sep 17 00:00:00 2001 From: RoboPhred Date: Sat, 22 Feb 2020 12:22:45 -0700 Subject: [PATCH 21/35] Implement short circuit eval for OR --- index.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/index.js b/index.js index 55b8123..f0057e0 100644 --- a/index.js +++ b/index.js @@ -50,6 +50,14 @@ module.exports = function (ast, vars) { if (r === FAIL) return FAIL; return r; } + else if (op === '||') { + var l = walk(node.left); + if (l === FAIL) return FAIL; + if (l) return l; + var r = walk(node.right); + if (r === FAIL) return FAIL; + return r; + } var l = walk(node.left); if (l === FAIL) return FAIL; From af8cceb75a1256a0d7f1ed2172f0ae322678dfd1 Mon Sep 17 00:00:00 2001 From: RoboPhred Date: Thu, 20 Feb 2020 11:34:55 -0700 Subject: [PATCH 22/35] Test for function invocation during declaration --- test/eval.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/test/eval.js b/test/eval.js index 4055b35..d966342 100644 --- a/test/eval.js +++ b/test/eval.js @@ -44,6 +44,37 @@ test('array methods', function(t) { t.deepEqual(evaluate(ast), [2, 4, 6]); }); +test('array methods invocation count', function(t) { + t.plan(2); + + var variables = { + values: [1, 2, 3], + receiver: [] + }; + var src = 'values.forEach(function(x) { receiver.push(x); })' + var ast = parse(src).body[0].expression; + evaluate(ast, variables); + t.equal(variables.receiver.length, 3); + t.deepEqual(variables.receiver, [1, 2, 3]); +}) + +test('array methods invocation count debugging', function(t) { + t.plan(1); + + var invoked = false; + var variables = { + values: [], + onValue: () => { + console.log("Invoke happened") + invoked = true + } + }; + var src = 'values.forEach(function(x) { onValue() })' + var ast = parse(src).body[0].expression; + evaluate(ast, variables); + t.equal(invoked, false); +}) + test('array methods with vars', function(t) { t.plan(1); @@ -121,3 +152,17 @@ test('constructor at runtime only', function(t) { var res = evaluate(ast); t.equal(res, undefined); }); + +test('function declaration does not invoke CallExpressions', function(t) { + t.plan(1); + + var invoked = false; + var variables = { + noop: function(){}, + onInvoke: function() {invoked = true} + }; + var src = `noop(function(){ onInvoke(); })`; + var ast = parse(src).body[0].expression; + evaluate(ast, variables); + t.equal(invoked, false); +}); \ No newline at end of file From b1be8a6c4078266f27a52e7473d28bf908ad7bdb Mon Sep 17 00:00:00 2001 From: RoboPhred Date: Thu, 20 Feb 2020 11:35:04 -0700 Subject: [PATCH 23/35] Fix functions invoked during declaration --- index.js | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/index.js b/index.js index 7620e5b..7ae9a6f 100644 --- a/index.js +++ b/index.js @@ -4,12 +4,12 @@ module.exports = function (ast, vars) { if (!vars) vars = {}; var FAIL = {}; - var result = (function walk (node, scopeVars) { + var result = (function walk (node, noExecute) { if (node.type === 'Literal') { return node.value; } else if (node.type === 'UnaryExpression'){ - var val = walk(node.argument) + var val = walk(node.argument, noExecute) if (node.operator === '+') return +val if (node.operator === '-') return -val if (node.operator === '~') return ~val @@ -19,7 +19,7 @@ module.exports = function (ast, vars) { else if (node.type === 'ArrayExpression') { var xs = []; for (var i = 0, l = node.elements.length; i < l; i++) { - var x = walk(node.elements[i]); + var x = walk(node.elements[i], noExecute); if (x === FAIL) return FAIL; xs.push(x); } @@ -31,7 +31,7 @@ module.exports = function (ast, vars) { var prop = node.properties[i]; var value = prop.value === null ? prop.value - : walk(prop.value) + : walk(prop.value, noExecute) ; if (value === FAIL) return FAIL; obj[prop.key.value || prop.key.name] = value; @@ -40,9 +40,9 @@ module.exports = function (ast, vars) { } else if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') { - var l = walk(node.left); + var l = walk(node.left, noExecute); if (l === FAIL) return FAIL; - var r = walk(node.right); + var r = walk(node.right, noExecute); if (r === FAIL) return FAIL; var op = node.operator; @@ -80,23 +80,29 @@ module.exports = function (ast, vars) { else return FAIL; } else if (node.type === 'CallExpression') { - var callee = walk(node.callee); + var callee = walk(node.callee, noExecute); if (callee === FAIL) return FAIL; if (typeof callee !== 'function') return FAIL; + - var ctx = node.callee.object ? walk(node.callee.object) : FAIL; + var ctx = node.callee.object ? walk(node.callee.object, noExecute) : FAIL; if (ctx === FAIL) ctx = null; var args = []; for (var i = 0, l = node.arguments.length; i < l; i++) { - var x = walk(node.arguments[i]); + var x = walk(node.arguments[i], noExecute); if (x === FAIL) return FAIL; args.push(x); } + + if (noExecute) { + return undefined; + } + return callee.apply(ctx, args); } else if (node.type === 'MemberExpression') { - var obj = walk(node.object); + var obj = walk(node.object, noExecute); // do not allow access to methods on Function if((obj === FAIL) || (typeof obj == 'function')){ return FAIL; @@ -105,26 +111,25 @@ module.exports = function (ast, vars) { if (isUnsafeProperty(node.property.name)) return FAIL; return obj[node.property.name]; } - var prop = walk(node.property); + var prop = walk(node.property, noExecute); if (prop === null || prop === FAIL) return FAIL; if (isUnsafeProperty(prop)) return FAIL; return obj[prop]; } else if (node.type === 'ConditionalExpression') { - var val = walk(node.test) + var val = walk(node.test, noExecute) if (val === FAIL) return FAIL; - return val ? walk(node.consequent) : walk(node.alternate) + return val ? walk(node.consequent) : walk(node.alternate, noExecute) } else if (node.type === 'ExpressionStatement') { - var val = walk(node.expression) + var val = walk(node.expression, noExecute) if (val === FAIL) return FAIL; return val; } else if (node.type === 'ReturnStatement') { - return walk(node.argument) + return walk(node.argument, noExecute) } else if (node.type === 'FunctionExpression') { - var bodies = node.body.body; // Create a "scope" for our arguments @@ -141,7 +146,7 @@ module.exports = function (ast, vars) { else return FAIL; } for(var i in bodies){ - if(walk(bodies[i]) === FAIL){ + if(walk(bodies[i], true) === FAIL){ return FAIL; } } @@ -157,14 +162,14 @@ module.exports = function (ast, vars) { else if (node.type === 'TemplateLiteral') { var str = ''; for (var i = 0; i < node.expressions.length; i++) { - str += walk(node.quasis[i]); - str += walk(node.expressions[i]); + str += walk(node.quasis[i], noExecute); + str += walk(node.expressions[i], noExecute); } - str += walk(node.quasis[i]); + str += walk(node.quasis[i], noExecute); return str; } else if (node.type === 'TaggedTemplateExpression') { - var tag = walk(node.tag); + var tag = walk(node.tag, noExecute); var quasi = node.quasi; var strings = quasi.quasis.map(walk); var values = quasi.expressions.map(walk); From 753ca8996eee1e72cf832c42abd052e57ce14386 Mon Sep 17 00:00:00 2001 From: RoboPhred Date: Thu, 20 Feb 2020 11:40:29 -0700 Subject: [PATCH 24/35] Remove debug test --- test/eval.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/test/eval.js b/test/eval.js index d966342..6315926 100644 --- a/test/eval.js +++ b/test/eval.js @@ -58,23 +58,6 @@ test('array methods invocation count', function(t) { t.deepEqual(variables.receiver, [1, 2, 3]); }) -test('array methods invocation count debugging', function(t) { - t.plan(1); - - var invoked = false; - var variables = { - values: [], - onValue: () => { - console.log("Invoke happened") - invoked = true - } - }; - var src = 'values.forEach(function(x) { onValue() })' - var ast = parse(src).body[0].expression; - evaluate(ast, variables); - t.equal(invoked, false); -}) - test('array methods with vars', function(t) { t.plan(1); From 6d79e78424c6c4ee48da4a59ef23a7bd01e11e31 Mon Sep 17 00:00:00 2001 From: RoboPhred Date: Thu, 20 Feb 2020 11:44:23 -0700 Subject: [PATCH 25/35] Do not use template string where not required --- test/eval.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/eval.js b/test/eval.js index 6315926..adf48a8 100644 --- a/test/eval.js +++ b/test/eval.js @@ -144,7 +144,7 @@ test('function declaration does not invoke CallExpressions', function(t) { noop: function(){}, onInvoke: function() {invoked = true} }; - var src = `noop(function(){ onInvoke(); })`; + var src = 'noop(function(){ onInvoke(); })'; var ast = parse(src).body[0].expression; evaluate(ast, variables); t.equal(invoked, false); From 7c6c02996c847ee434bccd6c46924e877a43d142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sat, 7 Mar 2020 16:26:19 +0100 Subject: [PATCH 26/35] Remove dead branch --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index f0057e0..c934992 100644 --- a/index.js +++ b/index.js @@ -80,7 +80,6 @@ module.exports = function (ast, vars) { if (op === '|') return l | r; if (op === '&') return l & r; if (op === '^') return l ^ r; - if (op === '||') return l || r; return FAIL; } From 995a736c46d1a40e61c02eebb3324260367e016f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sat, 7 Mar 2020 16:28:54 +0100 Subject: [PATCH 27/35] Update badge URL. --- readme.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.markdown b/readme.markdown index dcb7bd6..554fdff 100644 --- a/readme.markdown +++ b/readme.markdown @@ -4,7 +4,7 @@ evaluate statically-analyzable expressions [![testling badge](https://ci.testling.com/substack/static-eval.png)](https://ci.testling.com/substack/static-eval) -[![build status](https://secure.travis-ci.org/substack/static-eval.png)](http://travis-ci.org/substack/static-eval) +[![build status](https://secure.travis-ci.org/browserify/static-eval.png)](http://travis-ci.org/browserify/static-eval) # security From b9cd982fc0aaa46dbda11c399605a0eb950a7ec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sat, 7 Mar 2020 16:31:51 +0100 Subject: [PATCH 28/35] 2.0.4 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ab81751 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# static-eval Change Log +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## 2.0.4 +* Short-circuit evaluation in `&&` and `||` expressions. ([@RoboPhred](https://github.com/RoboPhred) in [#28](https://github.com/browserify/static-eval/pull/28)) +* Start tracking changes. diff --git a/package.json b/package.json index 925de2b..c345359 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "static-eval", - "version": "2.0.3", + "version": "2.0.4", "description": "evaluate statically-analyzable expressions", "main": "index.js", "dependencies": { From 9d39dd57483edcc2cdb1f3fbcb5a6b0203816975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sat, 7 Mar 2020 16:32:50 +0100 Subject: [PATCH 29/35] Update repository URL in package.json. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c345359..fdf60ef 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ }, "repository": { "type": "git", - "url": "git://github.com/substack/static-eval.git" + "url": "git://github.com/browserify/static-eval.git" }, - "homepage": "https://github.com/substack/static-eval", + "homepage": "https://github.com/browserify/static-eval", "keywords": [ "static", "eval", From b5ab0d79f2951872126d2924217d6aa1d9eab25a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sat, 7 Mar 2020 16:44:49 +0100 Subject: [PATCH 30/35] 2.0.5 --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab81751..1841c10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## 2.0.5 +* Fix function bodies being invoked during declaration. ([@RoboPhred](https://github.com/RoboPhred) in [#30](https://github.com/browserify/static-eval/pull/30)) + ## 2.0.4 * Short-circuit evaluation in `&&` and `||` expressions. ([@RoboPhred](https://github.com/RoboPhred) in [#28](https://github.com/browserify/static-eval/pull/28)) * Start tracking changes. diff --git a/package.json b/package.json index fdf60ef..d8244a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "static-eval", - "version": "2.0.4", + "version": "2.0.5", "description": "evaluate statically-analyzable expressions", "main": "index.js", "dependencies": { From 798b0d510a7e9644f02240b4b6e90c164fa43c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sat, 7 Mar 2020 16:45:24 +0100 Subject: [PATCH 31/35] ci: add node 12 and 13 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0eeabed..74a9c0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,3 +11,5 @@ node_js: - "9" - "10" - "11" + - "12" + - "13" From 36587c22add0ff925760610824e75e2bb6fbc496 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 11 Jun 2020 14:10:36 -0400 Subject: [PATCH 32/35] remove trailing spaces --- index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 32c84bc..66f38c0 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ var unparse = require('escodegen').generate; module.exports = function (ast, vars) { if (!vars) vars = {}; var FAIL = {}; - + var result = (function walk (node, noExecute) { if (node.type === 'Literal') { return node.value; @@ -63,7 +63,7 @@ module.exports = function (ast, vars) { if (l === FAIL) return FAIL; var r = walk(node.right, noExecute); if (r === FAIL) return FAIL; - + if (op === '==') return l == r; if (op === '===') return l === r; if (op === '!=') return l != r; @@ -80,7 +80,7 @@ module.exports = function (ast, vars) { if (op === '|') return l | r; if (op === '&') return l & r; if (op === '^') return l ^ r; - + return FAIL; } else if (node.type === 'Identifier') { @@ -100,7 +100,7 @@ module.exports = function (ast, vars) { if (callee === FAIL) return FAIL; if (typeof callee !== 'function') return FAIL; - + var ctx = node.callee.object ? walk(node.callee.object, noExecute) : FAIL; if (ctx === FAIL) ctx = null; @@ -119,7 +119,7 @@ module.exports = function (ast, vars) { } else if (node.type === 'MemberExpression') { var obj = walk(node.object, noExecute); - // do not allow access to methods on Function + // do not allow access to methods on Function if((obj === FAIL) || (typeof obj == 'function')){ return FAIL; } @@ -147,7 +147,7 @@ module.exports = function (ast, vars) { } else if (node.type === 'FunctionExpression') { var bodies = node.body.body; - + // Create a "scope" for our arguments var oldVars = {}; Object.keys(vars).forEach(function(element){ @@ -168,7 +168,7 @@ module.exports = function (ast, vars) { } // restore the vars and scope after we walk vars = oldVars; - + var keys = Object.keys(vars); var vals = keys.map(function(key) { return vars[key]; @@ -196,7 +196,7 @@ module.exports = function (ast, vars) { } else return FAIL; })(ast); - + return result === FAIL ? undefined : result; }; From e619afcfeb5dfeca56a27dadd9f36c70c006dceb Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 11 Jun 2020 14:19:21 -0400 Subject: [PATCH 33/35] make option to enable allowAccessToMethodsOnFunctions namely to be used by cwise transform --- index.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 66f38c0..92f4687 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,9 @@ var unparse = require('escodegen').generate; -module.exports = function (ast, vars) { +module.exports = function (ast, vars, opts) { + if(!opts) opts = {}; + var rejectAccessToMethodsOnFunctions = !opts.allowAccessToMethodsOnFunctions; + if (!vars) vars = {}; var FAIL = {}; @@ -119,8 +122,9 @@ module.exports = function (ast, vars) { } else if (node.type === 'MemberExpression') { var obj = walk(node.object, noExecute); - // do not allow access to methods on Function - if((obj === FAIL) || (typeof obj == 'function')){ + if((obj === FAIL) || ( + (typeof obj == 'function') && rejectAccessToMethodsOnFunctions + )){ return FAIL; } if (node.property.type === 'Identifier' && !node.computed) { From 054adac1324132206fe04e503f7fa35503956c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 15 Jun 2020 12:18:22 +0200 Subject: [PATCH 34/35] ci: add node 14 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 74a9c0f..6e35c0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: node_js +os: linux +dist: bionic before_install: - "nvm install-latest-npm" node_js: @@ -13,3 +15,4 @@ node_js: - "11" - "12" - "13" + - "14" From 1a4d734404aaac14d0611b624696b07dba65c7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 15 Jun 2020 12:23:03 +0200 Subject: [PATCH 35/35] 2.1.0 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1841c10..e91f004 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## 2.1.0 +* Add `allowAccessToMethodsOnFunctions` option to restore 1.x behaviour so that [cwise](https://github.com/scijs/cwise) can upgrade. ([@archmoj](https://github.com/archmoj) in [#31](https://github.com/browserify/static-eval/pull/31)) + + Do not use this option if you are not sure that you need it, as it had previously been removed for security reasons. There is a known exploit to execute arbitrary code. Only use it on trusted inputs, like the developer's JS files in a build system. + ## 2.0.5 * Fix function bodies being invoked during declaration. ([@RoboPhred](https://github.com/RoboPhred) in [#30](https://github.com/browserify/static-eval/pull/30)) diff --git a/package.json b/package.json index d8244a4..84db957 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "static-eval", - "version": "2.0.5", + "version": "2.1.0", "description": "evaluate statically-analyzable expressions", "main": "index.js", "dependencies": {