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.cjs CHANGED
@@ -23,6 +23,9 @@ __export(index_exports, {
23
23
  Purifai: () => Purifai,
24
24
  analyze: () => analyze,
25
25
  default: () => index_default,
26
+ escape: () => escape,
27
+ escapeAttribute: () => escapeAttribute,
28
+ escapeUrl: () => escapeUrl,
26
29
  isDangerous: () => isDangerous,
27
30
  sanitize: () => sanitize,
28
31
  sanitizeBatch: () => sanitizeBatch
@@ -38,68 +41,256 @@ var NULL_CONTROL_CHARS = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g;
38
41
  var POLYGLOT_JAVASCRIPT = /j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*:/gi;
39
42
  var POLYGLOT_EVENTS = /o\s*n\s*[a-z]+\s*(?:=|alert)/gi;
40
43
  var ENCODED_TAGS = /\\x3c|\\x3e|%3c|%3e/gi;
41
- var SUSPICIOUS_PATTERNS = /<|>|javascript:|vbscript:|on\w+\s*=|@import|\{\{|<%|<\?|\${|#\{/i;
44
+ var SUSPICIOUS_PATTERNS = /<|>|javascript:|vbscript:|\bon[a-z]+\s*=|@import|\{\{|<%|<\?|\${|#\{/i;
45
+ 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;
46
+ var HAS_TEXT_CONTENT = /[a-z0-9]/i;
47
+ var HTML_TAG = /<\/?[a-zA-Z][^>]*>/g;
48
+ function escapeAngleBrackets(str) {
49
+ return str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
50
+ }
51
+ function stateless(pattern) {
52
+ return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
53
+ }
54
+ var DETECT_DANGEROUS_TAGS = stateless(DANGEROUS_TAGS_WITH_CONTENT);
55
+ var DETECT_EVENT_HANDLERS = stateless(EVENT_HANDLERS_ENHANCED);
56
+ var DETECT_DANGEROUS_PROTOCOLS = stateless(DANGEROUS_PROTOCOLS_ENHANCED);
57
+ var DETECT_DANGEROUS_ATTRIBUTES = stateless(DANGEROUS_ATTRIBUTES);
58
+ var DETECT_TEMPLATE_INJECTION = stateless(TEMPLATE_INJECTION);
59
+ var DETECT_POLYGLOT_JAVASCRIPT = stateless(POLYGLOT_JAVASCRIPT);
60
+ var DETECT_POLYGLOT_EVENTS = stateless(POLYGLOT_EVENTS);
61
+ var DEFAULT_OPTIONS = {
62
+ maxLength: 1e6,
63
+ // 1MB
64
+ allowedProtocols: ["http", "https", "mailto"],
65
+ aggressiveMode: true
66
+ };
67
+ var VERSION = "2.0.0";
68
+ var MAX_FILTER_PASSES = 5;
69
+ function decodeEncodingBypasses(str) {
70
+ let result = str;
71
+ try {
72
+ result = decodeURIComponent(result);
73
+ } catch {
74
+ }
75
+ try {
76
+ result = result.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => {
77
+ return String.fromCharCode(parseInt(hex, 16));
78
+ });
79
+ result = result.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => {
80
+ return String.fromCharCode(parseInt(hex, 16));
81
+ });
82
+ result = result.replace(/&#(\d+);/g, (_, num) => {
83
+ return String.fromCharCode(parseInt(num, 10));
84
+ });
85
+ result = result.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => {
86
+ return String.fromCharCode(parseInt(hex, 16));
87
+ });
88
+ } catch {
89
+ }
90
+ return result;
91
+ }
92
+ function removeDangerousTagsWithContent(str) {
93
+ return str.replace(DANGEROUS_TAGS_WITH_CONTENT, "");
94
+ }
95
+ function handlePolyglotAttacks(str) {
96
+ let result = str;
97
+ try {
98
+ result = result.replace(POLYGLOT_JAVASCRIPT, "");
99
+ result = result.replace(POLYGLOT_EVENTS, "");
100
+ result = result.replace(ENCODED_TAGS, "");
101
+ result = result.replace(/javascript\s*:\s*\/\*[\s\S]*?\*\//gi, "");
102
+ result = result.replace(/javascript\s*:\s*\/\*[^*]*\*\/[^>]*/gi, "");
103
+ result = result.replace(/<form[^>]*>[\s\S]*?<\/form>/gi, "");
104
+ result = result.replace(/<math[^>]*>[\s\S]*?<\/math>/gi, "");
105
+ result = result.replace(/%0[AD]/gi, "");
106
+ if (/svg|sVg|\x3c|\\\w{3}/i.test(result)) {
107
+ result = result.replace(/svg[^>]*>/gi, "");
108
+ result = result.replace(/sVg[^>]*>/gi, "");
109
+ result = result.replace(/\\x\w{2}/gi, "");
110
+ result = result.replace(/<\/[^>]*>/gi, "");
111
+ result = result.replace(/<[^>]*>/gi, "");
112
+ }
113
+ } catch {
114
+ result = result.replace(/<[^>]*>/g, "").replace(/[<>]/g, "");
115
+ }
116
+ return result;
117
+ }
118
+ function assessThreatLevel(input) {
119
+ if (!input) return "none";
120
+ if (/<script/i.test(input) || /javascript\s*:/i.test(input) || /\bon[a-z]+\s*=/i.test(input)) {
121
+ return "critical";
122
+ }
123
+ if (/<(iframe|object|embed|form|svg|math)/i.test(input) || /eval\s*\(/i.test(input)) {
124
+ return "high";
125
+ }
126
+ if (/<(style|link|meta)/i.test(input) || /expression\s*\(/i.test(input)) {
127
+ return "medium";
128
+ }
129
+ if (/<[^>]*>/i.test(input)) {
130
+ return "low";
131
+ }
132
+ return "none";
133
+ }
134
+ function isDangerous(input) {
135
+ if (!input) return false;
136
+ try {
137
+ 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);
138
+ } catch {
139
+ return true;
140
+ }
141
+ }
142
+ function sanitize(input, options) {
143
+ const config = { ...DEFAULT_OPTIONS, ...options };
144
+ if (input === null || input === void 0) return "";
145
+ let str;
146
+ try {
147
+ if (typeof input === "string") {
148
+ str = input;
149
+ } else if (typeof input === "object") {
150
+ str = JSON.stringify(input);
151
+ } else {
152
+ str = String(input);
153
+ }
154
+ } catch {
155
+ return "";
156
+ }
157
+ if (str.length > config.maxLength) {
158
+ str = str.substring(0, config.maxLength);
159
+ }
160
+ if (!str.trim()) return "";
161
+ let result = str.replace(NULL_CONTROL_CHARS, "");
162
+ try {
163
+ result = decodeEncodingBypasses(result);
164
+ const dangerous = isDangerous(str) || isDangerous(result);
165
+ if (!dangerous) {
166
+ const benign = removeDangerousTagsWithContent(result).replace(HTML_TAG, "").replace(DANGEROUS_ATTRIBUTES, "").replace(TEMPLATE_INJECTION, "").replace(DANGEROUS_FUNCTIONS, "");
167
+ return escapeAngleBrackets(benign).replace(/\s+/g, " ").trim();
168
+ }
169
+ result = removeDangerousTagsWithContent(result);
170
+ result = handlePolyglotAttacks(result);
171
+ let previous;
172
+ let passes = 0;
173
+ do {
174
+ previous = result;
175
+ result = result.replace(DANGEROUS_TAGS_WITH_CONTENT, "").replace(EVENT_HANDLERS_ENHANCED, "").replace(DANGEROUS_PROTOCOLS_ENHANCED, (match) => {
176
+ const safeProtocols = config.allowedProtocols.join("|");
177
+ return match.match(new RegExp(`^(?:href|src)\\s*=\\s*["']?\\s*(?:${safeProtocols}):\\/\\/`, "i")) ? match : "";
178
+ }).replace(DANGEROUS_ATTRIBUTES, "").replace(TEMPLATE_INJECTION, "").replace(DANGEROUS_FUNCTIONS, "");
179
+ passes++;
180
+ } while (result !== previous && passes < MAX_FILTER_PASSES);
181
+ if (config.aggressiveMode && SUSPICIOUS_PATTERNS.test(result)) {
182
+ const tempCheck = result.replace(/<[^>]*>/g, "");
183
+ if (SUSPICIOUS_PATTERNS.test(tempCheck)) {
184
+ 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, "");
185
+ }
186
+ }
187
+ result = result.replace(/\s+/g, " ").trim();
188
+ if (config.aggressiveMode && !HAS_TEXT_CONTENT.test(result)) {
189
+ return "";
190
+ }
191
+ return escapeAngleBrackets(result);
192
+ } catch {
193
+ 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();
194
+ }
195
+ }
196
+ function analyze(input, options) {
197
+ const startTime = performance.now();
198
+ const originalStr = String(input ?? "");
199
+ const sanitized = sanitize(input, options);
200
+ const processingTime = performance.now() - startTime;
201
+ const hadThreats = sanitized !== originalStr;
202
+ const threatLevel = assessThreatLevel(originalStr);
203
+ return {
204
+ content: sanitized,
205
+ hadThreats,
206
+ processingTime,
207
+ threatLevel
208
+ };
209
+ }
210
+ function sanitizeBatch(inputs, options) {
211
+ return inputs.map((input) => {
212
+ try {
213
+ return sanitize(input, options);
214
+ } catch {
215
+ return "";
216
+ }
217
+ });
218
+ }
219
+ function escape(input) {
220
+ if (input === null || input === void 0) return "";
221
+ const str = typeof input === "string" ? input : String(input);
222
+ return str.replace(NULL_CONTROL_CHARS, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
223
+ }
224
+ function escapeAttribute(input) {
225
+ if (input === null || input === void 0) return "";
226
+ const str = (typeof input === "string" ? input : String(input)).replace(NULL_CONTROL_CHARS, "");
227
+ let out = "";
228
+ for (const char of str) {
229
+ const code = char.codePointAt(0);
230
+ if (/[a-zA-Z0-9]/.test(char)) {
231
+ out += char;
232
+ } else if (code < 256) {
233
+ out += `&#x${code.toString(16).padStart(2, "0")};`;
234
+ } else {
235
+ out += `&#x${code.toString(16)};`;
236
+ }
237
+ }
238
+ return out;
239
+ }
240
+ function escapeUrl(input, options) {
241
+ if (input === null || input === void 0) return "";
242
+ const config = { ...DEFAULT_OPTIONS, ...options };
243
+ const str = (typeof input === "string" ? input : String(input)).trim();
244
+ if (!str) return "";
245
+ const normalized = str.replace(/[\x00-\x20\x7F-\x9F]/g, "");
246
+ const colon = normalized.indexOf(":");
247
+ if (colon !== -1) {
248
+ const beforeColon = normalized.slice(0, colon);
249
+ if (!/[/?#]/.test(beforeColon)) {
250
+ const protocol = beforeColon.toLowerCase();
251
+ if (!config.allowedProtocols.map((p) => p.toLowerCase()).includes(protocol)) {
252
+ return "";
253
+ }
254
+ }
255
+ }
256
+ return escapeAttribute(normalized);
257
+ }
258
+ function getVersion() {
259
+ return VERSION;
260
+ }
261
+ function getStats() {
262
+ return {
263
+ version: getVersion(),
264
+ securityLevel: "100% XSS Protection",
265
+ performance: "Optimized for high-throughput"
266
+ };
267
+ }
42
268
  var Purifai = class {
43
269
  /**
44
270
  * Sanitize input with maximum security protection
45
- *
271
+ *
46
272
  * @param input - Content to sanitize (string, object, or any type)
47
273
  * @param options - Optional configuration
48
274
  * @returns Sanitized string safe for HTML output
49
- *
275
+ *
50
276
  * @example
51
277
  * ```typescript
52
278
  * import { Purifai } from 'purifai';
53
- *
279
+ *
54
280
  * const clean = Purifai.sanitize('<script>alert("xss")</script>Hello World');
55
281
  * console.log(clean); // "Hello World"
56
282
  * ```
57
283
  */
58
284
  static sanitize(input, options) {
59
- const config = { ...this.defaultOptions, ...options };
60
- if (input === null || input === void 0) return "";
61
- let str;
62
- try {
63
- if (typeof input === "string") {
64
- str = input;
65
- } else if (typeof input === "object") {
66
- str = JSON.stringify(input);
67
- } else {
68
- str = String(input);
69
- }
70
- } catch {
71
- return "";
72
- }
73
- if (str.length > config.maxLength) {
74
- str = str.substring(0, config.maxLength);
75
- }
76
- if (!str.trim()) return "";
77
- let result = str.replace(NULL_CONTROL_CHARS, "");
78
- try {
79
- result = this.decodeEncodingBypasses(result);
80
- result = this.handlePolyglotAttacks(result);
81
- result = result.replace(DANGEROUS_TAGS_WITH_CONTENT, "").replace(EVENT_HANDLERS_ENHANCED, "").replace(DANGEROUS_PROTOCOLS_ENHANCED, (match) => {
82
- const safeProtocols = config.allowedProtocols.join("|");
83
- return match.match(new RegExp(`^(?:href|src)\\s*=\\s*["']?\\s*(?:${safeProtocols}):\\/\\/`, "i")) ? match : "";
84
- }).replace(DANGEROUS_ATTRIBUTES, "").replace(TEMPLATE_INJECTION, "").replace(DANGEROUS_FUNCTIONS, "");
85
- if (config.aggressiveMode && SUSPICIOUS_PATTERNS.test(result)) {
86
- const tempCheck = result.replace(/<[^>]*>/g, "");
87
- if (SUSPICIOUS_PATTERNS.test(tempCheck)) {
88
- 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, "");
89
- }
90
- }
91
- return result.replace(/\s+/g, " ").trim();
92
- } catch (error) {
93
- 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();
94
- }
285
+ return sanitize(input, options);
95
286
  }
96
287
  /**
97
288
  * Sanitize input and return detailed analysis
98
- *
289
+ *
99
290
  * @param input - Content to sanitize
100
291
  * @param options - Optional configuration
101
292
  * @returns Detailed sanitization result with threat analysis
102
- *
293
+ *
103
294
  * @example
104
295
  * ```typescript
105
296
  * const result = Purifai.analyze('<script>alert("xss")</script>Hello');
@@ -109,93 +300,14 @@ var Purifai = class {
109
300
  * ```
110
301
  */
111
302
  static analyze(input, options) {
112
- const startTime = performance.now();
113
- const originalStr = String(input || "");
114
- const sanitized = this.sanitize(input, options);
115
- const processingTime = performance.now() - startTime;
116
- const hadThreats = sanitized !== originalStr;
117
- const threatLevel = this.assessThreatLevel(originalStr);
118
- return {
119
- content: sanitized,
120
- hadThreats,
121
- processingTime,
122
- threatLevel
123
- };
124
- }
125
- /**
126
- * Decode common encoding bypasses
127
- */
128
- static decodeEncodingBypasses(str) {
129
- let result = str;
130
- try {
131
- result = decodeURIComponent(result);
132
- result = result.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => {
133
- return String.fromCharCode(parseInt(hex, 16));
134
- });
135
- result = result.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => {
136
- return String.fromCharCode(parseInt(hex, 16));
137
- });
138
- result = result.replace(/&#(\d+);/g, (_, num) => {
139
- return String.fromCharCode(parseInt(num, 10));
140
- });
141
- result = result.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => {
142
- return String.fromCharCode(parseInt(hex, 16));
143
- });
144
- } catch {
145
- }
146
- return result;
147
- }
148
- /**
149
- * Enhanced polyglot attack handling - Addresses sophisticated XSS techniques
150
- */
151
- static handlePolyglotAttacks(str) {
152
- let result = str;
153
- try {
154
- result = result.replace(POLYGLOT_JAVASCRIPT, "");
155
- result = result.replace(POLYGLOT_EVENTS, "");
156
- result = result.replace(ENCODED_TAGS, "");
157
- result = result.replace(/javascript\s*:\s*\/\*[\s\S]*?\*\//gi, "");
158
- result = result.replace(/javascript\s*:\s*\/\*[^*]*\*\/[^>]*/gi, "");
159
- result = result.replace(/<form[^>]*>[\s\S]*?<\/form>/gi, "");
160
- result = result.replace(/<math[^>]*>[\s\S]*?<\/math>/gi, "");
161
- result = result.replace(/%0[AD]/gi, "");
162
- if (/svg|sVg|\x3c|\\\w{3}/i.test(result)) {
163
- result = result.replace(/svg[^>]*>/gi, "");
164
- result = result.replace(/sVg[^>]*>/gi, "");
165
- result = result.replace(/\\x\w{2}/gi, "");
166
- result = result.replace(/<\/[^>]*>/gi, "");
167
- result = result.replace(/<[^>]*>/gi, "");
168
- }
169
- } catch {
170
- result = result.replace(/<[^>]*>/g, "").replace(/[<>]/g, "");
171
- }
172
- return result;
173
- }
174
- /**
175
- * Assess threat level of input content
176
- */
177
- static assessThreatLevel(input) {
178
- if (!input) return "none";
179
- if (/<script/i.test(input) || /javascript\s*:/i.test(input) || /on\w+\s*=/i.test(input)) {
180
- return "critical";
181
- }
182
- if (/<(iframe|object|embed|form|svg|math)/i.test(input) || /eval\s*\(/i.test(input)) {
183
- return "high";
184
- }
185
- if (/<(style|link|meta)/i.test(input) || /expression\s*\(/i.test(input)) {
186
- return "medium";
187
- }
188
- if (/<[^>]*>/i.test(input)) {
189
- return "low";
190
- }
191
- return "none";
303
+ return analyze(input, options);
192
304
  }
193
305
  /**
194
306
  * Check if input contains dangerous patterns
195
- *
307
+ *
196
308
  * @param input - Content to check
197
309
  * @returns true if dangerous content detected
198
- *
310
+ *
199
311
  * @example
200
312
  * ```typescript
201
313
  * const isDangerous = Purifai.isDangerous('<script>alert("xss")</script>');
@@ -203,20 +315,15 @@ var Purifai = class {
203
315
  * ```
204
316
  */
205
317
  static isDangerous(input) {
206
- if (!input) return false;
207
- try {
208
- 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);
209
- } catch {
210
- return true;
211
- }
318
+ return isDangerous(input);
212
319
  }
213
320
  /**
214
321
  * Batch sanitize multiple inputs for optimal performance
215
- *
322
+ *
216
323
  * @param inputs - Array of inputs to sanitize
217
324
  * @param options - Optional configuration
218
325
  * @returns Array of sanitized strings
219
- *
326
+ *
220
327
  * @example
221
328
  * ```typescript
222
329
  * const cleaned = Purifai.sanitizeBatch([
@@ -227,57 +334,73 @@ var Purifai = class {
227
334
  * ```
228
335
  */
229
336
  static sanitizeBatch(inputs, options) {
230
- return inputs.map((input) => {
231
- try {
232
- return this.sanitize(input, options);
233
- } catch {
234
- return "";
235
- }
236
- });
337
+ return sanitizeBatch(inputs, options);
338
+ }
339
+ /**
340
+ * Escape text for an HTML body context (lossless — nothing is removed)
341
+ *
342
+ * @example
343
+ * ```typescript
344
+ * Purifai.escape('if (a<b && c>d)'); // "if (a&lt;b &amp;&amp; c&gt;d)"
345
+ * ```
346
+ */
347
+ static escape(input) {
348
+ return escape(input);
349
+ }
350
+ /**
351
+ * Escape text for an HTML attribute value, safe even when unquoted
352
+ *
353
+ * @example
354
+ * ```typescript
355
+ * `<div title="${Purifai.escapeAttribute(userInput)}">`
356
+ * ```
357
+ */
358
+ static escapeAttribute(input) {
359
+ return escapeAttribute(input);
360
+ }
361
+ /**
362
+ * Escape a URL, returning '' if its protocol is not allowed
363
+ *
364
+ * @example
365
+ * ```typescript
366
+ * Purifai.escapeUrl('javascript:alert(1)'); // ""
367
+ * Purifai.escapeUrl('https://example.com'); // escaped, safe for href
368
+ * ```
369
+ */
370
+ static escapeUrl(input, options) {
371
+ return escapeUrl(input, options);
237
372
  }
238
373
  /**
239
374
  * Get version information
240
375
  */
241
376
  static getVersion() {
242
- return "1.0.0";
377
+ return getVersion();
243
378
  }
244
379
  /**
245
380
  * Get performance and security statistics
246
381
  */
247
382
  static getStats() {
248
- return {
249
- version: this.getVersion(),
250
- securityLevel: "100% XSS Protection",
251
- performance: "Optimized for high-throughput"
252
- };
383
+ return getStats();
253
384
  }
254
385
  };
255
- Purifai.defaultOptions = {
256
- maxLength: 1e6,
257
- // 1MB
258
- allowBasicHtml: false,
259
- allowedProtocols: ["http", "https", "mailto"],
260
- aggressiveMode: true
261
- };
262
- var sanitize = Purifai.sanitize;
263
- var analyze = Purifai.analyze;
264
- var isDangerous = Purifai.isDangerous;
265
- var sanitizeBatch = Purifai.sanitizeBatch;
266
386
  var index_default = Purifai;
267
387
  // Annotate the CommonJS export names for ESM import in node:
268
388
  0 && (module.exports = {
269
389
  Purifai,
270
390
  analyze,
391
+ escape,
392
+ escapeAttribute,
393
+ escapeUrl,
271
394
  isDangerous,
272
395
  sanitize,
273
396
  sanitizeBatch
274
397
  });
275
398
  /**
276
399
  * 🛡️ PURIFAI - Ultra-Secure HTML Sanitizer
277
- *
400
+ *
278
401
  * Advanced XSS protection with polyglot attack resistance.
279
402
  * Blocks sophisticated obfuscation techniques that bypass other sanitizers.
280
- *
281
- * @version 1.0.0
403
+ *
404
+ * @version 2.0.0
282
405
  * @license MIT
283
406
  */
package/dist/index.d.cts 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;