1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use std::path::PathBuf;
/// JavaScript string-literal encoding strategy.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum JsStringEncoding {
/// Leave string literals untouched.
None,
/// Per-character escapes, randomized over `\xHH` / `\uXXXX` / `\u{..}`
/// (strict-mode-safe, token-level). Default.
#[default]
Escapes,
/// Hoist string literals into a base64 array decoded by an injected runtime
/// prelude. Requires the AST engine ([`ObfuscationConfig::js_ast`]); falls
/// back to [`JsStringEncoding::Escapes`] when the AST engine is off.
Array,
}
/// Configuration for HTML obfuscation.
///
/// Cosmetic transforms default to `true`. Transforms that change the DOM,
/// output size, runtime cost, or accessibility are opt-in (`false`). Build via
/// the [`crate::Obfuscator`] builder.
#[derive(Debug, Clone)]
pub struct ObfuscationConfig {
// ---- HTML (cosmetic, on by default) ----
pub remove_comments: bool,
pub collapse_whitespace: bool,
pub encode_text_entities: bool,
pub encode_attr_entities: bool,
pub shuffle_attributes: bool,
pub randomize_tag_case: bool,
// ---- CSS (cosmetic, on by default) ----
pub rename_classes: bool,
pub rename_ids: bool,
pub minify_css: bool,
pub unicode_escape_selectors: bool,
// ---- JS (cosmetic, on by default) ----
pub js_string_encoding: JsStringEncoding,
pub minify_js: bool,
// ---- Honeypots / decoys (opt-in) ----
/// Inject invisible decoy links, fields, and classes to trap scrapers.
pub inject_honeypots: bool,
/// Number of decoy nodes to inject (when [`Self::inject_honeypots`]).
pub honeypot_count: usize,
// ---- Structural obfuscation (opt-in, WebCloak-style) ----
/// Move text content into encoded data-attributes and restore it
/// client-side via an injected script. Resists static scrapers but
/// requires JS execution and degrades no-JS / SEO / accessibility.
pub structural_obfuscation: bool,
// ---- AST-based JS engine (opt-in, oxc) ----
/// Route `<script>` JavaScript through the oxc AST pipeline instead of the
/// token state machine. Required by mangling / string arrays / CFF / dead code.
pub js_ast: bool,
/// Scope-aware renaming of local JS bindings (requires [`Self::js_ast`]).
pub mangle_identifiers: bool,
/// Flatten sequential control flow into a switch dispatcher (requires AST).
pub control_flow_flattening: bool,
/// Inject opaque-predicate-guarded dead code (requires AST).
pub dead_code_injection: bool,
/// Fraction (0.0..=1.0) of eligible sites that receive dead code.
pub dead_code_threshold: f32,
// ---- External resources (opt-in, local files only - stays offline) ----
/// Inline and obfuscate `<link rel=stylesheet>` / `<script src>` whose URL
/// resolves to a **local file** under [`Self::base_dir`]. Never fetches
/// over the network.
pub inline_local_resources: bool,
/// Base directory used to resolve local resource paths.
pub base_dir: Option<PathBuf>,
// ---- Polymorphism / determinism ----
/// Randomize *which* optional cosmetic transforms run and their intensity
/// on each invocation, so identical input yields structurally different
/// output every time (signature/cache evasion). Ignored when a `seed` is set.
pub polymorphic: bool,
/// Optional seed for deterministic output.
pub seed: Option<u64>,
}
impl Default for ObfuscationConfig {
fn default() -> Self {
Self {
remove_comments: true,
collapse_whitespace: true,
encode_text_entities: true,
encode_attr_entities: true,
shuffle_attributes: true,
randomize_tag_case: true,
rename_classes: true,
rename_ids: true,
minify_css: true,
unicode_escape_selectors: true,
js_string_encoding: JsStringEncoding::Escapes,
minify_js: true,
inject_honeypots: false,
honeypot_count: 6,
structural_obfuscation: false,
js_ast: false,
mangle_identifiers: false,
control_flow_flattening: false,
dead_code_injection: false,
dead_code_threshold: 0.4,
inline_local_resources: false,
base_dir: None,
polymorphic: false,
seed: None,
}
}
}
impl ObfuscationConfig {
/// Whether any AST-only JS transform is requested.
pub fn wants_ast(&self) -> bool {
self.js_ast
&& (self.mangle_identifiers
|| self.control_flow_flattening
|| self.dead_code_injection
|| self.js_string_encoding == JsStringEncoding::Array)
}
}