js-confuser
Advanced tools
Comparing version 1.5.6 to 1.5.7
@@ -0,1 +1,15 @@ | ||
# `1.5.7` | ||
Countermeasures function fixes | ||
This update focuses on fixing Countermeasures bugs | ||
The `countermeasures` is custom callback function to invoke when a lock is triggered. | ||
- Fixed [#66](https://github.com/MichaelXF/js-confuser/issues/66) | ||
- - RGF to properly handle the countermeasures function | ||
- Added additional code to prevent an infinite loop from occurring | ||
- Slight improvements to RGF | ||
# `1.5.6` | ||
@@ -2,0 +16,0 @@ Website changed and RGF fixes |
@@ -151,3 +151,3 @@ "use strict"; | ||
return () => { | ||
object.__hiddenCountermeasures = this.lock.getCounterMeasuresCode(); | ||
object.__hiddenCountermeasures = this.lock.getCounterMeasuresCode(object, parents); | ||
@@ -182,4 +182,8 @@ object.$eval = () => { | ||
object.body = (0, _gen.BlockStatement)([functionDeclaration, (0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(hashName, (0, _gen.CallExpression)((0, _insert.clone)(this.hashFn), [(0, _gen.CallExpression)((0, _insert.clone)(this.stringFn), [(0, _gen.Identifier)(functionName)]), (0, _gen.Literal)(this.seed)]))), ifStatement]); | ||
object.body = (0, _gen.BlockStatement)([functionDeclaration, (0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(hashName, (0, _gen.CallExpression)((0, _insert.clone)(this.hashFn), [(0, _gen.CallExpression)((0, _insert.clone)(this.stringFn), [(0, _gen.Identifier)(functionName)]), (0, _gen.Literal)(this.seed)]))), ifStatement]); // Make sure the countermeasures activation variable is present | ||
if (this.lock.counterMeasuresActivated) { | ||
object.body.body.unshift((0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(this.lock.counterMeasuresActivated))); | ||
} | ||
if (object.type == "ArrowFunctionExpression") { | ||
@@ -186,0 +190,0 @@ object.type = "FunctionExpression"; |
@@ -46,2 +46,6 @@ "use strict"; | ||
class Lock extends _transform.default { | ||
/** | ||
* This is a boolean variable injected into the source code determining wether the countermeasures function has been called. | ||
* This is used to prevent infinite loops from happening | ||
*/ | ||
constructor(o) { | ||
@@ -59,2 +63,4 @@ super(o, _order.ObfuscateOrder.Lock); // Removed feature | ||
_defineProperty(this, "counterMeasuresActivated", void 0); | ||
_defineProperty(this, "made", void 0); | ||
@@ -75,30 +81,25 @@ | ||
if (typeof this.options.lock.countermeasures === "string" && (0, _compare.isValidIdentifier)(this.options.lock.countermeasures)) { | ||
var defined = new Set(); | ||
(0, _traverse.default)(tree, (object, parents) => { | ||
if (object.type == "Identifier") { | ||
if (object.type == "Identifier" && object.name === this.options.lock.countermeasures) { | ||
var info = (0, _identifiers.getIdentifierInfo)(object, parents); | ||
if (info.spec.isDefined) { | ||
defined.add(object.name); | ||
if (this.counterMeasuresNode) { | ||
throw new Error("Countermeasures function was already defined, it must have a unique name from the rest of your code"); | ||
} else { | ||
var definingContext = (0, _insert.getVarContext)(parents[0], parents.slice(1)); | ||
if (object.name === this.options.lock.countermeasures) { | ||
if (this.counterMeasuresNode) { | ||
throw new Error("Countermeasures function was already defined, it must have a unique name from the rest of your code"); | ||
} else { | ||
var definingContext = (0, _insert.getVarContext)(parents[0], parents.slice(1)); | ||
if (definingContext != tree) { | ||
throw new Error("Countermeasures function must be defined at the global level"); | ||
} | ||
if (definingContext != tree) { | ||
throw new Error("Countermeasures function must be defined at the global level"); | ||
} | ||
var chain = [object, parents]; | ||
var chain = [object, parents]; | ||
if (info.isFunctionDeclaration) { | ||
chain = [parents[0], parents.slice(1)]; | ||
} else if (info.isVariableDeclaration) { | ||
chain = [parents[1], parents.slice(2)]; | ||
} | ||
if (info.isFunctionDeclaration) { | ||
chain = [parents[0], parents.slice(1)]; | ||
} else if (info.isVariableDeclaration) { | ||
chain = [parents[1], parents.slice(2)]; | ||
} | ||
this.counterMeasuresNode = chain; | ||
} | ||
this.counterMeasuresNode = chain; | ||
} | ||
@@ -117,3 +118,3 @@ } | ||
getCounterMeasuresCode() { | ||
getCounterMeasuresCode(object, parents) { | ||
var opt = this.options.lock.countermeasures; | ||
@@ -127,4 +128,9 @@ | ||
if (typeof opt === "string") { | ||
// Since Lock occurs before variable renaming, we are using the pre-obfuscated function name | ||
return [(0, _gen.ExpressionStatement)((0, _gen.CallExpression)((0, _template.default)(opt).single().expression, []))]; | ||
if (!this.counterMeasuresActivated) { | ||
this.counterMeasuresActivated = this.getPlaceholder(); | ||
(0, _insert.prepend)(parents[parents.length - 1] || object, (0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(this.counterMeasuresActivated))); | ||
} // Since Lock occurs before variable renaming, we are using the pre-obfuscated function name | ||
return [(0, _gen.ExpressionStatement)((0, _gen.LogicalExpression)("||", (0, _gen.Identifier)(this.counterMeasuresActivated), (0, _gen.SequenceExpression)([(0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(this.counterMeasuresActivated), (0, _gen.Literal)(true)), (0, _gen.CallExpression)((0, _template.default)(opt).single().expression, [])])))]; | ||
} | ||
@@ -247,3 +253,3 @@ | ||
var callExpression = (0, _template.default)("\n (\n function(){\n // Breaks JSNice.org, beautifier.io\n var namedFunction = function(){\n const test = function(){\n const regExp=new RegExp('\\n');\n return regExp['test'](namedFunction)\n };\n return test()\n }\n\n return namedFunction();\n }\n )()\n ").single().expression; | ||
nodes.push((0, _gen.IfStatement)(callExpression, this.getCounterMeasuresCode() || [], null)); | ||
nodes.push((0, _gen.IfStatement)(callExpression, this.getCounterMeasuresCode(object, parents) || [], null)); | ||
break; | ||
@@ -279,3 +285,3 @@ | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode() || [], null)); | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode(object, parents) || [], null)); | ||
} | ||
@@ -287,3 +293,3 @@ | ||
test = (0, _gen.BinaryExpression)("<", dateNow, (0, _gen.Literal)(this.getTime(this.options.lock.startDate))); | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode() || [], null)); | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode(object, parents) || [], null)); | ||
break; | ||
@@ -293,7 +299,8 @@ | ||
test = (0, _gen.BinaryExpression)(">", dateNow, (0, _gen.Literal)(this.getTime(this.options.lock.endDate))); | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode() || [], null)); | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode(object, parents) || [], null)); | ||
break; | ||
case "context": | ||
var prop = (0, _random.choice)(this.options.lock.context); // Todo: Alternative to `this` | ||
var prop = (0, _random.choice)(this.options.lock.context); | ||
var code = this.getCounterMeasuresCode(object, parents) || []; // Todo: Alternative to `this` | ||
@@ -307,3 +314,3 @@ if (!this.globalVar) { | ||
test = (0, _gen.UnaryExpression)("!", (0, _gen.MemberExpression)((0, _gen.Identifier)(this.globalVar), (0, _gen.Literal)(prop), true)); | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode() || [], null)); | ||
nodes.push((0, _gen.IfStatement)(test, code, null)); | ||
break; | ||
@@ -314,2 +321,3 @@ | ||
(0, _assert.ok)(this.options.lock.osLock); | ||
var code = this.getCounterMeasuresCode(object, parents) || []; | ||
this.options.lock.osLock.forEach(osName => { | ||
@@ -352,3 +360,3 @@ var agentMatcher = { | ||
}); | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode() || [], null)); | ||
nodes.push((0, _gen.IfStatement)(test, code, null)); | ||
break; | ||
@@ -375,3 +383,3 @@ | ||
}); | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode() || [], null)); | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode(object, parents) || [], null)); | ||
break; | ||
@@ -408,3 +416,3 @@ | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode() || [], null)); | ||
nodes.push((0, _gen.IfStatement)(test, this.getCounterMeasuresCode(object, parents) || [], null)); | ||
} | ||
@@ -411,0 +419,0 @@ |
@@ -20,4 +20,2 @@ "use strict"; | ||
var _antiDestructuring = _interopRequireDefault(require("../es5/antiDestructuring")); | ||
var _compare = require("../../util/compare"); | ||
@@ -183,7 +181,2 @@ | ||
this.before.push(new ExplicitDeclarations(o)); | ||
if (this.options.es5) { | ||
this.before.push(new _antiDestructuring.default(o)); | ||
} // this.before.push(new NameConflicts(o)); | ||
} | ||
@@ -190,0 +183,0 @@ |
@@ -78,2 +78,4 @@ "use strict"; | ||
if (object !== contextObject && (0, _insert.isFunction)(object) && !object.$requiresEval && !object.async && !object.generator && (0, _insert.getVarContext)(parents[0], parents.slice(1)) === contextObject) { | ||
var _this$options$lock; | ||
// Discard getter/setter methods | ||
@@ -89,2 +91,15 @@ if (parents[0].type === "Property" && parents[0].value === object) { | ||
return; | ||
} // Avoid applying to the countermeasures function | ||
if (typeof ((_this$options$lock = this.options.lock) === null || _this$options$lock === void 0 ? void 0 : _this$options$lock.countermeasures) === "string") { | ||
// function countermeasures(){...} | ||
if (object.type === "FunctionDeclaration" && object.id.type === "Identifier" && object.id.name === this.options.lock.countermeasures) { | ||
return; | ||
} // var countermeasures = function(){...} | ||
if (parents[0].type === "VariableDeclarator" && parents[0].init === object && parents[0].id.type === "Identifier" && parents[0].id.name === this.options.lock.countermeasures) { | ||
return; | ||
} | ||
} | ||
@@ -95,3 +110,14 @@ | ||
var isBound = false; | ||
(0, _traverse.walk)(object.body, [object, ...parents], (o, p) => { | ||
/** | ||
* The fnTraverses serves two important purposes | ||
* | ||
* - Identify all the variables referenced and defined here | ||
* - Identify is the 'this' keyword is used anywhere | ||
* | ||
* @param o | ||
* @param p | ||
* @returns | ||
*/ | ||
const fnTraverser = (o, p) => { | ||
if (o.type == "Identifier" && !_constants.reservedIdentifiers.has(o.name) && !this.options.globalVariables.has(o.name)) { | ||
@@ -104,3 +130,3 @@ var info = (0, _identifiers.getIdentifierInfo)(o, p); | ||
if (info.spec.isDefined) { | ||
if (info.spec.isDefined && (0, _insert.getDefiningContext)(o, p) === object) { | ||
defined.add(o.name); | ||
@@ -115,4 +141,7 @@ } else { | ||
} | ||
}); | ||
}; | ||
(0, _traverse.walk)(object.params, [object, ...parents], fnTraverser); | ||
(0, _traverse.walk)(object.body, [object, ...parents], fnTraverser); | ||
if (!isBound) { | ||
@@ -119,0 +148,0 @@ var _object$id; |
{ | ||
"name": "js-confuser", | ||
"version": "1.5.6", | ||
"version": "1.5.7", | ||
"description": "JavaScript Obfuscation Tool.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -198,3 +198,6 @@ import Transform from "../transform"; | ||
return () => { | ||
object.__hiddenCountermeasures = this.lock.getCounterMeasuresCode(); | ||
object.__hiddenCountermeasures = this.lock.getCounterMeasuresCode( | ||
object, | ||
parents | ||
); | ||
@@ -262,2 +265,11 @@ object.$eval = () => { | ||
// Make sure the countermeasures activation variable is present | ||
if (this.lock.counterMeasuresActivated) { | ||
object.body.body.unshift( | ||
VariableDeclaration( | ||
VariableDeclarator(this.lock.counterMeasuresActivated) | ||
) | ||
); | ||
} | ||
if (object.type == "ArrowFunctionExpression") { | ||
@@ -264,0 +276,0 @@ object.type = "FunctionExpression"; |
@@ -14,9 +14,3 @@ import Transform from "../transform"; | ||
NewExpression, | ||
FunctionDeclaration, | ||
ReturnStatement, | ||
VariableDeclaration, | ||
ObjectExpression, | ||
Property, | ||
ArrayExpression, | ||
FunctionExpression, | ||
ThisExpression, | ||
@@ -26,2 +20,3 @@ VariableDeclarator, | ||
LogicalExpression, | ||
SequenceExpression, | ||
} from "../../util/gen"; | ||
@@ -52,2 +47,8 @@ import traverse, { getBlock, isBlock } from "../../traverse"; | ||
/** | ||
* This is a boolean variable injected into the source code determining wether the countermeasures function has been called. | ||
* This is used to prevent infinite loops from happening | ||
*/ | ||
counterMeasuresActivated: string; | ||
made: number; | ||
@@ -79,32 +80,28 @@ | ||
) { | ||
var defined = new Set<string>(); | ||
traverse(tree, (object, parents) => { | ||
if (object.type == "Identifier") { | ||
if ( | ||
object.type == "Identifier" && | ||
object.name === this.options.lock.countermeasures | ||
) { | ||
var info = getIdentifierInfo(object, parents); | ||
if (info.spec.isDefined) { | ||
defined.add(object.name); | ||
if (object.name === this.options.lock.countermeasures) { | ||
if (this.counterMeasuresNode) { | ||
if (this.counterMeasuresNode) { | ||
throw new Error( | ||
"Countermeasures function was already defined, it must have a unique name from the rest of your code" | ||
); | ||
} else { | ||
var definingContext = getVarContext(parents[0], parents.slice(1)); | ||
if (definingContext != tree) { | ||
throw new Error( | ||
"Countermeasures function was already defined, it must have a unique name from the rest of your code" | ||
"Countermeasures function must be defined at the global level" | ||
); | ||
} else { | ||
var definingContext = getVarContext( | ||
parents[0], | ||
parents.slice(1) | ||
); | ||
if (definingContext != tree) { | ||
throw new Error( | ||
"Countermeasures function must be defined at the global level" | ||
); | ||
} | ||
var chain: Location = [object, parents]; | ||
if (info.isFunctionDeclaration) { | ||
chain = [parents[0], parents.slice(1)]; | ||
} else if (info.isVariableDeclaration) { | ||
chain = [parents[1], parents.slice(2)]; | ||
} | ||
} | ||
var chain: Location = [object, parents]; | ||
if (info.isFunctionDeclaration) { | ||
chain = [parents[0], parents.slice(1)]; | ||
} else if (info.isVariableDeclaration) { | ||
chain = [parents[1], parents.slice(2)]; | ||
} | ||
this.counterMeasuresNode = chain; | ||
} | ||
this.counterMeasuresNode = chain; | ||
} | ||
@@ -127,3 +124,3 @@ } | ||
getCounterMeasuresCode(): Node[] { | ||
getCounterMeasuresCode(object: Node, parents: Node[]): Node[] { | ||
var opt = this.options.lock.countermeasures; | ||
@@ -137,6 +134,26 @@ | ||
if (typeof opt === "string") { | ||
if (!this.counterMeasuresActivated) { | ||
this.counterMeasuresActivated = this.getPlaceholder(); | ||
prepend( | ||
parents[parents.length - 1] || object, | ||
VariableDeclaration(VariableDeclarator(this.counterMeasuresActivated)) | ||
); | ||
} | ||
// Since Lock occurs before variable renaming, we are using the pre-obfuscated function name | ||
return [ | ||
ExpressionStatement( | ||
CallExpression(Template(opt).single().expression, []) | ||
LogicalExpression( | ||
"||", | ||
Identifier(this.counterMeasuresActivated), | ||
SequenceExpression([ | ||
AssignmentExpression( | ||
"=", | ||
Identifier(this.counterMeasuresActivated), | ||
Literal(true) | ||
), | ||
CallExpression(Template(opt).single().expression, []), | ||
]) | ||
) | ||
), | ||
@@ -297,3 +314,3 @@ ]; | ||
callExpression, | ||
this.getCounterMeasuresCode() || [], | ||
this.getCounterMeasuresCode(object, parents) || [], | ||
null | ||
@@ -334,3 +351,7 @@ ) | ||
nodes.push( | ||
IfStatement(test, this.getCounterMeasuresCode() || [], null) | ||
IfStatement( | ||
test, | ||
this.getCounterMeasuresCode(object, parents) || [], | ||
null | ||
) | ||
); | ||
@@ -349,3 +370,7 @@ } | ||
nodes.push( | ||
IfStatement(test, this.getCounterMeasuresCode() || [], null) | ||
IfStatement( | ||
test, | ||
this.getCounterMeasuresCode(object, parents) || [], | ||
null | ||
) | ||
); | ||
@@ -363,3 +388,7 @@ | ||
nodes.push( | ||
IfStatement(test, this.getCounterMeasuresCode() || [], null) | ||
IfStatement( | ||
test, | ||
this.getCounterMeasuresCode(object, parents) || [], | ||
null | ||
) | ||
); | ||
@@ -372,2 +401,4 @@ | ||
var code = this.getCounterMeasuresCode(object, parents) || []; | ||
// Todo: Alternative to `this` | ||
@@ -398,5 +429,3 @@ if (!this.globalVar) { | ||
); | ||
nodes.push( | ||
IfStatement(test, this.getCounterMeasuresCode() || [], null) | ||
); | ||
nodes.push(IfStatement(test, code, null)); | ||
@@ -412,2 +441,4 @@ break; | ||
var code = this.getCounterMeasuresCode(object, parents) || []; | ||
this.options.lock.osLock.forEach((osName) => { | ||
@@ -465,5 +496,3 @@ var agentMatcher = { | ||
test = UnaryExpression("!", { ...test }); | ||
nodes.push( | ||
IfStatement(test, this.getCounterMeasuresCode() || [], null) | ||
); | ||
nodes.push(IfStatement(test, code, null)); | ||
break; | ||
@@ -505,3 +534,7 @@ | ||
nodes.push( | ||
IfStatement(test, this.getCounterMeasuresCode() || [], null) | ||
IfStatement( | ||
test, | ||
this.getCounterMeasuresCode(object, parents) || [], | ||
null | ||
) | ||
); | ||
@@ -558,3 +591,7 @@ break; | ||
nodes.push( | ||
IfStatement(test, this.getCounterMeasuresCode() || [], null) | ||
IfStatement( | ||
test, | ||
this.getCounterMeasuresCode(object, parents) || [], | ||
null | ||
) | ||
); | ||
@@ -561,0 +598,0 @@ } |
@@ -6,20 +6,7 @@ /** | ||
import { | ||
BlockStatement, | ||
Identifier, | ||
LabeledStatement, | ||
Literal, | ||
Location, | ||
Node, | ||
ReturnStatement, | ||
} from "../../util/gen"; | ||
import { BlockStatement, Literal, ReturnStatement } from "../../util/gen"; | ||
import { ObfuscateOrder } from "../../order"; | ||
import { getIndexDirect, clone, getFunction } from "../../util/insert"; | ||
import { ok } from "assert"; | ||
import { clone, getFunction } from "../../util/insert"; | ||
import { getIdentifierInfo } from "../../util/identifiers"; | ||
import { walk } from "../../traverse"; | ||
import Label from "../label"; | ||
import NameConflicts from "./nameConflicts"; | ||
import AntiDestructuring from "../es5/antiDestructuring"; | ||
import { OPERATOR_PRECEDENCE } from "../../precedence"; | ||
import { isLoop } from "../../util/compare"; | ||
@@ -185,8 +172,2 @@ | ||
this.before.push(new ExplicitDeclarations(o)); | ||
if (this.options.es5) { | ||
this.before.push(new AntiDestructuring(o)); | ||
} | ||
// this.before.push(new NameConflicts(o)); | ||
} | ||
@@ -193,0 +174,0 @@ |
@@ -33,2 +33,3 @@ import { compileJsSync } from "../compiler"; | ||
prepend, | ||
getDefiningContext, | ||
} from "../util/insert"; | ||
@@ -110,2 +111,24 @@ import { getRandomString } from "../util/random"; | ||
// Avoid applying to the countermeasures function | ||
if (typeof this.options.lock?.countermeasures === "string") { | ||
// function countermeasures(){...} | ||
if ( | ||
object.type === "FunctionDeclaration" && | ||
object.id.type === "Identifier" && | ||
object.id.name === this.options.lock.countermeasures | ||
) { | ||
return; | ||
} | ||
// var countermeasures = function(){...} | ||
if ( | ||
parents[0].type === "VariableDeclarator" && | ||
parents[0].init === object && | ||
parents[0].id.type === "Identifier" && | ||
parents[0].id.name === this.options.lock.countermeasures | ||
) { | ||
return; | ||
} | ||
} | ||
var defined = new Set<string>(), | ||
@@ -116,3 +139,13 @@ referenced = new Set<string>(); | ||
walk(object.body, [object, ...parents], (o, p) => { | ||
/** | ||
* The fnTraverses serves two important purposes | ||
* | ||
* - Identify all the variables referenced and defined here | ||
* - Identify is the 'this' keyword is used anywhere | ||
* | ||
* @param o | ||
* @param p | ||
* @returns | ||
*/ | ||
const fnTraverser = (o, p) => { | ||
if ( | ||
@@ -127,3 +160,3 @@ o.type == "Identifier" && | ||
} | ||
if (info.spec.isDefined) { | ||
if (info.spec.isDefined && getDefiningContext(o, p) === object) { | ||
defined.add(o.name); | ||
@@ -138,4 +171,7 @@ } else { | ||
} | ||
}); | ||
}; | ||
walk(object.params, [object, ...parents], fnTraverser); | ||
walk(object.body, [object, ...parents], fnTraverser); | ||
if (!isBound) { | ||
@@ -142,0 +178,0 @@ defined.forEach((identifier) => { |
@@ -83,1 +83,19 @@ import JsConfuser from "../../../src/index"; | ||
}); | ||
// https://github.com/MichaelXF/js-confuser/issues/66 | ||
test("Variant #5: Should work with RGF enabled", async () => { | ||
await JsConfuser.obfuscate( | ||
` | ||
function myCountermeasuresFunction(){ | ||
} | ||
`, | ||
{ | ||
target: "node", | ||
lock: { | ||
countermeasures: "myCountermeasuresFunction", | ||
}, | ||
rgf: true, | ||
} | ||
); | ||
}); |
@@ -254,2 +254,39 @@ import { writeFileSync } from "fs"; | ||
}); | ||
it("should not apply to functions that reference their parent scope in the parameters", async () => { | ||
var output = await JsConfuser.obfuscate( | ||
` | ||
var outsideVar = 0; | ||
function myFunction(insideVar = outsideVar){ | ||
} | ||
`, | ||
{ | ||
target: "node", | ||
rgf: true, | ||
} | ||
); | ||
expect(output).not.toContain("new Function"); | ||
}); | ||
it("should not apply to functions that reference their parent scope in previously defined names", async () => { | ||
var output = await JsConfuser.obfuscate( | ||
` | ||
var outsideVar = 0; | ||
function myFunction(){ | ||
(function (){ | ||
var outsideVar; | ||
})(); | ||
console.log(outsideVar); | ||
} | ||
`, | ||
{ | ||
target: "node", | ||
rgf: true, | ||
} | ||
); | ||
expect(output).not.toContain("new Function"); | ||
}); | ||
}); | ||
@@ -256,0 +293,0 @@ |
1285011
35189