diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.travis.yml b/.travis.yml index cc4dba2..6e35c0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,18 @@ language: node_js +os: linux +dist: bionic +before_install: + - "nvm install-latest-npm" node_js: - "0.8" - "0.10" + - "0.12" + - "4" + - "6" + - "8" + - "9" + - "10" + - "11" + - "12" + - "13" + - "14" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e91f004 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# 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.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)) + +## 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/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 diff --git a/index.js b/index.js index 3cfbdba..92f4687 100644 --- a/index.js +++ b/index.js @@ -1,15 +1,18 @@ 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 = {}; - - 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 +22,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 +34,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,12 +43,30 @@ module.exports = function (ast, vars) { } else if (node.type === 'BinaryExpression' || node.type === 'LogicalExpression') { - var l = walk(node.left); + 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; + } + 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, 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; + if (op === '==') return l == r; if (op === '===') return l === r; if (op === '!=') return l != r; @@ -62,9 +83,7 @@ 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; } else if (node.type === 'Identifier') { @@ -80,70 +99,80 @@ 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); - // do not allow access to methods on Function - if((obj === FAIL) || (typeof obj == 'function')){ + var obj = walk(node.object, noExecute); + if((obj === FAIL) || ( + (typeof obj == 'function') && rejectAccessToMethodsOnFunctions + )){ 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; + 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 var oldVars = {}; Object.keys(vars).forEach(function(element){ oldVars[element] = vars[element]; }) - node.params.forEach(function(key) { + for(var i=0; i