webfuscator 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/analysis/constant.js +3 -4
  2. package/dist/analysis/constant.js.map +1 -1
  3. package/dist/analysis/inlineability.d.ts.map +1 -1
  4. package/dist/analysis/inlineability.js +37 -25
  5. package/dist/analysis/inlineability.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/obfuscator.d.ts +2 -1
  9. package/dist/obfuscator.d.ts.map +1 -1
  10. package/dist/obfuscator.js +47 -12
  11. package/dist/obfuscator.js.map +1 -1
  12. package/dist/options.d.ts +24 -4
  13. package/dist/options.d.ts.map +1 -1
  14. package/dist/preparation/expand-destructuring.d.ts +3 -3
  15. package/dist/preparation/expand-destructuring.js +6 -6
  16. package/dist/preparation/expand-destructuring.js.map +1 -1
  17. package/dist/transforms/arrow-to-function.js +3 -5
  18. package/dist/transforms/arrow-to-function.js.map +1 -1
  19. package/dist/transforms/conceal-strings.d.ts +57 -0
  20. package/dist/transforms/conceal-strings.d.ts.map +1 -0
  21. package/dist/transforms/conceal-strings.js +361 -0
  22. package/dist/transforms/conceal-strings.js.map +1 -0
  23. package/dist/transforms/const-let-to-var.d.ts +3 -3
  24. package/dist/transforms/const-let-to-var.d.ts.map +1 -1
  25. package/dist/transforms/const-let-to-var.js +36 -9
  26. package/dist/transforms/const-let-to-var.js.map +1 -1
  27. package/dist/transforms/const-to-let.d.ts +2 -3
  28. package/dist/transforms/const-to-let.d.ts.map +1 -1
  29. package/dist/transforms/const-to-let.js +3 -5
  30. package/dist/transforms/const-to-let.js.map +1 -1
  31. package/dist/transforms/fold-builtin-methods.d.ts.map +1 -1
  32. package/dist/transforms/fold-builtin-methods.js +48 -10
  33. package/dist/transforms/fold-builtin-methods.js.map +1 -1
  34. package/dist/transforms/fold-constants.js +3 -0
  35. package/dist/transforms/fold-constants.js.map +1 -1
  36. package/dist/transforms/hoist-variables.d.ts +30 -0
  37. package/dist/transforms/hoist-variables.d.ts.map +1 -0
  38. package/dist/transforms/hoist-variables.js +224 -0
  39. package/dist/transforms/hoist-variables.js.map +1 -0
  40. package/dist/transforms/inline-functions.js +3 -4
  41. package/dist/transforms/inline-functions.js.map +1 -1
  42. package/dist/transforms/mangle-properties.js +7 -4
  43. package/dist/transforms/mangle-properties.js.map +1 -1
  44. package/dist/transforms/merge-variable-declarations.d.ts +17 -0
  45. package/dist/transforms/merge-variable-declarations.d.ts.map +1 -0
  46. package/dist/transforms/merge-variable-declarations.js +68 -0
  47. package/dist/transforms/merge-variable-declarations.js.map +1 -0
  48. package/dist/transforms/numbers-to-strings.d.ts.map +1 -1
  49. package/dist/transforms/numbers-to-strings.js +1 -8
  50. package/dist/transforms/numbers-to-strings.js.map +1 -1
  51. package/dist/transforms/pack-declarations-into-parameters.js +1 -5
  52. package/dist/transforms/pack-declarations-into-parameters.js.map +1 -1
  53. package/dist/transforms/pack.d.ts +5 -7
  54. package/dist/transforms/pack.d.ts.map +1 -1
  55. package/dist/transforms/pack.js +194 -32
  56. package/dist/transforms/pack.js.map +1 -1
  57. package/dist/transforms/remove-unused-code.d.ts.map +1 -1
  58. package/dist/transforms/remove-unused-code.js +7 -39
  59. package/dist/transforms/remove-unused-code.js.map +1 -1
  60. package/dist/transforms/rename-identifiers.d.ts.map +1 -1
  61. package/dist/transforms/rename-identifiers.js +29 -17
  62. package/dist/transforms/rename-identifiers.js.map +1 -1
  63. package/dist/transforms/reorder-function-declarations.d.ts +25 -0
  64. package/dist/transforms/reorder-function-declarations.d.ts.map +1 -0
  65. package/dist/transforms/reorder-function-declarations.js +64 -0
  66. package/dist/transforms/reorder-function-declarations.js.map +1 -0
  67. package/dist/transforms/specials-to-strings.js +4 -3
  68. package/dist/transforms/specials-to-strings.js.map +1 -1
  69. package/dist/transforms/update-to-assignment.js +1 -16
  70. package/dist/transforms/update-to-assignment.js.map +1 -1
  71. package/dist/utils/ast.d.ts +1 -2
  72. package/dist/utils/ast.d.ts.map +1 -1
  73. package/dist/utils/ast.js +12 -42
  74. package/dist/utils/ast.js.map +1 -1
  75. package/dist/utils/literal.d.ts.map +1 -1
  76. package/dist/utils/literal.js +74 -2
  77. package/dist/utils/literal.js.map +1 -1
  78. package/dist/utils/statement-lists.d.ts +9 -0
  79. package/dist/utils/statement-lists.d.ts.map +1 -0
  80. package/dist/utils/statement-lists.js +27 -0
  81. package/dist/utils/statement-lists.js.map +1 -0
  82. package/package.json +12 -13
  83. package/src/analysis/constant.ts +3 -6
  84. package/src/analysis/inlineability.ts +40 -32
  85. package/src/index.ts +1 -0
  86. package/src/obfuscator.ts +51 -14
  87. package/src/options.ts +28 -3
  88. package/src/preparation/expand-destructuring.ts +6 -6
  89. package/src/transforms/arrow-to-function.ts +3 -5
  90. package/src/transforms/conceal-strings.ts +530 -0
  91. package/src/transforms/const-let-to-var.ts +37 -9
  92. package/src/transforms/const-to-let.ts +3 -5
  93. package/src/transforms/fold-builtin-methods.ts +67 -12
  94. package/src/transforms/fold-constants.ts +3 -0
  95. package/src/transforms/hoist-variables.ts +272 -0
  96. package/src/transforms/inline-functions.ts +3 -4
  97. package/src/transforms/mangle-properties.ts +7 -7
  98. package/src/transforms/merge-variable-declarations.ts +91 -0
  99. package/src/transforms/numbers-to-strings.ts +2 -12
  100. package/src/transforms/pack-declarations-into-parameters.ts +1 -5
  101. package/src/transforms/pack.ts +202 -35
  102. package/src/transforms/remove-unused-code.ts +9 -64
  103. package/src/transforms/rename-identifiers.ts +42 -17
  104. package/src/transforms/reorder-function-declarations.ts +80 -0
  105. package/src/transforms/specials-to-strings.ts +4 -3
  106. package/src/transforms/update-to-assignment.ts +1 -17
  107. package/src/utils/ast.ts +15 -51
  108. package/src/utils/literal.ts +91 -2
  109. package/src/utils/statement-lists.ts +45 -0
  110. package/DISCLOSURE +0 -9
  111. package/README.md +0 -62
