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/README.md +81 -49
- package/dist/index.cjs +282 -159
- package/dist/index.d.cts +80 -25
- package/dist/index.d.ts +80 -25
- package/dist/index.js +279 -159
- package/package.json +28 -11
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
|
|
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, "<").replace(/>/g, ">");
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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<b && c>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
|
|
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
|
|
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
|
|
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<b && c>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
|
-
|
|
132
|
-
|
|
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;
|