purifai 1.0.0 → 2.0.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.
package/dist/index.d.ts CHANGED
@@ -4,17 +4,15 @@
4
4
  * Advanced XSS protection with polyglot attack resistance.
5
5
  * Blocks sophisticated obfuscation techniques that bypass other sanitizers.
6
6
  *
7
- * @version 1.0.0
7
+ * @version 2.0.0
8
8
  * @license MIT
9
9
  */
10
10
  /**
11
11
  * Purifai Configuration Options
12
12
  */
13
- interface PurifaiOptions {
13
+ export interface PurifaiOptions {
14
14
  /** Maximum input length (default: 1MB) */
15
15
  maxLength?: number;
16
- /** Allow safe HTML tags like <b>, <i>, <p> (default: false for maximum security) */
17
- allowBasicHtml?: boolean;
18
16
  /** Custom allowed protocols (default: ['http', 'https', 'mailto']) */
19
17
  allowedProtocols?: string[];
20
18
  /** Enable aggressive mode for maximum security (default: true) */
@@ -23,7 +21,7 @@ interface PurifaiOptions {
23
21
  /**
24
22
  * Purifai Sanitization Result
25
23
  */
26
- interface PurifaiResult {
24
+ export interface PurifaiResult {
27
25
  /** Sanitized content */
28
26
  content: string;
29
27
  /** Whether dangerous content was detected */
@@ -33,14 +31,59 @@ interface PurifaiResult {
33
31
  /** Threat level: 'none' | 'low' | 'medium' | 'high' | 'critical' */
34
32
  threatLevel: 'none' | 'low' | 'medium' | 'high' | 'critical';
35
33
  }
34
+ /**
35
+ * Check if input contains dangerous patterns
36
+ */
37
+ declare function isDangerous(input: string): boolean;
38
+ /**
39
+ * Sanitize input with maximum security protection
40
+ */
41
+ declare function sanitize(input: unknown, options?: PurifaiOptions): string;
42
+ /**
43
+ * Sanitize input and return detailed analysis
44
+ */
45
+ declare function analyze(input: unknown, options?: PurifaiOptions): PurifaiResult;
46
+ /**
47
+ * Batch sanitize multiple inputs for optimal performance
48
+ */
49
+ declare function sanitizeBatch(inputs: unknown[], options?: PurifaiOptions): string[];
50
+ /**
51
+ * Escape text for insertion into an HTML body context.
52
+ *
53
+ * Lossless and total: nothing is removed, so this is the correct choice when
54
+ * the input is plain text rather than markup. `sanitize()` cannot tell the two
55
+ * apart — `a<b && c>d` is a valid HTML start tag by the parsing spec, so
56
+ * `sanitize()` drops it while `escape()` preserves it verbatim.
57
+ */
58
+ declare function escape(input: unknown): string;
59
+ /**
60
+ * Escape text for insertion into an HTML attribute value.
61
+ *
62
+ * Stricter than `escape()`: every character outside `[a-zA-Z0-9]` is
63
+ * hex-encoded, which stays safe even in an unquoted attribute — the case that
64
+ * breaks naive escaping, since a bare space or backtick can end the value and
65
+ * start a new attribute such as `onerror=`.
66
+ */
67
+ declare function escapeAttribute(input: unknown): string;
68
+ /**
69
+ * Escape and validate a value used as a URL.
70
+ *
71
+ * Returns '' when the protocol is not in `allowedProtocols`, which is what
72
+ * stops `javascript:`, `data:` and friends from reaching an `href`. Relative
73
+ * URLs are allowed through, since they cannot carry a protocol.
74
+ */
75
+ declare function escapeUrl(input: unknown, options?: PurifaiOptions): string;
36
76
  /**
37
77
  * 🛡️ Purifai - Ultra-Secure HTML Sanitizer
38
78
  *
39
79
  * Advanced lightweight HTML sanitizer with superior XSS protection
40
80
  * against known attack vectors including advanced polyglot attacks.
81
+ *
82
+ * Every method delegates to a module-level function and reads no instance or
83
+ * class state, so `Purifai.sanitize` and the standalone `sanitize` export are
84
+ * interchangeable and neither depends on its call-site receiver.
41
85
  */
42
- declare class Purifai {
43
- private static defaultOptions;
86
+ export declare class Purifai {
44
87
  /**
45
88
  * Sanitize input with maximum security protection
46
89
  *
@@ -73,18 +116,6 @@ declare class Purifai {
73
116
  * ```
74
117
  */
75
118
  static analyze(input: unknown, options?: PurifaiOptions): PurifaiResult;
76
- /**
77
- * Decode common encoding bypasses
78
- */
79
- private static decodeEncodingBypasses;
80
- /**
81
- * Enhanced polyglot attack handling - Addresses sophisticated XSS techniques
82
- */
83
- private static handlePolyglotAttacks;
84
- /**
85
- * Assess threat level of input content
86
- */
87
- private static assessThreatLevel;
88
119
  /**
89
120
  * Check if input contains dangerous patterns
90
121
  *
@@ -115,6 +146,34 @@ declare class Purifai {
115
146
  * ```
116
147
  */
117
148
  static sanitizeBatch(inputs: unknown[], options?: PurifaiOptions): string[];
149
+ /**
150
+ * Escape text for an HTML body context (lossless — nothing is removed)
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * Purifai.escape('if (a<b && c>d)'); // "if (a&lt;b &amp;&amp; c&gt;d)"
155
+ * ```
156
+ */
157
+ static escape(input: unknown): string;
158
+ /**
159
+ * Escape text for an HTML attribute value, safe even when unquoted
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * `<div title="${Purifai.escapeAttribute(userInput)}">`
164
+ * ```
165
+ */
166
+ static escapeAttribute(input: unknown): string;
167
+ /**
168
+ * Escape a URL, returning '' if its protocol is not allowed
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * Purifai.escapeUrl('javascript:alert(1)'); // ""
173
+ * Purifai.escapeUrl('https://example.com'); // escaped, safe for href
174
+ * ```
175
+ */
176
+ static escapeUrl(input: unknown, options?: PurifaiOptions): string;
118
177
  /**
119
178
  * Get version information
120
179
  */
@@ -128,9 +187,5 @@ declare class Purifai {
128
187
  performance: string;
129
188
  };
130
189
  }
131
- declare const sanitize: typeof Purifai.sanitize;
132
- declare const analyze: typeof Purifai.analyze;
133
- declare const isDangerous: typeof Purifai.isDangerous;
134
- declare const sanitizeBatch: typeof Purifai.sanitizeBatch;
135
-
136
- export { Purifai, type PurifaiOptions, type PurifaiResult, analyze, Purifai as default, isDangerous, sanitize, sanitizeBatch };
190
+ export { sanitize, analyze, isDangerous, sanitizeBatch, escape, escapeAttribute, escapeUrl };
191
+ export default Purifai;
package/dist/index.js CHANGED
@@ -9,68 +9,256 @@ var NULL_CONTROL_CHARS = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g;
9
9
  var POLYGLOT_JAVASCRIPT = /j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*:/gi;
10
10
  var POLYGLOT_EVENTS = /o\s*n\s*[a-z]+\s*(?:=|alert)/gi;
11
11
  var ENCODED_TAGS = /\\x3c|\\x3e|%3c|%3e/gi;
12
- var SUSPICIOUS_PATTERNS = /<|>|javascript:|vbscript:|on\w+\s*=|@import|\{\{|<%|<\?|\${|#\{/i;
12
+ var SUSPICIOUS_PATTERNS = /<|>|javascript:|vbscript:|\bon[a-z]+\s*=|@import|\{\{|<%|<\?|\${|#\{/i;
13
+ var DANGEROUS_PROTOCOL_TOKEN = /(?:j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t|v\s*b\s*s\s*c\s*r\s*i\s*p\s*t|l\s*i\s*v\s*e\s*s\s*c\s*r\s*i\s*p\s*t|m\s*o\s*c\s*h\s*a)\s*:|data\s*:\s*text\/html/i;
14
+ var HAS_TEXT_CONTENT = /[a-z0-9]/i;
15
+ var HTML_TAG = /<\/?[a-zA-Z][^>]*>/g;
16
+ function escapeAngleBrackets(str) {
17
+ return str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
18
+ }
19
+ function stateless(pattern) {
20
+ return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
21
+ }
22
+ var DETECT_DANGEROUS_TAGS = stateless(DANGEROUS_TAGS_WITH_CONTENT);
23
+ var DETECT_EVENT_HANDLERS = stateless(EVENT_HANDLERS_ENHANCED);
24
+ var DETECT_DANGEROUS_PROTOCOLS = stateless(DANGEROUS_PROTOCOLS_ENHANCED);
25
+ var DETECT_DANGEROUS_ATTRIBUTES = stateless(DANGEROUS_ATTRIBUTES);
26
+ var DETECT_TEMPLATE_INJECTION = stateless(TEMPLATE_INJECTION);
27
+ var DETECT_POLYGLOT_JAVASCRIPT = stateless(POLYGLOT_JAVASCRIPT);
28
+ var DETECT_POLYGLOT_EVENTS = stateless(POLYGLOT_EVENTS);
29
+ var DEFAULT_OPTIONS = {
30
+ maxLength: 1e6,
31
+ // 1MB
32
+ allowedProtocols: ["http", "https", "mailto"],
33
+ aggressiveMode: true
34
+ };
35
+ var VERSION = "2.0.0";
36
+ var MAX_FILTER_PASSES = 5;
37
+ function decodeEncodingBypasses(str) {
38
+ let result = str;
39
+ try {
40
+ result = decodeURIComponent(result);
41
+ } catch {
42
+ }
43
+ try {
44
+ result = result.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => {
45
+ return String.fromCharCode(parseInt(hex, 16));
46
+ });
47
+ result = result.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => {
48
+ return String.fromCharCode(parseInt(hex, 16));
49
+ });
50
+ result = result.replace(/&#(\d+);/g, (_, num) => {
51
+ return String.fromCharCode(parseInt(num, 10));
52
+ });
53
+ result = result.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => {
54
+ return String.fromCharCode(parseInt(hex, 16));
55
+ });
56
+ } catch {
57
+ }
58
+ return result;
59
+ }
60
+ function removeDangerousTagsWithContent(str) {
61
+ return str.replace(DANGEROUS_TAGS_WITH_CONTENT, "");
62
+ }
63
+ function handlePolyglotAttacks(str) {
64
+ let result = str;
65
+ try {
66
+ result = result.replace(POLYGLOT_JAVASCRIPT, "");
67
+ result = result.replace(POLYGLOT_EVENTS, "");
68
+ result = result.replace(ENCODED_TAGS, "");
69
+ result = result.replace(/javascript\s*:\s*\/\*[\s\S]*?\*\//gi, "");
70
+ result = result.replace(/javascript\s*:\s*\/\*[^*]*\*\/[^>]*/gi, "");
71
+ result = result.replace(/<form[^>]*>[\s\S]*?<\/form>/gi, "");
72
+ result = result.replace(/<math[^>]*>[\s\S]*?<\/math>/gi, "");
73
+ result = result.replace(/%0[AD]/gi, "");
74
+ if (/svg|sVg|\x3c|\\\w{3}/i.test(result)) {
75
+ result = result.replace(/svg[^>]*>/gi, "");
76
+ result = result.replace(/sVg[^>]*>/gi, "");
77
+ result = result.replace(/\\x\w{2}/gi, "");
78
+ result = result.replace(/<\/[^>]*>/gi, "");
79
+ result = result.replace(/<[^>]*>/gi, "");
80
+ }
81
+ } catch {
82
+ result = result.replace(/<[^>]*>/g, "").replace(/[<>]/g, "");
83
+ }
84
+ return result;
85
+ }
86
+ function assessThreatLevel(input) {
87
+ if (!input) return "none";
88
+ if (/<script/i.test(input) || /javascript\s*:/i.test(input) || /\bon[a-z]+\s*=/i.test(input)) {
89
+ return "critical";
90
+ }
91
+ if (/<(iframe|object|embed|form|svg|math)/i.test(input) || /eval\s*\(/i.test(input)) {
92
+ return "high";
93
+ }
94
+ if (/<(style|link|meta)/i.test(input) || /expression\s*\(/i.test(input)) {
95
+ return "medium";
96
+ }
97
+ if (/<[^>]*>/i.test(input)) {
98
+ return "low";
99
+ }
100
+ return "none";
101
+ }
102
+ function isDangerous(input) {
103
+ if (!input) return false;
104
+ try {
105
+ return DETECT_DANGEROUS_TAGS.test(input) || DETECT_EVENT_HANDLERS.test(input) || DETECT_DANGEROUS_PROTOCOLS.test(input) || DETECT_DANGEROUS_ATTRIBUTES.test(input) || DETECT_TEMPLATE_INJECTION.test(input) || DETECT_POLYGLOT_JAVASCRIPT.test(input) || DETECT_POLYGLOT_EVENTS.test(input) || DANGEROUS_PROTOCOL_TOKEN.test(input);
106
+ } catch {
107
+ return true;
108
+ }
109
+ }
110
+ function sanitize(input, options) {
111
+ const config = { ...DEFAULT_OPTIONS, ...options };
112
+ if (input === null || input === void 0) return "";
113
+ let str;
114
+ try {
115
+ if (typeof input === "string") {
116
+ str = input;
117
+ } else if (typeof input === "object") {
118
+ str = JSON.stringify(input);
119
+ } else {
120
+ str = String(input);
121
+ }
122
+ } catch {
123
+ return "";
124
+ }
125
+ if (str.length > config.maxLength) {
126
+ str = str.substring(0, config.maxLength);
127
+ }
128
+ if (!str.trim()) return "";
129
+ let result = str.replace(NULL_CONTROL_CHARS, "");
130
+ try {
131
+ result = decodeEncodingBypasses(result);
132
+ const dangerous = isDangerous(str) || isDangerous(result);
133
+ if (!dangerous) {
134
+ const benign = removeDangerousTagsWithContent(result).replace(HTML_TAG, "").replace(DANGEROUS_ATTRIBUTES, "").replace(TEMPLATE_INJECTION, "").replace(DANGEROUS_FUNCTIONS, "");
135
+ return escapeAngleBrackets(benign).replace(/\s+/g, " ").trim();
136
+ }
137
+ result = removeDangerousTagsWithContent(result);
138
+ result = handlePolyglotAttacks(result);
139
+ let previous;
140
+ let passes = 0;
141
+ do {
142
+ previous = result;
143
+ result = result.replace(DANGEROUS_TAGS_WITH_CONTENT, "").replace(EVENT_HANDLERS_ENHANCED, "").replace(DANGEROUS_PROTOCOLS_ENHANCED, (match) => {
144
+ const safeProtocols = config.allowedProtocols.join("|");
145
+ return match.match(new RegExp(`^(?:href|src)\\s*=\\s*["']?\\s*(?:${safeProtocols}):\\/\\/`, "i")) ? match : "";
146
+ }).replace(DANGEROUS_ATTRIBUTES, "").replace(TEMPLATE_INJECTION, "").replace(DANGEROUS_FUNCTIONS, "");
147
+ passes++;
148
+ } while (result !== previous && passes < MAX_FILTER_PASSES);
149
+ if (config.aggressiveMode && SUSPICIOUS_PATTERNS.test(result)) {
150
+ const tempCheck = result.replace(/<[^>]*>/g, "");
151
+ if (SUSPICIOUS_PATTERNS.test(tempCheck)) {
152
+ result = result.replace(/<[^>]*>/g, "").replace(/[<>]/g, "").replace(/javascript|vbscript/gi, "").replace(/\bon[a-z]+\s*=/gi, "").replace(/svg|SVG|sVg/gi, "").replace(/script|SCRIPT/gi, "").replace(/alert|eval/gi, "").replace(/\\x\w{2}/gi, "").replace(/[(){}[\]]/g, "");
153
+ }
154
+ }
155
+ result = result.replace(/\s+/g, " ").trim();
156
+ if (config.aggressiveMode && !HAS_TEXT_CONTENT.test(result)) {
157
+ return "";
158
+ }
159
+ return escapeAngleBrackets(result);
160
+ } catch {
161
+ return str.replace(NULL_CONTROL_CHARS, "").replace(/<[^>]*>/g, "").replace(/[<>]/g, "").replace(/javascript|vbscript|eval|alert/gi, "").replace(/\bon[a-z]+\s*=/gi, "").replace(/\s+/g, " ").trim();
162
+ }
163
+ }
164
+ function analyze(input, options) {
165
+ const startTime = performance.now();
166
+ const originalStr = String(input ?? "");
167
+ const sanitized = sanitize(input, options);
168
+ const processingTime = performance.now() - startTime;
169
+ const hadThreats = sanitized !== originalStr;
170
+ const threatLevel = assessThreatLevel(originalStr);
171
+ return {
172
+ content: sanitized,
173
+ hadThreats,
174
+ processingTime,
175
+ threatLevel
176
+ };
177
+ }
178
+ function sanitizeBatch(inputs, options) {
179
+ return inputs.map((input) => {
180
+ try {
181
+ return sanitize(input, options);
182
+ } catch {
183
+ return "";
184
+ }
185
+ });
186
+ }
187
+ function escape(input) {
188
+ if (input === null || input === void 0) return "";
189
+ const str = typeof input === "string" ? input : String(input);
190
+ return str.replace(NULL_CONTROL_CHARS, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
191
+ }
192
+ function escapeAttribute(input) {
193
+ if (input === null || input === void 0) return "";
194
+ const str = (typeof input === "string" ? input : String(input)).replace(NULL_CONTROL_CHARS, "");
195
+ let out = "";
196
+ for (const char of str) {
197
+ const code = char.codePointAt(0);
198
+ if (/[a-zA-Z0-9]/.test(char)) {
199
+ out += char;
200
+ } else if (code < 256) {
201
+ out += `&#x${code.toString(16).padStart(2, "0")};`;
202
+ } else {
203
+ out += `&#x${code.toString(16)};`;
204
+ }
205
+ }
206
+ return out;
207
+ }
208
+ function escapeUrl(input, options) {
209
+ if (input === null || input === void 0) return "";
210
+ const config = { ...DEFAULT_OPTIONS, ...options };
211
+ const str = (typeof input === "string" ? input : String(input)).trim();
212
+ if (!str) return "";
213
+ const normalized = str.replace(/[\x00-\x20\x7F-\x9F]/g, "");
214
+ const colon = normalized.indexOf(":");
215
+ if (colon !== -1) {
216
+ const beforeColon = normalized.slice(0, colon);
217
+ if (!/[/?#]/.test(beforeColon)) {
218
+ const protocol = beforeColon.toLowerCase();
219
+ if (!config.allowedProtocols.map((p) => p.toLowerCase()).includes(protocol)) {
220
+ return "";
221
+ }
222
+ }
223
+ }
224
+ return escapeAttribute(normalized);
225
+ }
226
+ function getVersion() {
227
+ return VERSION;
228
+ }
229
+ function getStats() {
230
+ return {
231
+ version: getVersion(),
232
+ securityLevel: "100% XSS Protection",
233
+ performance: "Optimized for high-throughput"
234
+ };
235
+ }
13
236
  var Purifai = class {
14
237
  /**
15
238
  * Sanitize input with maximum security protection
16
- *
239
+ *
17
240
  * @param input - Content to sanitize (string, object, or any type)
18
241
  * @param options - Optional configuration
19
242
  * @returns Sanitized string safe for HTML output
20
- *
243
+ *
21
244
  * @example
22
245
  * ```typescript
23
246
  * import { Purifai } from 'purifai';
24
- *
247
+ *
25
248
  * const clean = Purifai.sanitize('<script>alert("xss")</script>Hello World');
26
249
  * console.log(clean); // "Hello World"
27
250
  * ```
28
251
  */
29
252
  static sanitize(input, options) {
30
- const config = { ...this.defaultOptions, ...options };
31
- if (input === null || input === void 0) return "";
32
- let str;
33
- try {
34
- if (typeof input === "string") {
35
- str = input;
36
- } else if (typeof input === "object") {
37
- str = JSON.stringify(input);
38
- } else {
39
- str = String(input);
40
- }
41
- } catch {
42
- return "";
43
- }
44
- if (str.length > config.maxLength) {
45
- str = str.substring(0, config.maxLength);
46
- }
47
- if (!str.trim()) return "";
48
- let result = str.replace(NULL_CONTROL_CHARS, "");
49
- try {
50
- result = this.decodeEncodingBypasses(result);
51
- result = this.handlePolyglotAttacks(result);
52
- result = result.replace(DANGEROUS_TAGS_WITH_CONTENT, "").replace(EVENT_HANDLERS_ENHANCED, "").replace(DANGEROUS_PROTOCOLS_ENHANCED, (match) => {
53
- const safeProtocols = config.allowedProtocols.join("|");
54
- return match.match(new RegExp(`^(?:href|src)\\s*=\\s*["']?\\s*(?:${safeProtocols}):\\/\\/`, "i")) ? match : "";
55
- }).replace(DANGEROUS_ATTRIBUTES, "").replace(TEMPLATE_INJECTION, "").replace(DANGEROUS_FUNCTIONS, "");
56
- if (config.aggressiveMode && SUSPICIOUS_PATTERNS.test(result)) {
57
- const tempCheck = result.replace(/<[^>]*>/g, "");
58
- if (SUSPICIOUS_PATTERNS.test(tempCheck)) {
59
- result = result.replace(/<[^>]*>/g, "").replace(/[<>]/g, "").replace(/javascript|vbscript/gi, "").replace(/on\w+\s*=/gi, "").replace(/svg|SVG|sVg/gi, "").replace(/script|SCRIPT/gi, "").replace(/alert|eval/gi, "").replace(/\\x\w{2}/gi, "").replace(/[(){}[\]]/g, "");
60
- }
61
- }
62
- return result.replace(/\s+/g, " ").trim();
63
- } catch (error) {
64
- return str.replace(NULL_CONTROL_CHARS, "").replace(/<[^>]*>/g, "").replace(/[<>]/g, "").replace(/javascript|vbscript|eval|alert/gi, "").replace(/on\w+\s*=/gi, "").replace(/\s+/g, " ").trim();
65
- }
253
+ return sanitize(input, options);
66
254
  }
67
255
  /**
68
256
  * Sanitize input and return detailed analysis
69
- *
257
+ *
70
258
  * @param input - Content to sanitize
71
259
  * @param options - Optional configuration
72
260
  * @returns Detailed sanitization result with threat analysis
73
- *
261
+ *
74
262
  * @example
75
263
  * ```typescript
76
264
  * const result = Purifai.analyze('<script>alert("xss")</script>Hello');
@@ -80,93 +268,14 @@ var Purifai = class {
80
268
  * ```
81
269
  */
82
270
  static analyze(input, options) {
83
- const startTime = performance.now();
84
- const originalStr = String(input || "");
85
- const sanitized = this.sanitize(input, options);
86
- const processingTime = performance.now() - startTime;
87
- const hadThreats = sanitized !== originalStr;
88
- const threatLevel = this.assessThreatLevel(originalStr);
89
- return {
90
- content: sanitized,
91
- hadThreats,
92
- processingTime,
93
- threatLevel
94
- };
95
- }
96
- /**
97
- * Decode common encoding bypasses
98
- */
99
- static decodeEncodingBypasses(str) {
100
- let result = str;
101
- try {
102
- result = decodeURIComponent(result);
103
- result = result.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => {
104
- return String.fromCharCode(parseInt(hex, 16));
105
- });
106
- result = result.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => {
107
- return String.fromCharCode(parseInt(hex, 16));
108
- });
109
- result = result.replace(/&#(\d+);/g, (_, num) => {
110
- return String.fromCharCode(parseInt(num, 10));
111
- });
112
- result = result.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => {
113
- return String.fromCharCode(parseInt(hex, 16));
114
- });
115
- } catch {
116
- }
117
- return result;
118
- }
119
- /**
120
- * Enhanced polyglot attack handling - Addresses sophisticated XSS techniques
121
- */
122
- static handlePolyglotAttacks(str) {
123
- let result = str;
124
- try {
125
- result = result.replace(POLYGLOT_JAVASCRIPT, "");
126
- result = result.replace(POLYGLOT_EVENTS, "");
127
- result = result.replace(ENCODED_TAGS, "");
128
- result = result.replace(/javascript\s*:\s*\/\*[\s\S]*?\*\//gi, "");
129
- result = result.replace(/javascript\s*:\s*\/\*[^*]*\*\/[^>]*/gi, "");
130
- result = result.replace(/<form[^>]*>[\s\S]*?<\/form>/gi, "");
131
- result = result.replace(/<math[^>]*>[\s\S]*?<\/math>/gi, "");
132
- result = result.replace(/%0[AD]/gi, "");
133
- if (/svg|sVg|\x3c|\\\w{3}/i.test(result)) {
134
- result = result.replace(/svg[^>]*>/gi, "");
135
- result = result.replace(/sVg[^>]*>/gi, "");
136
- result = result.replace(/\\x\w{2}/gi, "");
137
- result = result.replace(/<\/[^>]*>/gi, "");
138
- result = result.replace(/<[^>]*>/gi, "");
139
- }
140
- } catch {
141
- result = result.replace(/<[^>]*>/g, "").replace(/[<>]/g, "");
142
- }
143
- return result;
144
- }
145
- /**
146
- * Assess threat level of input content
147
- */
148
- static assessThreatLevel(input) {
149
- if (!input) return "none";
150
- if (/<script/i.test(input) || /javascript\s*:/i.test(input) || /on\w+\s*=/i.test(input)) {
151
- return "critical";
152
- }
153
- if (/<(iframe|object|embed|form|svg|math)/i.test(input) || /eval\s*\(/i.test(input)) {
154
- return "high";
155
- }
156
- if (/<(style|link|meta)/i.test(input) || /expression\s*\(/i.test(input)) {
157
- return "medium";
158
- }
159
- if (/<[^>]*>/i.test(input)) {
160
- return "low";
161
- }
162
- return "none";
271
+ return analyze(input, options);
163
272
  }
164
273
  /**
165
274
  * Check if input contains dangerous patterns
166
- *
275
+ *
167
276
  * @param input - Content to check
168
277
  * @returns true if dangerous content detected
169
- *
278
+ *
170
279
  * @example
171
280
  * ```typescript
172
281
  * const isDangerous = Purifai.isDangerous('<script>alert("xss")</script>');
@@ -174,20 +283,15 @@ var Purifai = class {
174
283
  * ```
175
284
  */
176
285
  static isDangerous(input) {
177
- if (!input) return false;
178
- try {
179
- return DANGEROUS_TAGS_WITH_CONTENT.test(input) || EVENT_HANDLERS_ENHANCED.test(input) || DANGEROUS_PROTOCOLS_ENHANCED.test(input) || DANGEROUS_ATTRIBUTES.test(input) || TEMPLATE_INJECTION.test(input) || POLYGLOT_JAVASCRIPT.test(input) || POLYGLOT_EVENTS.test(input);
180
- } catch {
181
- return true;
182
- }
286
+ return isDangerous(input);
183
287
  }
184
288
  /**
185
289
  * Batch sanitize multiple inputs for optimal performance
186
- *
290
+ *
187
291
  * @param inputs - Array of inputs to sanitize
188
292
  * @param options - Optional configuration
189
293
  * @returns Array of sanitized strings
190
- *
294
+ *
191
295
  * @example
192
296
  * ```typescript
193
297
  * const cleaned = Purifai.sanitizeBatch([
@@ -198,57 +302,73 @@ var Purifai = class {
198
302
  * ```
199
303
  */
200
304
  static sanitizeBatch(inputs, options) {
201
- return inputs.map((input) => {
202
- try {
203
- return this.sanitize(input, options);
204
- } catch {
205
- return "";
206
- }
207
- });
305
+ return sanitizeBatch(inputs, options);
306
+ }
307
+ /**
308
+ * Escape text for an HTML body context (lossless — nothing is removed)
309
+ *
310
+ * @example
311
+ * ```typescript
312
+ * Purifai.escape('if (a<b && c>d)'); // "if (a&lt;b &amp;&amp; c&gt;d)"
313
+ * ```
314
+ */
315
+ static escape(input) {
316
+ return escape(input);
317
+ }
318
+ /**
319
+ * Escape text for an HTML attribute value, safe even when unquoted
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * `<div title="${Purifai.escapeAttribute(userInput)}">`
324
+ * ```
325
+ */
326
+ static escapeAttribute(input) {
327
+ return escapeAttribute(input);
328
+ }
329
+ /**
330
+ * Escape a URL, returning '' if its protocol is not allowed
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * Purifai.escapeUrl('javascript:alert(1)'); // ""
335
+ * Purifai.escapeUrl('https://example.com'); // escaped, safe for href
336
+ * ```
337
+ */
338
+ static escapeUrl(input, options) {
339
+ return escapeUrl(input, options);
208
340
  }
209
341
  /**
210
342
  * Get version information
211
343
  */
212
344
  static getVersion() {
213
- return "1.0.0";
345
+ return getVersion();
214
346
  }
215
347
  /**
216
348
  * Get performance and security statistics
217
349
  */
218
350
  static getStats() {
219
- return {
220
- version: this.getVersion(),
221
- securityLevel: "100% XSS Protection",
222
- performance: "Optimized for high-throughput"
223
- };
351
+ return getStats();
224
352
  }
225
353
  };
226
- Purifai.defaultOptions = {
227
- maxLength: 1e6,
228
- // 1MB
229
- allowBasicHtml: false,
230
- allowedProtocols: ["http", "https", "mailto"],
231
- aggressiveMode: true
232
- };
233
- var sanitize = Purifai.sanitize;
234
- var analyze = Purifai.analyze;
235
- var isDangerous = Purifai.isDangerous;
236
- var sanitizeBatch = Purifai.sanitizeBatch;
237
354
  var index_default = Purifai;
238
355
  export {
239
356
  Purifai,
240
357
  analyze,
241
358
  index_default as default,
359
+ escape,
360
+ escapeAttribute,
361
+ escapeUrl,
242
362
  isDangerous,
243
363
  sanitize,
244
364
  sanitizeBatch
245
365
  };
246
366
  /**
247
367
  * 🛡️ PURIFAI - Ultra-Secure HTML Sanitizer
248
- *
368
+ *
249
369
  * Advanced XSS protection with polyglot attack resistance.
250
370
  * Blocks sophisticated obfuscation techniques that bypass other sanitizers.
251
- *
252
- * @version 1.0.0
371
+ *
372
+ * @version 2.0.0
253
373
  * @license MIT
254
374
  */