@@ -2,7 +2,7 @@ import type { Binding, NodePath, Visitor } from '@babel/traverse'
2
2
  import * as t from '@babel/types'
3
3
  import type { File } from '@babel/types'
4
4
 
5
- import { isDirectEvalCall, isInsideWith } from '../utils/ast'
5
+ import { isInsideWith } from '../utils/ast'
6
6
  import { traverseForChanges } from '../utils/change-tracking'
7
7
  import type { ChangeState } from '../utils/change-tracking'
8
8
 
@@ -52,10 +52,6 @@ const visitor: Visitor<ChangeState> = {
52
52
  if (!isNumericBinding(binding)) {
53
53
  return
54
54
  }
55
- // Direct eval anywhere in the binding's scope can change its type.
56
- if (bindingScopeReachesDirectEval(binding!)) {
57
- return
58
- }
59
55
  const compound = operator === '++' ? '+=' : '-='
60
56
  path.replaceWith(t.assignmentExpression(compound, argument, t.numericLiteral(1)))
61
57
  state.changed = true
@@ -95,18 +91,6 @@ function isNumericBinding(binding: Binding | undefined): boolean {
95
91
  return binding.constantViolations.every((violation) => keepsNumeric(violation.node))
96
92
  }
97
93
 
98
- // A sibling or nested direct eval can still close over and reassign the binding.
99
- function bindingScopeReachesDirectEval(binding: Binding): boolean {
100
- let found = false
101
- t.traverseFast(binding.scope.block, (node) => {
102
- if (isDirectEvalCall(node)) {
103
- found = true
104
- }
105
- return found ? t.traverseFast.stop : undefined
106
- })
107
- return found
108
- }
109
-
110
94
  function isForInOfLoopVariable(binding: Binding): boolean {
111
95
  const declaration = binding.path.parentPath
112
96
  if (!declaration || !declaration.isVariableDeclaration()) {
package/src/utils/ast.ts CHANGED
@@ -62,75 +62,39 @@ export function isProtoKey(key: t.Node): boolean {
62
62
  )
63
63
  }
64
64
 
65
+ // A non-computed key names a property; it is not a runtime value.
66
+ export function isNonComputedKey(path: NodePath): boolean {
67
+ const parent = path.parent
68
+ return (
69
+ 'key' in parent && parent.key === path.node && 'computed' in parent && parent.computed === false
70
+ )
71
+ }
72
+
65
73
  // `with` can shadow names outside Babel's scope table.
66
74
  export function isInsideWith(path: NodePath): boolean {
67
- if (!programContains(path.scope.getProgramParent().block, programWith, t.isWithStatement)) {
75
+ if (!programHasWith(path.scope.getProgramParent().block)) {
68
76
  return false
69
77
  }
70
78
  return path.findParent((ancestor) => ancestor.isWithStatement()) !== null
71
79
  }
72
80
 
73
- // Direct eval can access local bindings by name. Indirect eval runs globally and
74
- // does not count.
75
- export function enclosingScopeHasDirectEval(path: NodePath): boolean {
76
- if (!programContains(path.scope.getProgramParent().block, programDirectEval, isDirectEvalCall)) {
77
- return false
78
- }
79
- let current: NodePath | null = path
80
- while (current) {
81
- if ((current.isFunction() || current.isProgram()) && ownScopeHasDirectEval(current.node)) {
82
- return true
83
- }
84
- current = current.parentPath
85
- }
86
- return false
87
- }
88
-
89
- // Cache program-wide `eval` and `with` checks to avoid a subtree walk per name.
90
- // No transform creates either construct, so a negative result stays valid.
91
- const programDirectEval = new WeakMap<t.Node, boolean>()
81
+ // Cache the program-wide scan because `isInsideWith` runs for many identifiers.
82
+ // Transforms never add `with`, so a cached miss remains valid.
92
83
  const programWith = new WeakMap<t.Node, boolean>()
93
84
 
94
- function programContains(
95
- programNode: t.Node,
96
- cache: WeakMap<t.Node, boolean>,
97
- matches: (node: t.Node) => boolean,
98
- ): boolean {
99
- const cached = cache.get(programNode)
85
+ function programHasWith(programNode: t.Node): boolean {
86
+ const cached = programWith.get(programNode)
100
87
  if (cached !== undefined) {
101
88
  return cached
102
89
  }
103
90
  let found = false
104
91
  t.traverseFast(programNode, (node) => {
105
- if (!matches(node)) {
92
+ if (!t.isWithStatement(node)) {
106
93
  return
107
94
  }
108
95
  found = true
109
96
  return t.traverseFast.stop
110
97
  })
111
- cache.set(programNode, found)
98
+ programWith.set(programNode, found)
112
99
  return found
113
100
  }
114
-
115
- function ownScopeHasDirectEval(owner: t.Node): boolean {
116
- let found = false
117
- // This walk stops at the first hit, unlike `walkOwnFunctionScope`.
118
- t.traverseFast(owner, (node) => {
119
- if (isDirectEvalCall(node)) {
120
- found = true
121
- return t.traverseFast.stop
122
- }
123
- return node !== owner && t.isFunction(node) ? t.traverseFast.skip : undefined
124
- })
125
- return found
126
- }
127
-
128
- export function isDirectEvalCall(node: t.Node): boolean {
129
- // `eval?.(...)` is an OptionalCallExpression yet still a direct eval, so both
130
- // call node shapes count.
131
- return (
132
- (t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
133
- t.isIdentifier(node.callee) &&
134
- node.callee.name === 'eval'
135
- )
136
- }
@@ -1,5 +1,9 @@
1
+ import generate from '@babel/generator'
1
2
  import * as t from '@babel/types'
2
3
 
4
+ const MAX_GENERATED_LITERAL_LENGTH = 8192
5
+ const MAX_LITERAL_DEPTH = 32
6
+ const MAX_LITERAL_NODES = 512
3
7
  const MAX_STRING_LITERAL_LENGTH = 4096
4
8
 
5
9
  // RegExp literals allocate mutable objects and are not primitive values.
@@ -45,19 +49,56 @@ function literalShapeKind(node: t.Node): 'bigint' | 'other' | null {
45
49
  }
46
50
 
47
51
  export function valueToLiteral(value: unknown): t.Expression | null {
52
+ const literal = buildValueLiteral(value, new Set(), { nodesRemaining: MAX_LITERAL_NODES }, 0)
53
+ if (!literal || generate(literal).code.length > MAX_GENERATED_LITERAL_LENGTH) {
54
+ return null
55
+ }
56
+ return literal
57
+ }
58
+
59
+ interface LiteralBudget {
60
+ nodesRemaining: number
61
+ }
62
+
63
+ function reserveLiteralNodes(budget: LiteralBudget, count: number): boolean {
64
+ if (budget.nodesRemaining < count) {
65
+ return false
66
+ }
67
+ budget.nodesRemaining -= count
68
+ return true
69
+ }
70
+
71
+ function buildValueLiteral(
72
+ value: unknown,
73
+ seen: Set<object>,
74
+ budget: LiteralBudget,
75
+ depth: number,
76
+ ): t.Expression | null {
77
+ if (depth > MAX_LITERAL_DEPTH) {
78
+ return null
79
+ }
48
80
  if (typeof value === 'string') {
49
- if (value.length > MAX_STRING_LITERAL_LENGTH) {
81
+ if (value.length > MAX_STRING_LITERAL_LENGTH || !reserveLiteralNodes(budget, 1)) {
50
82
  return null
51
83
  }
52
84
  return t.stringLiteral(value)
53
85
  }
54
86
  if (typeof value === 'boolean') {
87
+ if (!reserveLiteralNodes(budget, 1)) {
88
+ return null
89
+ }
55
90
  return t.booleanLiteral(value)
56
91
  }
57
92
  if (value === null) {
93
+ if (!reserveLiteralNodes(budget, 1)) {
94
+ return null
95
+ }
58
96
  return t.nullLiteral()
59
97
  }
60
98
  if (value === undefined) {
99
+ if (!reserveLiteralNodes(budget, 2)) {
100
+ return null
101
+ }
61
102
  // `void 0` cannot be shadowed like the identifier `undefined`.
62
103
  return t.unaryExpression('void', t.numericLiteral(0))
63
104
  }
@@ -65,8 +106,14 @@ export function valueToLiteral(value: unknown): t.Expression | null {
65
106
  // A UnaryExpression lets the generator parenthesize negative BigInt where
66
107
  // precedence requires it.
67
108
  if (value < 0n) {
109
+ if (!reserveLiteralNodes(budget, 2)) {
110
+ return null
111
+ }
68
112
  return t.unaryExpression('-', t.bigIntLiteral(-value))
69
113
  }
114
+ if (!reserveLiteralNodes(budget, 1)) {
115
+ return null
116
+ }
70
117
  return t.bigIntLiteral(value)
71
118
  }
72
119
  if (typeof value === 'number') {
@@ -74,17 +121,30 @@ export function valueToLiteral(value: unknown): t.Expression | null {
74
121
  return null
75
122
  }
76
123
  if (Object.is(value, -0)) {
124
+ if (!reserveLiteralNodes(budget, 2)) {
125
+ return null
126
+ }
77
127
  return t.unaryExpression('-', t.numericLiteral(0))
78
128
  }
79
129
  if (value < 0) {
130
+ if (!reserveLiteralNodes(budget, 2)) {
131
+ return null
132
+ }
80
133
  return t.unaryExpression('-', t.numericLiteral(-value))
81
134
  }
135
+ if (!reserveLiteralNodes(budget, 1)) {
136
+ return null
137
+ }
82
138
  return t.numericLiteral(value)
83
139
  }
84
140
  if (Array.isArray(value)) {
141
+ if (seen.has(value) || !arrayHasLiteralShape(value) || !reserveLiteralNodes(budget, 1)) {
142
+ return null
143
+ }
144
+ seen.add(value)
85
145
  const elements: t.Expression[] = []
86
146
  for (const item of value) {
87
- const lit = valueToLiteral(item)
147
+ const lit = buildValueLiteral(item, seen, budget, depth + 1)
88
148
  if (!lit) {
89
149
  return null
90
150
  }
@@ -94,3 +154,32 @@ export function valueToLiteral(value: unknown): t.Expression | null {
94
154
  }
95
155
  return null
96
156
  }
157
+
158
+ function arrayHasLiteralShape(value: unknown[]): boolean {
159
+ if (Object.getPrototypeOf(value) !== Array.prototype || !Object.isExtensible(value)) {
160
+ return false
161
+ }
162
+ const names = Object.getOwnPropertyNames(value)
163
+ if (names.length !== value.length + 1 || names.at(-1) !== 'length') {
164
+ return false
165
+ }
166
+ for (let index = 0; index < value.length; index++) {
167
+ if (
168
+ names[index] !== String(index) ||
169
+ !isOrdinaryDataProperty(Object.getOwnPropertyDescriptor(value, String(index)))
170
+ ) {
171
+ return false
172
+ }
173
+ }
174
+ return Object.getOwnPropertySymbols(value).length === 0
175
+ }
176
+
177
+ function isOrdinaryDataProperty(descriptor: PropertyDescriptor | undefined): boolean {
178
+ return (
179
+ descriptor !== undefined &&
180
+ 'value' in descriptor &&
181
+ descriptor.configurable === true &&
182
+ descriptor.enumerable === true &&
183
+ descriptor.writable === true
184
+ )
185
+ }
@@ -0,0 +1,45 @@
1
+ import type { NodePath } from '@babel/traverse'
2
+ import type * as t from '@babel/types'
3
+ import type { File } from '@babel/types'
4
+
5
+ import { traverseForChanges } from './change-tracking'
6
+ import type { ChangeState } from './change-tracking'
7
+
8
+ export type StatementListContainer = t.Program | t.BlockStatement | t.StaticBlock | t.SwitchCase
9
+
10
+ type Rewrite = (
11
+ statements: NodePath<t.Statement>[],
12
+ container: NodePath<StatementListContainer>,
13
+ ) => t.Statement[] | null
14
+
15
+ /** Applies a rewrite to every statement-list container in the AST. */
16
+ export function rewriteStatementLists(ast: File, rewrite: Rewrite): boolean {
17
+ return traverseForChanges(ast, {
18
+ Program(path, state) {
19
+ apply(path, 'body', rewrite, state)
20
+ },
21
+ BlockStatement(path, state) {
22
+ apply(path, 'body', rewrite, state)
23
+ },
24
+ StaticBlock(path, state) {
25
+ apply(path, 'body', rewrite, state)
26
+ },
27
+ SwitchCase(path, state) {
28
+ apply(path, 'consequent', rewrite, state)
29
+ },
30
+ })
31
+ }
32
+
33
+ function apply(
34
+ container: NodePath<StatementListContainer>,
35
+ key: 'body' | 'consequent',
36
+ rewrite: Rewrite,
37
+ state: ChangeState,
38
+ ): void {
39
+ const statements = container.get(key) as NodePath<t.Statement>[]
40
+ const replacement = rewrite(statements, container)
41
+ if (replacement) {
42
+ container.set(key, replacement)
43
+ state.changed = true
44
+ }
45
+ }
package/DISCLOSURE DELETED
@@ -1,9 +0,0 @@
1
- webfuscator transforms and obfuscates JavaScript source code. Its features
2
- include identifier and property-name mangling, constant folding, dead-code
3
- removal, and control-flow and literal rewrites.
4
-
5
- These capabilities can make JavaScript harder to inspect and may have both
6
- legitimate and harmful uses. The intended use is transforming software that
7
- the user owns or is authorized to modify. This package is not intended to
8
- create, conceal, distribute, or operate malware, or to interfere with systems
9
- without authorization.
package/README.md DELETED
@@ -1,62 +0,0 @@
1
- `webfuscator` is a Babel-based JavaScript obfuscator. It parses a JavaScript source string, applies configurable transforms, and returns JavaScript source.
2
-
3
- [Read the documentation](https://webfuscator.mintlify.app/) for the quickstart, configuration guides, API details, and a reference page for every transform.
4
-
5
- ## Install
6
-
7
- ```sh
8
- npm install --save-dev webfuscator
9
- ```
10
-
11
- The package is ESM-only, includes TypeScript declarations, and requires Node.js `^22.18.0 || >=24.11.0`.
12
-
13
- ## Usage
14
-
15
- ```js
16
- import { obfuscate } from 'webfuscator'
17
-
18
- const source = `export default function square(value) {
19
- return value * value
20
- }`
21
-
22
- const output = obfuscate(source, {
23
- minify: true,
24
- seed: 0,
25
- transforms: {
26
- renameIdentifiers: true,
27
- },
28
- })
29
- ```
30
-
31
- `obfuscate(code, options?)` is synchronous. It does not execute the input or write files.
32
-
33
- Configurable transforms are disabled by default; preparation passes always run.
34
-
35
- ## Documentation
36
-
37
- - [Quickstart](https://webfuscator.mintlify.app/getting-started/quickstart)
38
- - [JavaScript API](https://webfuscator.mintlify.app/reference/api)
39
- - [Configure transforms](https://webfuscator.mintlify.app/guides/configure-transforms)
40
- - [Obfuscator options](https://webfuscator.mintlify.app/reference/options)
41
- - [Property mangling](https://webfuscator.mintlify.app/reference/property-mangling-options)
42
- - [String generator modes](https://webfuscator.mintlify.app/reference/string-generator-modes)
43
- - [Report a bug](https://webfuscator.mintlify.app/troubleshooting/report-a-bug)
44
-
45
- ## Development
46
-
47
- ```sh
48
- pnpm install
49
- pnpm test
50
- pnpm check
51
- pnpm docs:check
52
- ```
53
-
54
- Read [AGENTS.md](./AGENTS.md) before changing transforms or analysis code.
55
-
56
- ## Responsible use
57
-
58
- Use `webfuscator` only on software you own or are authorized to modify. Read [DISCLOSURE](./DISCLOSURE) for the project's dual-use policy.
59
-
60
- ## License
61
-
62
- [MIT](./LICENSE)